Since version 2.3.0 onwards, the Ruby language has added a very interesting navigation (&.) Operator. This operator has been in C # and Groovy for a long time with slightly different syntax -? .. So what does it do?
Situation
For example, in the database, we have an Account
table and a Owner
table that have 1 to 1 relationships. Suppose we have an account
object and want to retrieve the address
information of that account through the owner
so that we do not encounter errors, we often write the following:
1 2 3 4 | <span class="token keyword">if</span> account <span class="token operator">&&</span> account <span class="token punctuation">.</span> owner <span class="token operator">&&</span> account <span class="token punctuation">.</span> owner <span class="token punctuation">.</span> address <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token keyword">end</span> |
This way is really wordy and confusing to the reader. That is also one of the reasons that ActiveSupport
is included with the try
method, we can rewrite the code to be more concise as follows:
1 2 3 4 | <span class="token keyword">if</span> account <span class="token punctuation">.</span> <span class="token function">try</span> <span class="token punctuation">(</span> <span class="token symbol">:owner</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">try</span> <span class="token punctuation">(</span> <span class="token symbol">:address</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token keyword">end</span> |
These two ways work similarly. This facility shall return address
if N address
of owner
(returns nil
if address
is nil
), or returns false
if owner
is false
Use safe navigation operators (&.)
We can rewrite the above example with the safe navigation operator as follows:
1 2 3 4 | <span class="token keyword">if</span> account <span class="token operator">&</span> <span class="token punctuation">.</span> owner <span class="token operator">&</span> <span class="token punctuation">.</span> address <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token keyword">end</span> |
Without the above two examples, the syntax seems a bit confusing, but the code looks a lot cleaner.
For example
In this example, I will compare the three above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | account <span class="token operator">=</span> <span class="token constant">Account</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> owner <span class="token punctuation">:</span> <span class="token keyword">nil</span> <span class="token punctuation">)</span> <span class="token comment"># account without an owner</span> account <span class="token punctuation">.</span> owner <span class="token punctuation">.</span> address <span class="token comment"># => NoMethodError: undefined method `address' for nil:NilClass</span> account <span class="token operator">&&</span> account <span class="token punctuation">.</span> owner <span class="token operator">&&</span> account <span class="token punctuation">.</span> owner <span class="token punctuation">.</span> address <span class="token comment"># => nil</span> account <span class="token punctuation">.</span> <span class="token function">try</span> <span class="token punctuation">(</span> <span class="token symbol">:owner</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">try</span> <span class="token punctuation">(</span> <span class="token symbol">:address</span> <span class="token punctuation">)</span> <span class="token comment"># => nil</span> account <span class="token operator">&</span> <span class="token punctuation">.</span> owner <span class="token operator">&</span> <span class="token punctuation">.</span> address <span class="token comment"># => nil</span> |
With the above explanation it seems the output we can foresee. But what if owner
is false
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | account <span class="token operator">=</span> <span class="token constant">Account</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> owner <span class="token punctuation">:</span> <span class="token keyword">false</span> <span class="token punctuation">)</span> account <span class="token punctuation">.</span> owner <span class="token punctuation">.</span> address <span class="token comment"># => NoMethodError: undefined method `address' for false:FalseClass `</span> account <span class="token operator">&&</span> account <span class="token punctuation">.</span> owner <span class="token operator">&&</span> account <span class="token punctuation">.</span> owner <span class="token punctuation">.</span> address <span class="token comment"># => false</span> account <span class="token punctuation">.</span> <span class="token function">try</span> <span class="token punctuation">(</span> <span class="token symbol">:owner</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">try</span> <span class="token punctuation">(</span> <span class="token symbol">:address</span> <span class="token punctuation">)</span> <span class="token comment"># => nil</span> account <span class="token operator">&</span> <span class="token punctuation">.</span> owner <span class="token operator">&</span> <span class="token punctuation">.</span> address <span class="token comment"># => undefined method `address' for false:FalseClass`</span> |
The first surprise is the syntax &.
just ignore nil
but realize false
, it is not exactly equivalent to the syntax s1 && s1.s2 && s1.s2.s3
What happens if exists owner
but no address
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | account <span class="token operator">=</span> <span class="token constant">Account</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> owner <span class="token punctuation">:</span> <span class="token builtin">Object</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">)</span> account <span class="token punctuation">.</span> owner <span class="token punctuation">.</span> address <span class="token comment"># => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8></span> account <span class="token operator">&&</span> account <span class="token punctuation">.</span> owner <span class="token operator">&&</span> account <span class="token punctuation">.</span> owner <span class="token punctuation">.</span> address <span class="token comment"># => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`</span> account <span class="token punctuation">.</span> <span class="token function">try</span> <span class="token punctuation">(</span> <span class="token symbol">:owner</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">try</span> <span class="token punctuation">(</span> <span class="token symbol">:address</span> <span class="token punctuation">)</span> <span class="token comment"># => nil</span> account <span class="token operator">&</span> <span class="token punctuation">.</span> owner <span class="token operator">&</span> <span class="token punctuation">.</span> address <span class="token comment"># => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`</span> |
Unfortunately, the try
method does not check if the object responds to the given method. To be more rigorous we use the try!
1 2 3 | account <span class="token punctuation">.</span> try <span class="token operator">!</span> <span class="token punctuation">(</span> <span class="token symbol">:owner</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> try <span class="token operator">!</span> <span class="token punctuation">(</span> <span class="token symbol">:address</span> <span class="token punctuation">)</span> <span class="token comment"># => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`</span> |
Risk
Be careful when using the & .
operator & .
and check the nil
values. Consider the following example:
1 2 3 4 5 6 7 8 9 | <span class="token keyword">nil</span> <span class="token punctuation">.</span> <span class="token keyword">nil</span> <span class="token operator">?</span> <span class="token comment"># => true</span> <span class="token keyword">nil</span> <span class="token operator">?</span> <span class="token punctuation">.</span> <span class="token keyword">nil</span> <span class="token operator">?</span> <span class="token comment"># => false</span> <span class="token keyword">nil</span> <span class="token operator">&</span> <span class="token punctuation">.</span> <span class="token keyword">nil</span> <span class="token operator">?</span> <span class="token comment"># => nil</span> |
In the last example, it’s quite confusing because nil&.nil?
should return true
. However that is a note
Array # dig and Hash # dig
An additional method is #dig
, instead of writing a piece of code like this:
1 2 3 4 5 6 | address <span class="token operator">=</span> params <span class="token punctuation">[</span> <span class="token symbol">:account</span> <span class="token punctuation">]</span> <span class="token punctuation">.</span> <span class="token function">try</span> <span class="token punctuation">(</span> <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">,</span> <span class="token symbol">:owner</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">try</span> <span class="token punctuation">(</span> <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">,</span> <span class="token symbol">:address</span> <span class="token punctuation">)</span> <span class="token comment"># or</span> address <span class="token operator">=</span> params <span class="token punctuation">[</span> <span class="token symbol">:account</span> <span class="token punctuation">]</span> <span class="token punctuation">.</span> <span class="token function">fetch</span> <span class="token punctuation">(</span> <span class="token symbol">:owner</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">.</span> <span class="token function">fetch</span> <span class="token punctuation">(</span> <span class="token symbol">:address</span> <span class="token punctuation">)</span> |
We can handle it more simply with Hash#dig
1 2 | address <span class="token operator">=</span> params <span class="token punctuation">.</span> <span class="token function">dig</span> <span class="token punctuation">(</span> <span class="token symbol">:account</span> <span class="token punctuation">,</span> <span class="token symbol">:owner</span> <span class="token punctuation">,</span> <span class="token symbol">:address</span> <span class="token punctuation">)</span> |
Epilogue
For a dynamic language, the handling of nil
values is quite tricky, so the addition of safe navigation operators and dig
methods are necessary.