6

I'm sure I've seen an elegant solution to this before, but I can't quite find it:

I have a rails controller which may-or-may-not have the following hash element:

myhash[:parent_field]

Inside that parent field, a child element could also be blank. I'm currently checking that via the (very ugly) method:

if (!myhash[:parent_field] || !myhash[:parent_field][:child_field] || myhash[:parent_field][:child_field].blank?)

Which works, but I figure - surely - there has to be a more elegant way. Just to reiterate:

  • myhash[:parent_field] may or may not exist
  • If it does exist, myhash[:parent_field][:child_field] may or may not exist
  • If that exists, it may or may not be blank.
PlankTon
  • 12,443
  • 16
  • 84
  • 153

5 Answers5

9

#fetch is your friend:

my_hash.fetch(:parent, {})[:child].blank?
d11wtq
  • 34,788
  • 19
  • 120
  • 195
  • Note that this will raise an exception if the `:parent` key does not exist. – Andrew Marshall Apr 28 '12 at 02:26
  • @AndrewMarshall No it won't. Read up on what fetch does. – d11wtq Apr 28 '12 at 02:48
  • Sorry, didn't see the second argument there. It will if there isn't, though. – Andrew Marshall Apr 28 '12 at 02:57
  • Correct :) That's why fetch is useful. It lets you specify a default value if the element does not exist. I use it lots. – d11wtq Apr 28 '12 at 04:03
  • Presumably, this can't chain easily, though (?). That's a reason why I prefer `rescue nil` (see my answer) - although I'll admit using the line `rescue` method does make me a bit nervous. So long as that line's clear, though, it does enable you to chain with ease: `!(h[:level1][:level2][:level3][:level4] rescue nil).blank?` –  Apr 28 '12 at 22:13
  • 1
    Why not? `my_hash.fetch(:parent, {}).fetch(:child, {}).fetch(:other)`. Obviously it gets to a point where that would get clunky, but I think that would suggest a design re-think... – d11wtq Apr 29 '12 at 00:08
3

What I would do is just use local variables to ease your burden:

unless (p=foo[:parent]) && (c=p[:child]) && !c.blank?
  # Stuff is borked!
end

But let's explore alternatives, for fun…


If you can't change your data structure (that's a Hash, by the way, not an Array) then you can use the Ruby andand gem in conjunction with the Rails' try as lazy ways of calling methods on things that might be nil objects.


You could alternatively change your data structure to Hashes that return empty auto-vivifying hashes when you ask for a key that does not exist:

mine = Hash.new{ |h,k| Hash.new(&h.default_proc) }
mine[:root] = { dive:42 }
p mine[:root][:dive]        #=> 42
p mine[:ohno][:blah][:whee] #=> {}
p mine[:root][:blah][:whee] #=> undefined method `[]' for nil:NilClass (NoMethodError)

However, you'd have to ensure that every object in your hierarchy was one of these hashes (which I explicitly failed to do for the contents of :dive, resulting in the error).


For alternative fun, you could add your own magic lookup method:

class Hash
  def divedive(*keys)
    obj = self
    keys.each do |key|
      return obj unless obj && obj.respond_to?(:[])
      obj = obj[key]
    end
    obj
  end
end

if myarray.divedive(:parent,:child).blank?
  # ...
Phrogz
  • 296,393
  • 112
  • 651
  • 745
1

This is a frequently asked question and should probably be closed as a duplicate of

The first in that list was closed as a dup of the other two, though I believe my answer there has more comprehensive coverage of techniques to address this problem than the later two.

Community
  • 1
  • 1
dbenhur
  • 20,008
  • 4
  • 48
  • 45
  • Darn - you're right. I did try searching, but couldn't bring up anything relevant. I've voted to close. And many thanks. :-) – PlankTon Apr 28 '12 at 00:27
0

It probably depends on what your actual needs are, but an OOP approach to this would be to convert the arrays and hashes into actual objects. Each object would manage its relationships (similar to ActiveRecord), and would know how to get children and parents.

Peter Brown
  • 50,956
  • 18
  • 113
  • 146
0

Since nil.blank? is true, you can remove the middle condition and simplify to this:

if !myarray[:parent_field] || myarray[:parent_field][:child_field].blank?

Also, calling a Hash myarray is a bit misleading.

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214