60

Here is my code:

class Order < Grape::Entity
  expose :id { |order, options| order.id.obfuscate }
  expose :time_left_to_review do |order, options|
    byebug
    order&.time_left_to_review # ERROR
  end
  expose :created_at { |order, options| order.last_transition.created_at }
end

# NoMethodError Exception: undefined method `time_left_to_review' for #<Order:0x007f83b9efc970>

I thought &. is a shortcut for .try but I guess I was wrong. May someone point me to the right direction regarding what I am missing?

I feel like it's not ruby related. Grape maybe? Though I don't get how it could be.

Danil Speransky
  • 29,891
  • 5
  • 68
  • 79
Adrien
  • 2,088
  • 1
  • 18
  • 35
  • They are not the same, you can read more about the difference [here](http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/) – Redithion Aug 22 '17 at 19:26

4 Answers4

96

&. 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, &. saves you from calling a method on nil, but if the object is present it will try to call the method as usual, including raising NoMethodError if the method is not implemented.

#try, on the other hand, saves you from calling a method on nil and calling a method that is not implemented. It will return nil in either case, and never raise NoMethodError.

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.

Community
  • 1
  • 1
Danil Speransky
  • 29,891
  • 5
  • 68
  • 79
20

The try method ignores a lot of things, it just gives it a shot and calls it a day if things don't work out.

The & conditional navigation option will only block calls on nil objects. Anything else is considered to be valid and will proceed with full consequences, exceptions included.

tadman
  • 208,517
  • 23
  • 234
  • 262
18

I am arriving to the party a bit late here, the other answers have covered how it works, but I wanted to add something that the other answers have not covered.

Your question asks What is the difference between try and &. in Ruby. Ruby being the key word here.

The biggest difference is that try doesn't exist in Ruby, it is a method provided by Rails. you can see this or yourself if you do something like this in the rails console:

[1, 2, 3].try(:join, '-')
#=> "1-2-3" 

However if you do the same thing in the irb console, you will get:

[1, 2, 3].try(:join, '-')
NoMethodError: undefined method `try' for [1, 2, 3]:Array

The &. is part of the Ruby standard library, and is therefore available in any Ruby project, not just Rails.

John Hayes-Reed
  • 1,358
  • 7
  • 13
  • 2
    So in case your code might be running in pure Ruby, you should do `foo.try(:try, ...)` to be on the safe side. – mwfearnley Sep 16 '19 at 10:16
  • 1
    Sorry @mwfearnley but I don't understand your comment. If the code is in pure Ruby (i.e outside of Rails or not using `ActiveSupport`) then what's the point of doing `foo.try(:try...)` if `foo.try` will raise a `NoMethodError` ? – gasc Nov 30 '20 at 20:11
  • 2
    @gasc Looking back at my comment now, I have to conclude it was a joke, based on the fact that the advice was "obviously" bad.. Of course, if you are almost certain that `foo.try(:try...)` will fail, then you should do `foo.try(:try, :try...)` to be extra safe.. – mwfearnley Nov 30 '20 at 20:47
  • 1
    ...and I ruined the joke =( My bad @mwfearnley – gasc Dec 03 '20 at 17:33
  • 4
    In pure Ruby, `foo` or `!foo`, there is no `#try` – Ben MacLeod Oct 07 '21 at 04:06
7

In addition to the above answers, I am adding some examples.1

account = Account.new(owner: Object.new)

account.try(:owner).try(:address)
# => nil

account&.owner&.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`

account.try!(:owner).try!(:address)
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`

As we can see, try doesn't check if the receiver responds to the given method or not. Whereas try! and &. behaves the same. If the receiver responds to the address method, all of them will return the same result. I prefer using &. as it looks more cleaner.

For more information on The Safe Navigation Operator (&.), I have found this blog really helpful https://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

Imam
  • 997
  • 7
  • 13
  • https://www.rubydoc.info/docs/rails/4.1.7/Object:try. "#try: Invokes the public method whose name goes as first argument just like public_send does, except that if the receiver does not respond to it the call returns nil rather than raising an exception." – don_Bigote Nov 05 '22 at 14:00