Ruby - access to multidimensional hash and exception access to nil object

Possible duplicate:
Ruby: Niels in IF statement
Is there a clean way to avoid calling the method on nil in the hash nested parameter?

Let's say I'm trying to access a hash like this:

my_hash['key1']['key2']['key3'] 

This is good if there are key1, key2 and key3 in the hash (es), but what if, for example, key1 does not exist?

Then I would get NoMethodError: undefined method [] for nil:NilClass . And nobody likes that.

So far I have been doing this conditional type execution:

if my_hash['key1'] && my_hash['key1']['key2'] ...

This is appropriate, is there another Rubiest way?

+68
ruby conditional hash
Apr 12 '12 at 19:50
source share
3 answers

There are many approaches to this.

If you are using Ruby 2.3 or higher, you can use dig

 my_hash.dig('key1', 'key2', 'key3') 

Many people stick to a simple ruby ​​and the && test chain.

You can use stdlib hash # fetch :

 my_hash.fetch('key1', {}).fetch('key2', {}).fetch('key3', nil) 

Some people like the binding of the ActiveSupport # try method.

 my_hash.try(:[], 'key1').try(:[], 'key2').try(:[], 'key3') 

Others use andand

 myhash['key1'].andand['key2'].andand['key3'] 

Some people think that egocentric nils is a good idea (although someone might prey on you and torture you if they find you are doing it).

 class NilClass def method_missing(*args); nil; end end my_hash['key1']['key2']['key3'] 

You can use Enumerable # reduce (or add an alias).

 ['key1','key2','key3'].reduce(my_hash) {|m,k| m && m[k] } 

Or maybe expand the hash or just your target hash object using the nested search method

 module NestedHashLookup def nest *keys keys.reduce(self) {|m,k| m && m[k] } end end my_hash.extend(NestedHashLookup) my_hash.nest 'key1', 'key2', 'key3' 

Oh, and how could we forget maybe monad?

 Maybe.new(my_hash)['key1']['key2']['key3'] 
+135
Apr 12 2018-12-12T00:
source share

Conditions my_hash['key1'] && my_hash['key1']['key2'] do not feel DRY .

Alternative:

1) autovivification magic. From this post:

 def autovivifying_hash Hash.new {|ht,k| ht[k] = autovivifying_hash} end 

Then, in your example:

 my_hash = autovivifying_hash my_hash['key1']['key2']['key3'] 

It is similar to the Hash.fetch approach, since both of them work with new hashes as default values, but this moves the details at creation time. Admittedly, this is a bit of a hoax: it will never return β€œzero” just an empty hash that is created β€œon the fly”. Depending on your use case, this can be wasteful.

2) Give up the data structure using the search engine and handle the unoccupied case behind the scenes. The simplest example:

 def lookup(model, key, *rest) v = model[key] if rest.empty? v else v && lookup(v, *rest) end end ##### lookup(my_hash, 'key1', 'key2', 'key3') => nil or value 

3) If you feel monadic, you can take a look at it, Maybe

+5
Apr 12 2018-12-12T00:
source share

You can also use Object # and and .

 my_hash['key1'].andand['key2'].andand['key3'] 
+5
Apr 12 2018-12-12T00:
source share



All Articles