9

If I try to access a hash element that isn't present, I get NoMethodError: undefined method '[]' for nil:NilClass. However, I can't predict which elements will be present.

@param_info = {}
@param_info["drug"]["name"]
# => NoMethodError: undefined method `[]' for nil:NilClass

How can I avoid raising this error when an element is unexpectedly nil?

user513951
  • 12,445
  • 7
  • 65
  • 82
Rob
  • 7,028
  • 15
  • 63
  • 95

4 Answers4

14

Ruby 2.3.0 introduced a method called dig on both Hash and Array.

It returns nil if an element is missing at any level of nesting.

h1 = {}
h2 = { a: {} }
h3 = { a: { b: 100 } }

h1.dig(:a, :b) # => nil
h2.dig(:a, :b) # => nil
h3.dig(:a, :b) # => 100

Your use case would look like this:

@param_info.dig('drug', 'name')
user513951
  • 12,445
  • 7
  • 65
  • 82
7

If I understand your question correctly i.e. make it forgiving in case an attribute value is missing, then you could try the following:

@param_info.try(:fetch, :drug).try(:fetch, :name)

This might return nil as well, but this will get rid of the error undefined methods '[]' for nil:NilClass

Update:

In order to handle keys that do not exist, you could try the following. (Got this hint from Equivalent of try for a hash):

@param_info.try(:[], :drug).try(:[], :name)
Community
  • 1
  • 1
vee
  • 38,255
  • 7
  • 74
  • 78
  • I really like how simple this is, but it still gives me an error: key not found: "drddsug" (I put in random characters to test) – Rob Nov 21 '13 at 16:38
  • @Rob, please see my updated answer. This will be even more fogiving! – vee Nov 21 '13 at 16:44
  • 1
    @Rob Ruby 2.3.0 introduced a new method called `Hash#dig` to solve this. Please see my answer below. – user513951 Jan 06 '16 at 01:39
1

I would do something like this :

begin
  @param_info.fetch(:drug).fetch(:name)
rescue KeyError
  # handle key not found
end

You can do it in a single function :

def param_info_key(info, key1, key2)
  info.fetch(key1).fetch(key2)
rescue KeyError
  nil
end

param_info_key({}, :a, :b) # nil
param_info_key({a: {}}, :a, :b) # nil
param_info_key({a: {b: "foo"}}, :a, :b) # "foo"

Sample :

irb(main):001:0> s = {}
=> {}
irb(main):002:0> begin
irb(main):003:1*   s.fetch(:foo).fetch(:bar)
irb(main):004:1> rescue KeyError
irb(main):005:1>   puts "Key not found"
irb(main):006:1> end
Key not found
=> nil
Thomas Ruiz
  • 3,611
  • 2
  • 20
  • 33
0

You can simply use unless to test this:

irb(main):001:0> def checker(val)
irb(main):002:1>   unless val.nil?
irb(main):003:2>     puts 'working on the railroad'
irb(main):004:2>     end
irb(main):005:1>   end
=> nil
irb(main):006:0> x = nil
=> nil
irb(main):007:0> checker(x)
=> nil
irb(main):008:0> y = 1
=> 1
irb(main):009:0> checker(y)
working on the railroad
=> nil
irb(main):010:0>