The “dig” method
Learn what the dig method is and how to use it in Ruby.
We'll cover the following
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 1400+ tech skills courses.