Ruby's Safe Navigation Operator `&.` and is it a Code Smell?
Tags: ruby, rails, codesmells, beginners
Published: 2020-07-06 18:34:47

---
title: Ruby's Safe Navigation Operator `&.` and is it a Code Smell?
published: true
description: What is &. and how to use it.
tags: ruby, rails, codesmells, beginners
cover_image: https://i.ibb.co/7rrBV2t/luca-bravo-f92dj-POg-9k-unsplash.jpg 
canonical_url: https://blog.beezwax.net/2020/07/08/rubys-safe-navigation-operator-and-is-it-a-code-smell/
---
_Originally published on [Beezwax.net](https://blog.beezwax.net/2020/07/08/rubys-safe-navigation-operator-and-is-it-a-code-smell)._

--------
#What is `&.`?

Ruby devs are probably all too familiar with seeing this error:

```
NoMethodError (undefined method `foo' for nil:NilClass)
```
If you're not familiar with this error, you can check out Ben's post on it: {% link https://dev.to/ben/nomethoderror-undefined-method-for-nil-nilclass-explained-422b %}

Most of the time, the error is probably due to a typo, but every now and then we end up having to do something like:

```ruby
defined?(bar) && bar.foo
# returns nil if bar is nil
```

If you're on Rails, or are using ActiveSupport, you can use `present?` or `try()`:

```ruby
bar.present? && bar.foo
# returns false if bar is nil

bar.try(:foo)
# returns nil if bar is nil
```

In Ruby 2.3.0, they introduced the safe navigation operator `&.` which returns `nil` when called on a `nil` object, like so:

```ruby
bar&.foo
# returns nil if bar is nil
```

This can be pretty handy when you're not in Rails-land or just want some compact code. 

#`&.` vs `.try()`

So other than the number of characters, what's the difference between ActiveSupport's `try()` and the Ruby safe navigation operator `&.`?

`&.` will only return `nil` if called on a `nil` object. Calling `&.` will raise a `NoMethodError` if it is called on a non-`nil` object that doesn't implement the method. However, `try()` will just return `nil` if called on anything that doesn't implement the method. (Note: `try!()` works just like `&.`)

```ruby
bar = 'a'

bar&.foo
# returns NoMethodError (undefined method `foo' for "a":String)

bar.try(:foo)
#returns nil

bar.try!(:foo)
# returns NoMethodError (undefined method `foo' for "a":String)
```

This also means that `&.` will return `NoMethodError` if called on `false`, whereas doing a presence check would return `false` before attempting to call the method. I'm not sure when this scenario would come up, but it is good to be aware of. 

```ruby
bar = false 

bar&.foo
# returns NoMethodError (undefined method `foo' for false:FalseClass)

bar.try(:foo)
#returns nil

bar.present? && bar.foo
# returns false
```

#Is this a code smell?

The safe navigator isn't inherently bad. In fact if you have something like this in your code `foo && foo.bar` Rubocop will admonish you for not using safe navigation.

However, using `&.` too often in your code is probably something to avoid. Relying on safe navigation encourages the bad habit of passing around `nil`s, it can make bugs harder to find, and personally I find it can make code harder to read. 

For example, I had this piece of code:

```ruby
if posts.empty? || mean(posts.pluck(:unicorns_count))&.zero?
```

At first it seems pretty innocuous, I was getting a `NoMethodError (undefined method 'zero?' for nil:NilClass)`, the fastest thing to do was just toss the safe navigator on there and move on. However, if I left it that way, I'd just run into the error again if I tried calling another method on the results of `mean()` somewhere else in my code. Does that mean going around tagging on safe navigators everywhere I called `mean()`? No way! The answer was to look at why a method called `mean()` was returning `nil` to begin with and refactor the method.  

The safe navigation operator should only be used when `nil` is an acceptable result, but more often than not, you don't want to be returning or passing `nil` anyway. If you're leaning on `&.` too often or you find yourself with code that looks something like:

```ruby 
potatoes.foo&.bar&.baz&.qux
```

It's time to take a closer look at refactoring your code. The 'Null Object Pattern` could be one potential solution (and potentially a future post for me to write🤔).

-----

I wrote the future post!
{% link https://dev.to/hkly/something-out-of-nothing-null-object-pattern-3clm %}

-----
_Cover photo by https://unsplash.com/@lucabravo_