Ruby Nested Hash - Deep Fetch - Returning a (Default) Value for a Key That Does Not Exist in a Nested Hash


Programming in Ruby tutorials and examples

Ruby Nested Hash - Deep Fetch - Returning a (Default) Value for a Key That Does Not Exist in a Nested Hash


Ok so this is a follow on post for Ruby Hash - Fetch - Returning a (Default) Value for a Key That Does Not Exist which I wrote yesterday.

Basically I found that you can use the fetch method on a hash to either retrieve a value for a key or return a specified value. In the posts comments someone mentioned that it was useless for nested hashes, which is true. It turns out that it is not that hard to implement the behaviour of fetch for fetching a nested key / value pair.

For this I have added a few methods to the Hash class (if your using rails you could make this an initializer). The deep_fetch method will behave exactly the same as fetch but search the hash at all levels for a keys value.

1
2
3
4
5
6
7
8
9
10
class Hash
 def deep_fetch(key, default = nil)
   default = yield if block_given?
   (deep_find(key) or default) or raise KeyError.new("key not found: #{key}")
 end

 def deep_find(key)
    key?(key) ? self[key] : self.values.inject(nil) {|memo, v| memo ||= v.deep_find(key) if v.respond_to?(:deep_find) }
  end
end

Here is an example of a nested hash we are going to work with.

1
hash = { :search => "Search Term", :order => :desc, :extra => { :user => { :uid => 12345, }}}

It will find a keys value at the first level of the hash (this is the same behaviour as fetch).

1
hash.deep_fetch(:search)  # => "Search Term"

If no default value is passed as the second argument a KeyError will be raised if the key is not found (this is the same behaviour as fetch).

1
hash.deep_fetch(:no_key)  # => "KeyError: key not found no_key

It will return a default value for a key that does not exist if the default argument is passed to the method.

1
hash.deep_fetch(:no_key, :my_value) # => :my_value

It can accept a block for calculated default values.

1
hash.deep_fetch(:no_key) { 5 + 5 } # => 10

It can fetch a value for a deeply nested key.

1
2
hash.deep_fetch(:user) # => { :uid => 12345 }
hash.deep_fetch(:uid) # => 12345

Comments