views:

381

answers:

5

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 "chilren" 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?

+2  A: 

One could use hash with default value of {} - empty hash. For example,

dino = Hash.new({})
dino[:pet] = {:name => "Dino"}
dino_has_children = !dino[:person][:children].nil? #=> false

That works with already created Hash as well:

dino = {:pet=>{:name=>"Dino"}}
dino.default = {}
dino_has_children = !dino[:person][:children].nil? #=> false

Or you can define [] method for nil class

class NilClass
  def [](* args)
     nil
   end
end

nil[:a] #=> nil
kirushik
+5  A: 

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.

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?
tadman
A: 

You can try to play with

dino.default = {}

Or for example:

empty_hash = {}
empty_hash.default = empty_hash

dino.default = empty_hash

That way you can call

empty_hash[:a][:b][:c][:d][:e] # and so on...
dino[:person][:children] # at worst it returns {}
MBO
A: 
dino_has_children = !dino.fetch(person, {})[:children].nil?

Note that in rails you can also do:

dino_has_children = !dino[person].try(:[], :children).nil?   #
Marc-André Lafortune
+1  A: 

You can use the andand gem:

require 'andand'

fred[:person].andand[:children].nil? #=> false
dino[:person].andand[:children].nil? #=> true

You can find further explanations at http://andand.rubyforge.org/.

paradigmatic