rails < 4.0 "try" method throwing NoMethodError?
Asked Answered
R

3

25

Why is try throwing an error? Doesnt that defeat the whole purpose? Maybe its just in the console?

ruby-1.9.2-p180 :101 > User.first.try(:something)
NoMethodError: undefined method `something' for #<User:0x000001046ad128>
    from /Users/me/.rvm/gems/ruby-1.9.2-p180/gems/activemodel-3.0.10/lib/active_model/attribute_methods.rb:392:in `method_missing'
    from /Users/me/.rvm/gems/ruby-1.9.2-p180/gems/activerecord-3.0.10/lib/active_record/attribute_methods.rb:46:in `method_missing'
    from (irb):101
    from /Users/me/.rvm/gems/ruby-1.9.2-p180/gems/railties-3.0.10/lib/rails/commands/console.rb:44:in `start'
    from /Users/me/.rvm/gems/ruby-1.9.2-p180/gems/railties-3.0.10/lib/rails/commands/console.rb:8:in `start'
    from /Users/me/.rvm/gems/ruby-1.9.2-p180/gems/railties-3.0.10/lib/rails/commands.rb:23:in `<top (required)>'
    from script/rails:6:in `require'
    from script/rails:6:in `<main>'

EDIT:

Thanks guys, now I get it.

Is there a way to do what I wanted without doing using respond_to?, such that User.try(:something) returns nil instead of throwing the error?

Rockwood answered 15/9, 2011 at 6:38 Comment(0)
R
58

Rails 3

You misunderstand how try works, from the fine manual:

try(*a, &b)
Invokes the method identified by the symbol method, passing it any arguments and/or the block specified, just like the regular Ruby Object#send does.

Unlike that method however, a NoMethodError exception will not be raised and nil will be returned instead, if the receiving object is a nil object or NilClass.

And the version of try that is patched into NilClass:

try(*args)
Calling try on nil always returns nil.

So try doesn't ignore your attempt to call a non-existent method on an object, it ignores your attempt to call a method on nil and returns nil instead of raising an exception. The try method is just an easy way to avoid having to check for nil at every step in a chain of method calls.


Rails 4

The behavior of try has changed in Rails 4 so now it:

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.

So now try takes care of both checks at once. If you want the Rails 3 behavior, there is try!:

Same as try, but will raise a NoMethodError exception if the receiving [sic] is not nil and does not implemented [sic] the tried method.

Repeal answered 15/9, 2011 at 6:45 Comment(2)
This behaviour changed in Rails 4.0, try will now not raise if the method is missing, however, try! will raise if the method is missing. See github.com/rails/rails/commit/…Superstructure
@TimonVonk: Thanks for the heads up, I've updated the answer to differentiate between Rails3 and Rails4.Repeal
T
8

This is what try does

Invokes the method identified by the symbol method, passing it any arguments and/or the block specified, just like the regular Ruby Object#send does. Unlike that method however, a NoMethodError exception will not be raised and nil will be returned instead, if the receiving object is a nil object or NilClass.

So, let's say you setup @user in your controller but you didn't instantiate it then @user.try(:foo) => nil instead of

@user.foo
NoMethodError: undefined method `foo' for nil:NilClass

The important point here is that try is an instance method. It also doesn't return nil if the object you try on isn't nil.

Tjirebon answered 15/9, 2011 at 6:47 Comment(3)
Oh, makes sense. Is there a function to do what I wanted?Rockwood
User.column_names.include?("name") or @u.attributes.has_key?("name")Tjirebon
so i have to do a check for it first, there is no way to do this is one line? that would be a nice feature ;)Rockwood
I
5

I know this is old, but it may help somebody else, because this is the first thing that popped up when I Googled this issue. I "borrowed" the code for try and implemented my own try_method method which acts just like try, except that it first checks to see if the method exists before calling send. I implemented this in Object and put it in an initializer, and I can now call it on any object.

class Object
  # Invokes the method identified by _method_, passing it any
  # arguments specified, just like the regular Ruby <tt>Object#send</tt> does.
  #
  # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised
  # if the method does not exist.
  #
  # This differs from the regular Ruby <tt>Object#try</tt> method which only
  # suppresses the +NoMethodError+ exception if the object is Nil
  #
  # If try_method is called without a method to call, it will yield any given block with the object.
  #
  # Please also note that +try_method+ is defined on +Object+, therefore it won't work with
  # subclasses of +BasicObject+. For example, using try_method with +SimpleDelegator+ will
  # delegate +try_method+ to target instead of calling it on delegator itself.
  #
  # ==== Examples
  #
  # Without +try_method+
  #   @person && @person.respond_to?(:name) && @person.name
  # or
  #   (@person && @person.respond_to?(:name)) ? @person.name : nil
  #
  # With +try_method+
  #   @person.try_method(:name)
  #
  # +try_method+ also accepts arguments and/or a block, for the method it is trying
  #   Person.try_method(:find, 1)
  #   @people.try_method(:collect) {|p| p.name}
  #
  # Without a method argument try_method will yield to the block unless the receiver is nil.
  #   @person.try_method { |p| "#{p.first_name} #{p.last_name}" }
  #--
  # +try_method+ behaves like +Object#send+, unless called on +NilClass+ or a class that does not implement _method_.
  def try_method(method=nil, *args, &block)
    if method == nil && block_given?
      yield self
    elsif respond_to?(method)
      __send__(method, *args, &block)
    else
      nil
    end
  end
end

class NilClass
  # Calling +try_method+ on +nil+ always returns +nil+.
  # It becomes specially helpful when navigating through associations that may return +nil+.
  #
  # === Examples
  #
  #   nil.try_method(:name) # => nil
  #
  # Without +try_method+
  #   @person && @person.respond_to(:children) && [email protected]? && @person.children.respond_to(:first) && @person.children.first.respond_to(:name) && @person.children.first.name
  #
  # With +try_method+
  #   @person.try_method(:children).try_method(:first).try_method(:name)
  def try_method(*args)
    nil
  end
end
Inviolable answered 25/8, 2012 at 6:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.