Featured image

When building applications in Ruby, almost every developer works with hashes on a daily basis. We especially run into nested hashes all the time, for example:

u = { 'user' => { 'id' => 1, 'lang' => { 'id' => 1, 'name' => 'Ruby' } } }
u['user']['lang']['name'] # => "Ruby"

Chaining keys like this looks cool, but it only works as long as every key actually holds a value:

u = { 'user' => { 'id' => 2, 'lang' => nil } }
u['user']['lang']['name'] # undefined method '[]' for nil (NoMethodError)

To fix this error, you could use the send method (though it looks pretty terrifying):

u.send(:[], 'user')&.send(:[], 'lang')&.send(:[], 'name') || 'No name'

Or a much more elegant solution using dig:

u.dig('user', 'lang', 'name') || 'No name'

By the way, in case you didn’t know, dig can also be combined with arrays:

u = { 'user' => { 'id' => 2, 'roles' => [{ 'id' => 1, 'name' => 'admin' }] } }
u.dig('user', 'roles', 0, 'name') # => "admin"

But what if you need a fallback value for a key that isn’t present in the hash or if the hash itself might be nil? Over my years of working with Ruby, I’ve landed on this approach:

h = [
  { 'key' => 'value' },
  {},
  nil
].sample

# Rubocop complains about this when enabled Style/SingleArgumentDig
h&.dig('otherkey') || 'None'

# Looks terrible (I don't think this is a suitable case for the send method)
h&.send(:[], 'otherkey') || 'None'

# It's better, but duplicating the default value looks disgusting
h&.fetch('otherkey', 'None') || 'None'

# Same as the example above, only the default value is in the block
h&.fetch('otherkey') { 'None' } || 'None'

# So far this is the best option of all
h&.fetch('otherkey', nil) || 'None'

# Unfortunately, we can't use it this way
# because if the key is missing, an error will occur.
h&.fetch('otherkey') || 'None'

Have you encountered this in your work? Is there a more elegant solution that I might have missed?

P.S.

I also recommend reading this article.