ruby_dig Gem Adds Hash#dig and Array#dig from Ruby 2.3 to Earlier Versions

Introducing `ruby_dig`

It may take us some time to upgrade to Ruby 2.3.  But we’d like to be able to start using `dig` right away.  The `ruby_dig` gem solves this by adding the `dig` method to `Array` and `Hash` just like Ruby 2.3+ has natively.

The gem can be found on ruby_gems and on github.

Why Do We Need `dig`?

With the ever-growing popularity of JSON-based APIs, we all find ourselves writing code to “dig” through a parsed JSON response of nested hashes and arrays. This can be error-prone and tedious, so Ruby 2.3 added the `dig` instance method to Array and Hash to simplify the process.

For a simple example, let’s take some code that uses Github’s API to get the assignee of the first Pull Request for a given repo:

uri = Uri.parse("https://github.com/repos/:owner/:repo/pulls")
pulls_response = Net::HTTP.get_response(uri)
pulls_response.code == 200 or raise "Got non-200 response code: #{response.inspect}"

pulls = JSON.parse(pulls_response.body)

first_assignee = pulls[0]['assignee']['login']

But what if the response doesn’t come back in the expected format? Any of the `[]` operators above might return `nil` and then next `[] would raise the dreaded—and nearly useless—Ruby exception:

NoMethodError: undefined method `[]' for nil:NilClass

Here is the last line rewritten to use `dig` and to raise a useful exception if the format is unexpected:

#   first_assignee = pulls[0]['assignee']['login']
first_assignee = pulls.dig(0, 'assignee', 'login') or raise "Got unexpected response #{pulls.inspect}"

Implementation Notes

The dig method is implemented by calling `self.[]` so it will work with classes that derive from `Array` or `Hash`.  Most notably, `ActiveSupport::HashWithIndifferentAccess`.  Therefore `dig` will work fine in a Rails application looking in `params`:

params.dig(:user, :emails, 0, :friendly_name)
params.dig("user", "emails", 0, "friendly_name") # equivalent to the above

Also negative array indexes will work fine:

params.dig(:user, :emails, -1, :friendly_name) # find the last email friendly name

However, neither the Ruby 2.3 documentation nor the tests in the commit make it clear what should happen in `Array#dig` if you pass non-numeric index.  This can happen easily, if the result you got had a hash where you expected an array.  It seems in keeping with the spirit of `dig` that it should return `nil` in this case rather than raising an exception:

TypeError: no implicit conversion of Symbol into Integer

Leave a Reply

Your email address will not be published. Required fields are marked *


4 − two =