Ruby Style: How to check whether a nested hash element exists
Consider a "person" stored in a hash. Two examples are:
fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
If the "person" doesn't have any children, the "children" element is not present. So, for Mr. Slate, we can check whether he has parents:
slate_has_children = !slate[:person][:children].nil?
So, what if we don't know that "slate" is a "person" hash? Consider:
dino = {:pet => {:name => "Dino"}}
We can't easily check for children any longer:
dino_has_children = !dino[:person][:children].nil?
NoMethodError: undefined method `[]' for nil:NilClass
So, how would you check the structure of a hash, especially if it is nested deeply (even deeper than the examples provided here)? Maybe a better question is: What's the "Ruby way" to do this?
Solution 1:
The most obvious way to do this is to simply check each step of the way:
has_children = slate[:person] && slate[:person][:children]
Use of .nil? is really only required when you use false as a placeholder value, and in practice this is rare. Generally you can simply test it exists.
Update: If you're using Ruby 2.3 or later there's a built-in
dig
method that does what's described in this answer.
If not, you can also define your own Hash "dig" method which can simplify this substantially:
class Hash
def dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end
This method will check each step of the way and avoid tripping up on calls to nil. For shallow structures the utility is somewhat limited, but for deeply nested structures I find it's invaluable:
has_children = slate.dig(:person, :children)
You might also make this more robust, for example, testing if the :children entry is actually populated:
children = slate.dig(:person, :children)
has_children = children && !children.empty?
Solution 2:
With Ruby 2.3 we'll have support for the safe navigation operator: https://www.ruby-lang.org/en/news/2015/11/11/ruby-2-3-0-preview1-released/
has_children
now could be written as:
has_children = slate[:person]&.[](:children)
dig
is being added as well:
has_children = slate.dig(:person, :children)