问题
The answers to every question I can find (Q1, Q2) regarding Ruby's new safe navigation operator (&.) wrongly declare that obj&.foo is equivalent to obj && obj.foo.
It's easy to demonstrate that this equivalence is incorrect:
obj = false
obj && obj.foo # => false
obj&.foo # => NoMethodError: undefined method `foo' for false:FalseClass
Further, there is the problem of multiple evaluation. Replacing obj with an expression having side effects shows that the side effects are doubled only in the && expression:
def inc() @x += 1 end
@x = 0
inc && inc.itself # => 2
@x = 0
inc&.itself # => 1
What is the most concise pre-2.3 equivalent to obj&.foo that avoids these issues?
回答1:
The safe navigation operator in Ruby 2.3 works almost exactly the same as the try! method added by ActiveSupport, minus its block handling.
A simplified version of that could look like this:
class Object
def try(method, *args, &block)
return nil if self.nil?
public_send(method, *args, &block)
end
end
You can use this like
obj.try(:foo).try(:each){|i| puts i}
This try method implements various details of the safe navigation operator, including:
- It always returns
nilif the receiver isnil, regardless of whethernilactually implements the queried method or not. - It raises a
NoMethodErrorif the non-nilreceiver doesn't support the method. - It doesn't swallow any exceptions on method calls.
Due to differences in language semantics, it can not (fully) implement other features of the real safe navigation operator, including:
Our
trymethod always evaluates additional arguments, in contrast to the safe navigation operator. Consider this examplenil&.foo(bar())Here,
bar()is not evaluated. When using ourtrymethod asnil.try(:foo, bar())we always call the
barmethod first, regardless of whether we later callfoowith it or not.obj&.attr += 1is valid syntax in Ruby 2.3.0 which can not be emulated with just a single method call in previous language versions.
Note that when actually implementing this code in production, you should have a look at Refinements instead of patching core classes.
回答2:
I think the most similar method that the safe traversal operators emulate is Rails' try method. However not exactly, we need to handle the case when the object is not nil but also does not respond to the method.
Which will return nil if the method can not evaluate the given method.
We can rewrite try pretty simply by:
class Object
def try(method)
if !self.respond_to?(method) && !self.nil?
raise NoMethodError, "undefined method #{ method } for #{ self.class }"
else
begin
self.public_send(method)
rescue NoMethodError
nil
end
end
end
end
Then it can be used in much the same way:
Ruby 2.2 and lower:
a = nil
a.try(:foo).try(:bar).try(:baz)
# => nil
a = false
a.try(:foo)
# => NoMethodError: undefined method :foo for FalseClass
Equivalent in Ruby 2.3
a = nil
a&.foo&.bar&.baz
# => nil
a = false
a&.foo
# => NoMethodError
来源:https://stackoverflow.com/questions/34602054/what-is-the-pre-ruby2-3-equivalent-to-the-safe-navigation-operator