Avoiding “NoMethodError” for nil in Ruby
How the safe navigation operator and the #dig method will change your life
One of my main frustrations as a junior Ruby developer was dealing with “NoMethodError” when there was a risk my object was nil. Ruby 2.3.0 introduced a really useful way to handle this: the safe navigation operator and #dig method. Let’s have a look!
The Safe Navigation operator
Let’s say you have a Rails application which manages orders. Each order belongs_to a client, this client has one address which has a street attribute. Let’s try to display the first name of the client of our first order in console. We do something like this:
puts Order.first.client.address.street# => NoMethodError: undefined method `client' for nil:NilClass
Of course, we get an error. It seems that we do not have any order in our database. Order.first is nil and we cannot call client on it.
Intuitively, the first way to avoid this would be to add a condition:
order = Order.first
if order && order.client && order.client.address
puts order.client.address.street
end# => nil
It works! The code does not break and would display the street of the address of our client if it has one.
But it is ugly, annoying to code and could lead us to a lot of mistakes (what if we forget one condition?!)
Let’s do something better. ActiveSupport includes the #try method which can help us. By using it, we can refactor our code:
order = Order.first
puts order.try(:client).try(:address).try(:street)# => nil
It is getting better. The #try method check if it is called on nil. If so it returns nil, if not it calls the method it has as an argument.
But we can do even better using the safe navigation operator introduced in Ruby 2.3.0. The operator, which also exists in C# or Swift for example, allows us to write this really easily and clearly:
order = Order.first
puts order&.client&.address&.street# => nil
Perfect ! This is what we need. To go further, we should also make sure we correctly deal with “NoMethodError” for nil in our hash and array. In Ruby 2.3.0, we can do this with the #dig method. Let’s have a look.
The #dig method
Let’s now imagine that we have an array of nested hashes coming, for example, from a third party API. We don’t exactly know which form it has.
array_of_clients =
[{name: "Vincent",
address: { street: "Rue du Plat", city: "Lyon"}}, {name: "Romain"}]
Let’s get Vincent’s street.
puts array_of_clients[0][:address][:street]# => "Rue du Plat"
Now, let’s say we don’t know if Romain has an address, so we try to display it too:
puts array_of_clients[1][:address][:street]# => NoMethodError: undefined method `[]' for nil:NilClass
Of course, it fails. Romain does not have any address so we call [:street] on nil which raises a “NoMethodError”.
Intuitively, we could also use a condition to avoid this:
if array_of_clients[1] && array_of_clients[1][:address] && array_of_clients[1][:address][:street]
puts array_of_clients[1][:address][:street]
end# => nil
So ugly… We can do better than this !
Let’s now us the #dig method to deal with this. It is really easy.
puts array_of_clients.dig(0, :address, :street)# => "Rue du Plat"puts array_of_clients.dig(1, :address, :street)# => nil
It works and does not raise an error !! The great thing about the #dig method is that it can take integer (for array indexes) as well as symbols or string (for hash keys) as arguments. You can also call it on a hash as well as on an array.
The #dig method calls [] with its first arguments. If it returns nil, the #dig method returns nil. If not, it calls [] on the result with its second argument. And so on and so on.
Conclusion
Using the safe navigation operator and the #dig method makes dealing with “NoMethodError” for nil a much less painful process. Ruby is great because it values programmers happiness. These features are new proofs of this, if needed.
Resources to go further:
I wanted to keep everything simple so I voluntarily omitted some small subtilities of the safe navigation operator and the #dig method. To learn everything about them, please refer to the articles bellow ;)