Safe navigation equivalent to Rails try for hashes
&.
is not equivalent to Rails' try
, but you can use &.
for hashes. Just use it, nothing special.
hash[:key1]&.[](:key2)&.[](:key3)
Although I would not do that.
Safe navigation operator for hash with string keys
The fact that this key is String
isn't really relevant here. What you want (well, I guess) is to use safety operator with []
method and you can do it like this:
data&.[]('str_key')&.include?('string1')
You can also make use of Hash#dig
method, I think it will improve readability of this code:
data&.dig('str_key')&.include?('string1')
Hash#dig
also has the advantage of working properly with nested hashes (it was in fact designed to handle this case):
data = { 'str_key' => { 'str_key1' => { 'str_key2' => 'str_value' } } }
data.dig('str_key', 'str_key1', 'str_key2')
# => 'str_value'
Equivalent of .try() for a hash to avoid undefined method errors on nil?
You forgot to put a .
before the try
:
@myvar = session[:comments].try(:[], @comment.id)
since []
is the name of the method when you do [@comment.id]
.
Why is safe navigation better than using try in Rails?
(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!
Safe navigation operator (lonely operator) not working for hash
Because this
my_hash[:test]
is syntactic sugar for this one
my_hash.[](:test)
so this should work
my_hash&.[](:test)
but it's not pretty, I know.
Ruby - Is it possible to alias a method to the safe navigation operator
I guess you could go with something as simple as
class Object
def fast_try(meth,*args,&block)
self&.public_send(meth,*args,&block)
end
end
For Example:
["string","STRING","pOw"].map do |s|
s.fast_try(:upcase!)
.fast_try(:chars)
.fast_try(:find, ->{"No S"}) { |a| a == "S" }
.fast_try(:prepend, "!")
end
#=> ["!S",nil,"!No S"]
While your question states, " No, I don't care about the minor behavior differences between try and safe navigation operator.", given the fact that you have written a gem and noted the following
Differences between FastTry and ActiveSupport#try
It is not our goal to maintain any consistency with the ActiveSupport version of the try method. I do however want to maintain a simple list of the differences. Please create a PR or Issue if you find a difference that should be documented here.
Nothing reported yet
I feel it prudent to mention that there are discernible, and potentially poignant, differences between the 2 here is a Repl Spec to show the differences and for the sake of this answer and the fact that the link might die the output of this spec is as follows:
ActiveSupport#try vs. Safe Navigation (&.)
#try
handles would be NoMethodError with nil (using #respond_to?)
does not work on a BasicObject
behaves like a method call
with no method name given
when block_given?
yields self to a block with arity > 0
evaluates block with arity == 0 in the context of self
when no block_given?
raises an ArgumentError
with a method_name given
a non nil object
uses public_send for message transmission
nil
calls NilClass#try and returns nil
#try!
does not handle NoMethodError
does not work on a BasicObject
behaves like a method call
with no method name given
when block_given?
yields self to a block with arity > 0
evaluates block with arity == 0 in the context of self
when no block_given?
raises an ArgumentError
with a method_name given
a non nil object
uses public_send for message transmission
nil
calls NilClass#try and returns nil
&. (safe navigation)
does not handle NoMethodError
raises a SyntaxError with no method name given when block_given?
raises a SyntaxError with no method name given when no block_given?
works on a BasicObject
does not act like a method call
with a method_name given
a non nil object
&. is not called
nil
returns nil without a method call
How does the &.method syntax (safe navigation operator) works in Ruby?
There are 2 seperate operators here:
Safe navigation operator
&.
- It is safe navigation operator which was introduced in Ruby 2.3.0. It basically returnsnil
if the callee isnil
instead of raising excecptionundefined method called for Nil class
. eg:a = 1
a.next
# => 2
a&.next
# => 2
a = nil
a.next
# => NoMethodError (undefined method `next' for nil:NilClass)
a&.next
# => nil ## No exception, returns nilYou can read about it more here and documentation
Unary
&
: This operator is a little more complex. It is almost equivalent to calling#to_proc
but not quite that. But for this discussion let us think like that. So, if you have a Proc, calling with&
in front of it will call#to_proc
on the Proc and convert it into a blockmultiply_by_2 = Proc.new { |x| x * 2 }
# => #<Proc:0x00007fb4771cf560>
# &multiply_by_2 almost equivalent to { |x| x * 2 } but is not correct syntax
[1, 2].map(&multiply_by_2)
# => [2, 4]
# equivalent to [1, 2].map { |x| x * 2 }But what happens if we give a symbol like :abc to
&
operator instead of a proc. It will try to call#to_proc
on the symbol and ruby has definedSymbol#to_proc
which roughly translates to something like this:def to_proc
# this will return some block like { |x| x.send(:abc) }
lambda { |x| x.send(self) }
endSo
&:abc
roughly translates to this block{ |x| x.abc }
using the below transformation&:abc =====> :abc.to_proc =====> { |x| x.send(:abc) } ====> { |x| x.abc }
So, instead of doing
[1, 2, 3].map { |x| x.next }
, you could do[1, 2, 3].map(&:next)
as&:next
is roughly equivalent to the block{ |x| x.next }
.See unary & (which is the main source of what I have written here) for more reading.
Which syntax will recomend between try and &. when we're dealing with nil value in Ruby on Rails?
&.
works like#try!
, not#try
. documentation#try
not native in ruby but it is provided by rails.- Safe navigation
#&.
is almost 3~4 times faster than using the#try
require 'active_support/all'
require 'benchmark'
foo = nil
puts Benchmark.measure { 10_000_000.times { foo.try(:boo) } }
puts Benchmark.measure { 10_000_000.times { foo&.boo } }
Output
1.210000 0.000000 1.210000 ( 1.211835)
0.360000 0.000000 0.360000 ( 0.363127)
Syntactic sugar for Safe Navigation operator(&.)
How can the Safe Navigation operator(
&.
) be used on syntactic sugar for[]
method?
It can't. Matz doesn't want it to.
This feature was requested and rejected twice already:
- Bug #11618: Safe call syntax with aref or aset is
- Feature #11813: Extend safe navigation operator for
[]
and[]=
with syntax sugar
Matz says:
I am not going to add safe navigation for aref and aset.
Maybe you can usea.?[](x)
(method call of an operator).
Related Topics
Breaking Ruby Module Across Several Files
Bash: /Home/Xxx/.Rvm/Scripts/Rvm: No Such File or Directory
How to Fix Libv8 Error from Gemfile on Mavericks
Rails Custom Validation - Only One Record Can Be True
Ruby, Using Regex to Find Something in Between Two Strings
How to Dynamically Call Accessor Methods in Ruby
Actiondispatch::Http::Uploadedfile.Content_Type Not Being Initialized in Rspec Test
Paperclip: Upload from Url with Extension
How to Color Unit Tests with Lib Minitest or Test:Unit
Ruby String with Usd "Money" Converted to Number
How to Fix Undefined Method 'Split' for Nil:Nilclass Error
Openssl Error Installing Ruby 2.0.0-P195 on MAC with Rbenv
Are There Ruby Equivalents to Car, Cdr, and Cons