Safe navigation operator '&.' vs '.try' in Rails
We are back to our Blog
app, and this time we want to get the name of the post's author. The task is really straightforward, and we end up with the following piece of code:
post.author.name
We will get our desired result, only if post.author
and post.author.name
are defined - otherwise, we will end up with undefined method
error.
If we want to avoid those errors, we can make use of some ifs:
if post
if post.author
if post.author.name
post.author.name
end
end
end
This solution gets the job done, but looks extremely bad.
Fortunately, Ruby provides a safe navigation operator &.
and Rails (or ActiveSupport to be more specific) provides the try
method, that can help avoid undefined method
exceptions without extensive use of if statements.
In this post, we will compare both of those methods, and look for the differences between them.
Examples
Happy path
First, let's see how they behave when everything is defined.
author = Author.new(name: 'Joe')
post = Post.new(author: author)
post.author.name
> "Joe"
post&.author&.name
> "Joe"
post.try(:author).try(:name)
> "Joe"
There are no big surprises. When there is no nil
along the chain both &.
and try
return the author's name.
Post without an author
post = Post.new
post.author.name
> NoMethodError (undefined method 'name' for nil:NilClass)
post&.author&.name
> nil
post.try(:author).try(:name)
> nil
As expected, both methods return nil
instead of calling the name
method on an empty author.
Nil post
Let's see how both methods behave when they are used to call chained undefined methods for nil
nil.author.name
> NoMethodError (undefined method 'author' for nil:NilClass)
nil&.author&.name
> nil
nil.try(:author).try(:name)
> nil
Just like in the previous example both methods returned nil
instead of calling the author
method nil
.
Calling post's undefined method
Now let's get back to post, and see what happens when we try to call an undefined method
post = Post.new
post.undefined_method
> NoMethodError (undefined method 'undefined_method' for <Post:0x00007fe4ee8f5e50>)
post&.undefined_method
> NoMethodError (undefined method 'undefined_method' for <Post:0x00007fe4ee8f5e50>)
post.try(:undefined_method)
> nil
Here is the first difference between the discussed functions.
Safe navigation operator returns nil
only if the receiver is nil
as well - it does not do any validation of called methods.
On the other hand, try
is returning nil
whenever the receiver does not respond to the call.
Calling nil's defined method
Let's go back to the example with nil as the receiver of the first call, but this time we will extend NilClass
, so the called method is actually defined.
class NilClass
def name
'called nil name'
end
end
nil.name
> "called nil name"
Now, nil
respond to the name
method. Let’s see how both methods are handling that:
nil&.name
> nil
nil.try(:name)
> nil
The safe navigation operator behaves as expected - it does not care about the method implementation - it checks only if the receiver is nil
.
Method try
is behaving in the same matter - if the receiver is nil
it is not checking if it responds to called method, instead, it returns nil
as well.
Wrap up
Safe navigation operator &.
is provided by Ruby. It returns nil
only if the receiver of the method is nil
as well. It does not check if called method is defined.
Rails provides us with the try
method, which checks if the receiver (which is not nil
) responds to the called method, and if not it returns nil
instead of throwing an exception.