The “dig” method

Learn what the dig method is and how to use it in Ruby.

Iteration over nested data structure

Let’s look at the following nested data structure:

users = [
    { first: 'John', last: 'Smith', address: { city: 'San Francisco', country: 'US' } },
    { first: 'Pat', last: 'Roberts', address: { country: 'US' } },
    { first: 'Sam', last: 'Schwartzman' }
]

The structure above has its data scheme. The format is the same for every record, and there are three total records in this array, but the two last records are missing something. For example, the second one is missing the city. The third record doesn’t have an address. What we want is to print all the cities from all the records, and in the future we might have more than three.

The first thing that comes to our mind is iteration over the array of elements and using standard hash access:

users.each do |user|
  puts user[:address][:city]
end

Let’s give it a try:

San Francisco

-:8:in `block in <main>': undefined method `[]' for nil:NilClass (NoMethodError).

As we can see, it produces an error. But why? Well, let’s try to access every element manually

$ pry
> users[0][:address][:city]
=> "San Francisco"
> users[1][:address][:city]
=> nil
> users[2][:address][:city]
NoMethodError: undefined method `[]' for nil:NilClass

Here we go. It works for the first element. There is also no error for the second element, and the result is just nil. However, for the third user, with index 2, the expression users[2][:address] gives nil because there is no address field for Sam. Then, we execute nil[:city], which always produces an error because we can’t access nil like that because there is nothing insidenil.

Fixing the error

So, how do we fix this program? We can use an if statement:

users.each do |user|
  if user[:address]
    puts user[:address][:city]
  end
end

It works now, and there is no error; we did a great job! But what if we add one more nested object to address so that we always have two lines of street address for the address node?

street: { line1: '...', line2: '...' }

Here’s how it looks:

users = [
    {
        first: 'John',
        last: 'Smith',
        address: {
            city: 'San Francisco',
            country: 'US',
            street: {
                line1: '555 Market Street',
                line2: 'apt 123'
            }
        }
    },
    { first: 'Pat', last: 'Roberts', address: { country: 'US' } },
    { first: 'Sam', last: 'Schwartzman' }
]

Now, we want to print all line1 addresses for all the records. The first thing we will do is to improve the already existing program by adding [:line1] navigation:

users.each do |user|
  if user[:address]
    puts user[:address][:street][:line1]
  end
end

However, the code above will not work on the second record, because for the second record,user[:address][:street] is nil. If that’s not clear, we should try it in our pry or irb console.

We can add another check for nil:

users.each do |user|
  if user[:address] && user[:address][:street]
    puts user[:address][:street][:line1]
  end
end

It works great with the second condition. Now, we will check if address is not nil and if the following street is not nil:

if user[:address] && user[:address][:street]

In other words, for every level of nesting, we will need to add one more check, so our program won’t raise any errors on nil. It wasn’t very convenient and programmer-friendly, so Ruby starting from 2.3.0—you can check your version by running ruby -v—has the dig method:

users.each do |user|
  puts user.dig(:address, :street, :line1)
end

Get hands-on with 1200+ tech skills courses.