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
Tagged: | ruby | ruby-2-3 |
Cover Image: George Xistris, via Unsplash