Safe Navigation in Ruby: &., try, and dig

Ruby’s safe-navigation operator calls a method only when the receiver is not nil:

1
account&.owner&.address

It protects against nil; it does not protect against every invalid object.

1
2
3
account = Struct.new(:owner).new(false)
account&.owner&.address
# => NoMethodError: undefined method `address' for false

That behavior is useful. false is real data, not a missing value wearing a fake moustache.

&. versus ActiveSupport try

1
2
3
4
5
6
7
8
nil&.unknown_method
# => nil

object.try(:unknown_method)
# => nil

object.try!(:unknown_method)
# => NoMethodError

try is provided by ActiveSupport, not core Ruby. It returns nil when the receiver does not respond to the method. try! behaves more like &.: both tolerate a nil receiver but raise when a non-nil receiver lacks the method.

Prefer &. in ordinary Ruby code because it is language syntax, easier to read, and does not silently hide misspelled methods on non-nil objects.

Nested hashes

Use dig for nested hash or array access:

1
2
3
4
5
6
7
8
9
10
payload = {
user: {
address: {
city: "Saigon"
}
}
}

payload.dig(:user, :address, :city)
# => "Saigon"

This is clearer than:

1
payload&.[](:user)&.[](:address)&.[](:city)

Do not build a nil tunnel

A long chain such as:

1
order&.customer&.profile&.address&.country&.code

may be correct, but it can also hide an unclear domain contract. Ask which relationships are truly optional. If a customer must have a profile, silently returning nil may bury corrupted data instead of handling it.

Safe navigation is a precision tool. Using it everywhere because exceptions look unfriendly is how bugs become introverts.

References