diff options
Diffstat (limited to 'activesupport/lib/active_support/core_ext/object/try.rb')
-rw-r--r-- | activesupport/lib/active_support/core_ext/object/try.rb | 94 |
1 files changed, 62 insertions, 32 deletions
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 1079ddde98..26b8d58948 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -1,48 +1,78 @@ class Object - # Invokes the public method identified by the symbol +method+, passing it any arguments - # and/or the block specified, just like the regular Ruby <tt>Object#public_send</tt> does. + # 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. # - # *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. + # This method is defined to be able to write # - # This is also true if the receiving object does not implemented the tried method. It will - # return +nil+ in that case as well. + # @person.try(:name) # - # If try is called without a method to call, it will yield any given block with the object. + # instead of # - # Please also note that +try+ is defined on +Object+, therefore it won't work with - # subclasses of +BasicObject+. For example, using try with +SimpleDelegator+ will - # delegate +try+ to target instead of calling it on delegator itself. + # @person.name if @person # - # Without +try+ - # @person && @person.name - # or - # @person ? @person.name : nil + # +try+ calls can be chained: # - # With +try+ - # @person.try(:name) + # @person.try(:spouse).try(:name) + # + # instead of + # + # @person.spouse.name if @person && @person.spouse + # + # +try+ will also return +nil+ if the receiver does not respond to the method: + # + # @person.try(:non_existing_method) # => nil + # + # instead of + # + # @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil # - # +try+ also accepts arguments and/or a block, for the method it is trying - # Person.try(:find, 1) - # @people.try(:collect) {|p| p.name} + # +try+ returns +nil+ when called on +nil+ regardless of whether it responds + # to the method: # - # Without a method argument try will yield to the block unless the receiver is nil. - # @person.try { |p| "#{p.first_name} #{p.last_name}" } + # nil.try(:to_i) # => nil, rather than 0 # - # +try+ behaves like +Object#public_send+, unless called on +NilClass+. + # Arguments and blocks are forwarded to the method if invoked: + # + # @posts.try(:each_slice, 2) do |a, b| + # ... + # end + # + # The number of arguments in the signature must match. If the object responds + # to the method the call is attempted and +ArgumentError+ is still raised + # in case of argument mismatch. + # + # If +try+ is called without arguments it yields the receiver to a given + # block unless it is +nil+: + # + # @person.try do |p| + # ... + # end + # + # You can also call try with a block without accepting an argument, and the block + # will be instance_eval'ed instead: + # + # @person.try { upcase.truncate(50) } + # + # Please also note that +try+ is defined on +Object+. Therefore, it won't work + # with instances of classes that do not have +Object+ among their ancestors, + # like direct subclasses of +BasicObject+. For example, using +try+ with + # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on + # the delegator itself. def try(*a, &b) - if a.empty? && block_given? - yield self - else - public_send(*a, &b) if respond_to?(a.first) - end + try!(*a, &b) if a.empty? || respond_to?(a.first) end - # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and - # does not implemented the tried method. + # Same as #try, but will raise a NoMethodError exception if the receiver is not +nil+ and + # does not implement the tried method. + def try!(*a, &b) if a.empty? && block_given? - yield self + if b.arity.zero? + instance_eval(&b) + else + yield self + end else public_send(*a, &b) end @@ -51,12 +81,12 @@ end class NilClass # Calling +try+ on +nil+ always returns +nil+. - # It becomes specially helpful when navigating through associations that may return +nil+. + # It becomes especially helpful when navigating through associations that may return +nil+. # # nil.try(:name) # => nil # # Without +try+ - # @person && !@person.children.blank? && @person.children.first.name + # @person && @person.children.any? && @person.children.first.name # # With +try+ # @person.try(:children).try(:first).try(:name) |