What is the difference between `try` and `&.` (safe navigation operator) in Ruby
&.
works like #try!
, not #try
.
And here is description of #try!
(from documentation):
Same as #try, but will raise a NoMethodError exception if the receiving is not nil and does not implemented the tried method.
So basically it saves you from calling a method on nil
, but if an object is presented it will try to call its method as usual.
The quote is from Rails Documentation, and so it's important to emphasize
that Ruby does not provide #try
; it's provided by Rails, or more accurately ActiveSupport. The safe navigation operator (&.
) however, is a language feature presented in Ruby 2.3.0.
Why is safe navigation better than using try in Rails?
(1) &.
is generally shorter than try(...)
Depending on the scenario, this can make your code more readable.
(2) &.
is standard Ruby, as opposed to try
The method try
is not defined in a Ruby core library but rather in a Rails library. When you are not developing a RoR web app but instead are writing e.g. little helper scripts, this will get relevant pretty fast.
(I prefer Ruby over Bash, for example.)
(3) &.
makes debugging easier
The safe traversal operator will throw an error if a nonexistent method is being invoked.
>> "s"&.glubsch
NoMethodError: undefined method `glubsch' for "s":String
Only on nil
it will behave leniently:
>> nil&.glubsch
=> nil
The try
method will always return nil
.
>> "s".try(:glubsch)
=> nil
Note that this is the case with most recent versions of Ruby and Rails.
Now imagine a scenario where a method named glubsch
exists. Then you decide to rename that method but forget to rename it in one place. (Sadly, that can happen with ruby...) With the safe traversal operator, you will notice the mistake almost immediately (as soon as that line of code is executed for the first time). The try
method however will happily provide you with a nil
and you will get a nil
related error somewhere downstream in program execution. Figuring out where such a nil
came from can be hard at times.
Failing fast and hard with &.
makes debugging easier than blithely returning nil
with try
.
EDIT: There is also the variant try!
(with a bang) that behaves the same as &.
in this regard. Use that if you don't like &.
.
(4) What if I don't care if a method is implemented or not?
That would be strange. Since that would be an unexpected way of programming, please make it explicit. For example by fleshing out the two cases (implemented or not) using respond_to?
and branch off of that.
(5) What about try
's block form?
Instead of a method name, a block can be passed to try
. The block will be executed in the context of the receiver; and within the block there is no leniency applied. So with just a single method call, it will acutally behave the same as &.
.
>> "s".try{ glubsch }
NameError: undefined local variable or method `glubsch' for "s":String
For more complex computations, you might prefer this block form over introducing lots of local variables. E.g. a chain of
foo.try{ glubsch.blam }.try{ bar }.to_s
would allow foo
to be nil
but require foo.glubsch
to return a non-nil
value. Then again, you can do the same with the safe traversal operator in a more concise fashion:
foo&.glubsch.blam&.bar.to_s
Using try
's block form for complex computations IMHO is a code smell, though, because it impedes readability. When you need to implement complex logic, introduce local variables with descriptive names and maybe use an if
to branch off a nil
case. Your code will be more maintainable.
HTH!
What are the differences between the Ruby 2.3 safe navigation operator '&. and the 'try!' method from ActiveSupport?
A key difference is that try!
is a extra method call, whereas &.
is not. I can think of one (admittedly contrived) difference this creates
"1234"&.gsub(/\d/, "a")
$& #=> "1234"
No surprises here - I did a regex match so the regex global variables are set ($&
is the matched string).
But if (in a fresh irb session - this is important) I do
"1234".try!(:gsub, /\d+/, "a")
$& #=> nil
Then the regex related globals are nil. This is because these globals aren't really global - they are tied to where the code is called from (the docs call this thread and method-local global variables)
In this case $&
is still getting set, but it is being set inside the try!
method so you never see it.
The extra method call also makes try
slower (pretty much an order of magnitude in a quick benchmark) although in any real usage the cost of the actual method you are calling should dwarf the cost of try!
What does &. (ampersand dot) mean in Ruby?
It is called the Safe Navigation Operator. Introduced in Ruby 2.3.0, it lets you call methods on objects without worrying that the object may be nil
(Avoiding an undefined method for nil:NilClass
error), similar to the try
method in Rails.
So you can write
@person&.spouse&.name
instead of
@person.spouse.name if @person && @person.spouse
From the Docs:
my_object.my_method
This sends the
my_method
message tomy_object
. Any
object can be a receiver but depending on the method's visibility
sending a message may raise aNoMethodError
.You may use
&.
to designate a receiver, thenmy_method
is not invoked
and the result isnil
when the receiver isnil
. In that case, the
arguments ofmy_method
are not evaluated.
How does the &.method syntax (safe navigation operator) works in Ruby?
There are 2 seperate operators here:
Safe navigation operator
&.
- It is safe navigation operator which was introduced in Ruby 2.3.0. It basically returnsnil
if the callee isnil
instead of raising excecptionundefined method called for Nil class
. eg:a = 1
a.next
# => 2
a&.next
# => 2
a = nil
a.next
# => NoMethodError (undefined method `next' for nil:NilClass)
a&.next
# => nil ## No exception, returns nilYou can read about it more here and documentation
Unary
&
: This operator is a little more complex. It is almost equivalent to calling#to_proc
but not quite that. But for this discussion let us think like that. So, if you have a Proc, calling with&
in front of it will call#to_proc
on the Proc and convert it into a blockmultiply_by_2 = Proc.new { |x| x * 2 }
# => #<Proc:0x00007fb4771cf560>
# &multiply_by_2 almost equivalent to { |x| x * 2 } but is not correct syntax
[1, 2].map(&multiply_by_2)
# => [2, 4]
# equivalent to [1, 2].map { |x| x * 2 }But what happens if we give a symbol like :abc to
&
operator instead of a proc. It will try to call#to_proc
on the symbol and ruby has definedSymbol#to_proc
which roughly translates to something like this:def to_proc
# this will return some block like { |x| x.send(:abc) }
lambda { |x| x.send(self) }
endSo
&:abc
roughly translates to this block{ |x| x.abc }
using the below transformation&:abc =====> :abc.to_proc =====> { |x| x.send(:abc) } ====> { |x| x.abc }
So, instead of doing
[1, 2, 3].map { |x| x.next }
, you could do[1, 2, 3].map(&:next)
as&:next
is roughly equivalent to the block{ |x| x.next }
.See unary & (which is the main source of what I have written here) for more reading.
Is there a way to chain conditionals in Ruby, Rails or ActiveRecord?
If you are positive there will be either single or none records returned, you might safely use an enumerator instead:
SomeModel.where(some_attr: 'some_val')
.limit(1)
.map(&:some_method)
.first
Safe navigation equivalent to Rails try for hashes
&.
is not equivalent to Rails' try
, but you can use &.
for hashes. Just use it, nothing special.
hash[:key1]&.[](:key2)&.[](:key3)
Although I would not do that.
Which syntax will recomend between try and &. when we're dealing with nil value in Ruby on Rails?
&.
works like#try!
, not#try
. documentation#try
not native in ruby but it is provided by rails.- Safe navigation
#&.
is almost 3~4 times faster than using the#try
require 'active_support/all'
require 'benchmark'
foo = nil
puts Benchmark.measure { 10_000_000.times { foo.try(:boo) } }
puts Benchmark.measure { 10_000_000.times { foo&.boo } }
Output
1.210000 0.000000 1.210000 ( 1.211835)
0.360000 0.000000 0.360000 ( 0.363127)
Related Topics
Create Custom HTML Helpers in Ruby on Rails
How to Get a Particular Line from a File
Rails: Unable to Access Log File
Sass::Syntaxerror: File to Import Not Found or Unreadable: Compass in Production
Inserting an Array Using Sequel Gem in Postgresql
Array Include Any Value from Another Array
How to Get a Backtrace from a Systemstackerror: Stack Level Too Deep
Difference Between Resource and Resources in Rails Routing
How to Test CSV File Download in Capybara and Rspec
How to Insert a String into a Textfile
Parse Email Addresses for "From" and "To" Fields in Ruby
Sass/Compass Compile into Many Locations
Rails 3.2 'Link_To' (In Email) with 'Method: :Put' Still Producing Get Request
How to Insert Video Youtube API V3 Through Service Account with Ruby