From: Ross Bamford Date: 2006-09-27T03:28:36+09:00 Subject: Re: Accessing Nested Hashes Directly On Wed, 2006-09-27 at 02:55 +0900, atraver@gmail.com wrote: > I've been having a problem for a while with Ruby where accessing a hash > within a hash will fail if the first hash is nil. > [...snipped...] > Finally, I wrote a method to work around this problem: > > def get_value(object, *path) > new_object = object > > for item in path > item = item.to_s > > if new_object > if new_object[item] > new_object = new_object[item] > else > new_object = nil > end > else > break > end > end > > if new_object.kind_of?(String) > return new_object.strip > else > return new_object > end > end > > This method can be called like this: > > first_name = get_value(params, :body, :user, :name, :given) > > It will traverse the hash and kick back a nil at the first problem it > finds instead of raising an exception, or will return the value if it > actually exists. > > Here's my question, though: is this code efficient? Is there a better > way? Am I missing something fundamental in Ruby that would solve this > without the need for the new method? I think you can rewrite that method like this: def get_value(hash, *path) path.inject(hash) { |obj, item| obj[item] || break } end If you're up for some core-modifying mayhem, You could put it on Hash, and extend [] with 'path' capabilities: class Hash alias :__fetch :[] def traverse(*path) path.inject(self) { |obj, item| obj.__fetch(item) || break } end def [](*args) (args.length > 1) ? traverse(*args) : __fetch(*args) end end This way works like this: h = { :name => { :first => 'Ross', }, :contact => { :phone => { :office => '345345' } } } p h.traverse(:name, :first) # => "Ross" p h.traverse(:name, :middle) # => nil p h[:contact] # => {:phone=>{:office=>"345345"}} p h[:contact, :phone, :office] # => "345345" p h[:contact, :phone, :fax] # => nil Of course, there may be better ways to solve the underlying problem... -- Ross Bamford - rosco@roscopeco.REMOVE.co.uk