Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is safe navigation better than using try in Rails?

I'm reading this. What's the benefit of using this:

user&.address&.state 

over

user.try(:address).try(:state) 

I still don't get it.

like image 644
Jwan622 Avatar asked Jun 22 '16 20:06

Jwan622


People also ask

What is safe navigation operator in rails?

Safe navigation operator¶ ↑ &. , called “safe navigation operator”, allows to skip method call when receiver is nil . It returns nil and doesn't evaluate method's arguments if the call is skipped.

What does safe navigation operator return?

Use the safe navigation operator ( ?. ) to replace explicit, sequential checks for null references. This operator short-circuits expressions that attempt to operate on a null value and returns null instead of throwing a NullPointerException.


2 Answers

(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!

like image 162
Raffael Avatar answered Sep 21 '22 13:09

Raffael


(6) Speed

Safe navigation is almost 4 times faster than using the try method from activesupport

require 'active_support/all' require 'benchmark'  foo = nil  puts Benchmark.measure { 10_000_000.times { foo.try(:lala) } } puts Benchmark.measure { 10_000_000.times { foo&.lala } } 

Output

  1.310000   0.000000   1.310000 (  1.311835)   0.360000   0.000000   0.360000 (  0.353127) 
like image 38
rorra Avatar answered Sep 19 '22 13:09

rorra