Kamilski81
Kamilski81

Reputation: 15117

How do I see if a multi-dimensional hash has a value in ruby?

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.

Upvotes: 4

Views: 241

Answers (6)

sawa
sawa

Reputation: 168101

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

Upvotes: 0

Charles Caldwell
Charles Caldwell

Reputation: 17169

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.

Upvotes: 0

mu is too short
mu is too short

Reputation: 434675

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.

Upvotes: 1

ShadyKiller
ShadyKiller

Reputation: 710

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

Upvotes: 1

Alex Wayne
Alex Wayne

Reputation: 187034

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

Upvotes: 0

MurifoX
MurifoX

Reputation: 15089

Use a begin/rescue block.

begin
  map[:extra][:raw_info][:birthday]
rescue Exception => e
  'No birthday! =('
end

Upvotes: 0

Related Questions