---
title: Ruby safe navigation
teaser: 'Ruby''s safe navigation can hide some subtle edge cases. We explore approaches
  to think about conditional logic, alternatives to `&.`, and when `&.` is the best
  tool for the job.

  '
tags: development,web,ruby
author: Joël Quenneville
published_on: 2021-10-13
---

Ruby safe navigation, especially in long chains, can be difficult to read and can
hide some subtle edge cases.

## Safe navigation

Consider a scenario where the following is true:

1. Users are guaranteed to have an address
2. Addresses are guaranteed to have a zip code

Given an optional user, we want to either get their zip code or return `nil`.
Using very explicit code, we might write that as:

```ruby
if user
  user.address.zip
else
  nil
end
```

But we are Rubyists and want to write pleasant, terse code. We turn to the [safe
navigation operator] and refactor our code to this one-liner:

```ruby
user&.address&.zip
```

However, behavior here is subtly different.

[safe navigation operator]: https://ruby-doc.org/core-3.0.2/doc/syntax/calling_methods_rdoc.html#label-Safe+Navigation+Operator

## Conditional equivalence

When comparing various syntactic sugars for conditional logic, I find it helpful
to convert them to a standardized if/else form. When doing that with the safe
navigation chain we defined, we can see that it is subtly different than the
conditional code we started with.

```ruby
if user
  if user.address
    user.address.zip
  else
    nil
  end
else
  nil
end
```

The safe navigation version introduces extra uncertainty. Despite knowing that
we have a user present, our code is uncertain whether or not the user has an
address. This uncertainty leads to the extra nested condition.

## Invalid paths

By using syntactic sugar we've introduced some extra paths through out app that
didn't exist in our original requirements. We are now faced with a choice. We
can re-write our code to better match the reality we are trying to model.

Alternatively, we may realize our original requirements are incorrect and that
we _do_ need an extra nil check for the address. If we make this choice, we must
make sure to [add tests for the new edge case] we've discovered.

[add tests for the new edge case]: https://thoughtbot.com/blog/testing-your-edge-cases

## Propagating vs producing

The lonely operator can act in one of two roles in a method chain:

Guarding methods that **produce** uncertainty. For example
`uncertain1&.uncertain2&.uncertain3`. When using the lonely operator in this
manner, each call in the chain is equivalent to a nested condition.

**Propagating** uncertainty down the chain. Once we have a value that can
possibly be nil, every method call downstream of it also needs to check for nil.
For example: `uncertain&.certain1&.certain2`. This is also equivalent to nested
conditionals but often, what we actually _mean to express_ is a single
condition. This easily spills into **defensive coding**.

The tricky thing with `&.` is that, when reading the code, one can't tell which
of these two behaviors the author intended. To make things more complex, a chain
of `&.` might have a mix of both behaviors. Looking back at our original problem
`user&.address&.zip`, one can't tell whether or not `User#address` is a nullable
method or not.

## Alternatives

When only the first item in a chain is nullable, we can use `&&` instead of `&.`
to more accurately express our intention.

```ruby
user && user.address.zip
```

Beyond just using different syntax, there is also an opportunity to refactor.
The chain of non-nullable methods can safely be extracted out. This likely
results in cleaner code and also satisfies the [law of demeter].

```ruby
class User
  def zip
    address.zip
  end
end
```

With the given refactor we can now call `user&.zip` which _is_ equivalent to our
original if/else condition.

[law of demeter]: https://thoughtbot.com/upcase/videos/law-of-demeter

## Valid uses

So should we avoid `&.` altogether? No. It has some valid uses cases.

1. When every method in the chain might produce nil `nothing&.is&.certain` (make
   sure you have test coverage for all the edge cases!).
2. As the final method call `user&.zip`.

## Conclusion

Long chains of `&.` are usually a symptom of broader issues in a codebase such
as defensive code or leaking responsibilities. In moderation, `&.` is a helpful
tool but make sure to consider some of the other tools in your toolbox too.

## Want to upgrade your project?

If your team needs help with upgrading your Rails project, learn more about how [thoughtbot can help](https://thoughtbot.com/services/ruby-on-rails-development).
