Ruby Safe Navigation
Tony Hoare came up will Null references in 1965, and later called it a billion-dollar mistake. There are some languages today where null values are not allowed, but ruby isn’t one of them.
There is a common idiom to protect against one of the pitfalls of having a nil value in ruby. Say you have this line:
user.process_address
If your user
object doesn’t exist, you’re going to have a bad time.
So, you can do something like
if user
user.process_address
end
or
user && user.process_address
or
unless user.nil
user.process_address
end
to ensure that you don’t get a NoMethodError
.
Now, there is some syntactic sugar for this:
user&.process_address
At least, once this is common usage, there will be a single idiom to recognise, instead of the many slight variations on a theme. That’s good. On the other hand, ruby has an extra, slightly obtuse, bit of language to grok.
Of course, your code didn’t really resolve the fact that you thought you’d have a user object at this time, and you didn’t, but that’s a different problem, better solved by something like the Null Object pattern.
Warning
There is a drawback here. If you are chaining method calls:
user&.research&.topics&.find(:law_of_demeter)
=> nil
If this fails, you’re not going to be sure exactly which object is missing. Maybe the user doesn’t exist, maybe they haven’t researched the law of demeter, or maybe some intermediate step failed.
Dig
For similar reasons to the &
safe navigation operator, ruby 2.3 has introduced
a safer way to access nested hashes: dig
Here is the happy path:
example = {user: {age: 21, address: {street: '29 Acacia Ave.', postcode: 'BN1 ANA'}}}
example[:user][:address][:street]
# => "29 Acacia Ave."
example[:user][:age]
# => 21
example[:user][:weight]
# => nil
example.dig :user, :address, :street
# => "29 Acacia Ave."
example.dig :user, :age
# => 21
example.dig :user, :weight
# => nil
Here, you can see the advantage of dig:
example = {user: {age: 21, address: nil}}
=> {:user=>{:age=>21, :address=>nil}}
irb(main):011:0> example.dig :user, :address, :street
=> nil
irb(main):012:0> example[:user][:address][:street]
# => NoMethodError on nil