Kể từ phiên bản 2.3.0 trở đi, ngôn ngữ Ruby dược bổ sung thêm một toán tử điều hướng (&.) vô cùng thú vị. Toán tử này đã có mặt trong C # và Groovy trong một thời gian dài với cú pháp hơi khác nhau -? .. Vậy nó làm gì?
Tình huống
Ví dụ trong cơ sowe dữ liệu chúng ta có một bảng Account
và một bảng Owner
có quan hệ 1- 1 với nhau. Giả sử ta có một đối tượng account
và muốn lấy ra thông tin address
của account đó thống qua owner
để không gặp lỗi thì ta thường viết như sau:
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> |
Cách này thực sự rất dài dòng và gây khó hiểu cho người đọc. Đó cũng chính là một trong số nguyên nhân mà ActiveSupport
được bao gồm phương thức try
, ta có thể viết lại đoạn code cho ngắn gọn hơn như sau:
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> |
Hai cách này hoạt động tương tự nhau. Điều kiện trên sẽ trả về address
nếu tồn tại address
trong owner
(trả về nil
nếu address
là nil
), hoặc trả về false
nếu owner
là false
Sử dụng toán tử điều hướng an toàn (&.)
Chúng ta có thể viết lại ví dụ ở trên bằng toán tử điều hướng an toàn như sau:
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> |
Nếu chưa có hai ví dụ ở trên thì có vẻ cú pháp có vẻ hơi khó hiểu tuy nhiên trông đoạn code gọn hơn rất nhiều.
Ví dụ
Trong ví dụ này tôi sẽ so sánh cả ba cách ở trên với nhau:
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> |
Với cách giải thích phía trên có vẻ như đầu ra chúng ta có thể lường trước được. Nhưng nếu owner
là false
thì sao?
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> |
Điều bất ngờ đầu tiên là cú pháp &.
chỉ bỏ qua nil
nhưng lại nhận ra false
,
nó không chính xác tương đương với cú pháp s1 && s1.s2 && s1.s2.s3
Điều gì sảy ra nếu tồn tại owner
nhưng lại không có 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> |
Rất tiếc là phương thưc try
không kiểm tra nếu đối tượng phản hồi với phương thức đã cho.
Để nghiêm ngặt hơn chúng ta sử dụng phương thức 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> |
Rủi ro
Hãy cẩn thận khi sử dụng toán tử & .
và kiểm tra các giá trịnil
. Hãy xem xét ví dụ sau:
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> |
Ở ví dụ cuối cùng khá là khó hiểu vì nil&.nil?
nên trả về true
. Tuy nhiên đó là một lưu ý
Array#dig và Hash#dig
Một phương thức được bổ sung thêm nữa là #dig
, thay vì phải viết một đoạn code như thế này:
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> |
Chúng ta có thể xử lý đơn giản hơn với 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> |
Lời kết
Đối với một ngôn ngữ động thì việc xử lý những giá trị nil
là khá rắc rối, vì thế việc bổ sung các toán tử điều hướng an toàn và phương thức dig
là rất cấn thiết.