The Safe Navigation Operator (&.) in Ruby
Bài đăng này đã không được cập nhật trong 3 năm
Một trong những bổ sung thú vị nhất của Ruby 2.3.0 là Safe Navigation Operator(&.).
Ban đầu
Hãy tưởng tượng bạn có một account
một owner
và bạn muốn lấy được các owner
của addrress
. Nếu như bạn muốn được an toàn và không có nguy cơ lỗi nil
bạn sẽ viết một cái gì đó dạng như:
if account && account.owner && account.owner.addrress
...
end
Điều này thực sự thực rất rườm rà và tốn thời gian. ActiveSupport bao gồm các phương thức try
với cách thực hiện tương tự (nhưng có vài sự khác biệt quan trọng chúng ta sẽ bàn luận sau):
if account.try(:owner).try(:addrress)
...
end
Nó thực hiện cùng một thao tác, hoặc trả về addrress
hoặc nil
nếu có một trong số những giá trị trong chuỗi là nil
. Ví dụ đầu tiên cũng có thể trả về false
nếu như owner
được thiết lập sai.
Sử dụng &.
Chúng ta có thể viết lại ví dụ trước sử dụng safe navigation operator như sau:
account&.owner&.addrress
Cú pháp có hơi lạ và khó nhìn nhưng thật sự nó làm đoạn code đợn giản và ngắn gọn hơn rất nhiều.
Thêm một số ví dụ
Chúng ta hãy so sánh tất cả 3 phương pháp tiếp cận một cách chi tiết hơn.
accoun = Account.new(owner: nil) # account without an owner
account.owner.address
# => NoMethodError: undefined method 'address' for nil:NilClass
account && account.owner && account.owner.address
# => nil
account.try(:owner).try(:address)
# => nil
account&.owner&.address
# => nil
Không có gì đáng ngạc nhiên cả. Điều gì sẽ xảy ra nếu owner
là false
account = Account.new(owner: false)
account.owner.address
# => NoMethodError: undefined method `address' for false:FalseClass `
account && account.owner && account.owner.address
# => false account.try(:owner).try(:address) # => nil
account&.owner&.address # => undefined method 'address' for false:FalseClass`
Ở đây có những bất ngờ, đầu tiên là cú pháp &.
chỉ bở qua nil
nhưng thừa nhận false
, nó không tương đương với cú pháp s1 && s1.s2 && s1.s1.s3
.
Điều gì sẽ xảy ra nếu owner
hiện hữu nhưng addrress
không tồn tại?
account = Account.new(owner: Object.new)
account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>
account && account.owner && account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
account.try(:owner).try(:address)
# => nil
account&.owner&.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
Rất tiếc các phương thức try
không kiểm tra nếu người nhận đáp ứng những ký hiệu nhất định. Đó là lý do vì sao phiên bản chặt chẽ hơn của try
là try!
luôn tốt hơn để sử dụng:
account.try!(:owner).try!(:address)
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
Bẫy
nil.nil?
# => true
nil?.nil?
# => false
nil&.nil?
# => nil
Ta nghĩ rằng nil&.nil?
sẽ trả về true
nhưng thực ra nó sẽ trả về nil
Array#dig và Hash#dig
Các phương pháp #dig
là tính năng hữu ích nhất trong phiên bản này. Không còn làm chúng ta phải viết tồi tệ như sau:
address = params[:account].try(:[], :owner).try(:[], :address)
# or
address = params[:account].fetch(:owner).fetch(:address)
Bây giờ bạn có thể chỉ cần sử dụng Hash#dig và thực hiện được điều tương tự:
address = params.dig(:account, :owner, :address)
Cuối cùng
Việc đối phó với nil
giá trị trong các ngôn ngữ động thực sự khó chịu và sự bổ sung của safe operator và phương thức dig thực sự ngắn gọn, rất tiện lợi khii sử dụng.
All rights reserved