4

Currently I am doing the following, but I am sure there must be a better way:

def birthday_defined?(map)
    map && map[:extra] && map[:extra][:raw_info] && map[:extra][:raw_info][:birthday]
end

There may be cases where only map[:extra] is defined, and then I will end up getting Nil exception errors cause map[:extra][:raw_info] doesn't exist if I dont use my checked code above.

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
Kamilski81
  • 14,409
  • 33
  • 108
  • 161
  • If `map[:extra][:raw_info][:birthday]` is defined then `map[:extra]` must also be defined. – Austin Henley Oct 22 '12 at 17:11
  • @AustinHenley But if you only test for `map[:extra][:raw_info][:birthday]` and it doesn't exist, you get a `NoMethodError` for `NilClass`.... hence the long expression (I'm guessing) – Charles Caldwell Oct 22 '12 at 17:13
  • 2
    duplicate of [Is there a clean way to avoid calling a method on nil in a nested params hash?](http://stackoverflow.com/questions/5429790/is-there-a-clean-way-to-avoid-calling-a-method-on-nil-in-a-nested-params-hash) (and many others) – tokland Oct 22 '12 at 17:57
  • To me, this is a sign that your data is structured incorrectly. We can propose a million ways of "hiding" this complexity, but the method probably just shouldn't need to dig through three hash levels in the first place. – pje Oct 22 '12 at 18:02
  • 3
    @pje Unless the hash is coming from an outside API like Facebook (as this one appears to be) and hence such complexity can't be avoided. – Charles Caldwell Oct 22 '12 at 18:06
  • Possible duplicate of [How to avoid NoMethodError for missing elements in nested hashes, without repeated nil checks?](http://stackoverflow.com/questions/4371716/how-to-avoid-nomethoderror-for-missing-elements-in-nested-hashes-without-repeat) – user513951 Jan 06 '16 at 05:24

6 Answers6

1

I have two ways. Both have the same code but subtly different:

# Method 1

def birthday_defined?(map)     
    map[:extra][:raw_info][:birthday] rescue nil # rescues current line
end

# Method 2

def birthday_defined?(map)     
    map[:extra][:raw_info][:birthday]
rescue # rescues whole method
    nil
end
ShadyKiller
  • 700
  • 8
  • 16
1

If you're using Rails, then you can use try (and NilClass#try):

value = map.try(:[], :extra).try(:[], :raw_info).try(:[], :birthday)

That looks a bit repetitive: it is just doing the same thing over and over again while feeding the result of one step into the next step. That code pattern means that we have a hidden injection:

value = [:extra, :raw_info, :birthday].inject(map) { |h, k| h.try(:[], k) }

This approach nicely generalizes to any path into map that you have in mind:

path  = [ :some, :path, :of, :keys, :we, :care, :about ]
value = path.inject(map) { |h, k| h.try(:[], k) }

Then you can look at value.nil?.

Of course, if you're not using Rails then you'll need a replacement for try but that's not difficult.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
0

Use a begin/rescue block.

begin
  map[:extra][:raw_info][:birthday]
rescue Exception => e
  'No birthday! =('
end
MurifoX
  • 14,991
  • 3
  • 36
  • 60
  • -1 for using exceptions for control flow and for over-rescuing. Don't ever rescue Exception: http://www.mikeperham.com/2012/03/03/the-perils-of-rescue-exception/ – pje Oct 22 '12 at 17:30
  • It works, so i don't know why the downvote. There are many ways to solving the problem, and this is one of them. – MurifoX Oct 22 '12 at 17:35
0

That's idiomatic why to do it. And yes it can be a little cumbersome.

If you want to extend Hash a bit though, you can do some cool stuff with something like a key path. See Access Ruby Hash Using Dotted Path Key String

def birthday_defined?
  map.dig('extra.raw_info.birthday')
end
Community
  • 1
  • 1
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
0

This is a little hacky but it will work:

def birthday_defined?(map)
    map.to_s[":birthday"]
end

If map contains :birthday then it will return the string which will evaluate to true in a conditional statement while if it doesn't contain :birthday, it will return nil.

Note: This assumes the key :birthday does not appear at potentially multiple locations in map.

Charles Caldwell
  • 16,649
  • 4
  • 40
  • 47
0

This should work for you:

def birthday_defined?(map)
  map
  .tap{|x| (x[:extra] if x)
  .tap{|x| (x[:raw_info] if x)
  .tap{|x| (x[:birthday] if x)
  .tap{|x| return x}}}}
end
Chris Gerken
  • 16,221
  • 6
  • 44
  • 59
sawa
  • 165,429
  • 45
  • 277
  • 381