The Safe Navigation Operator (&.) in Ruby

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 ownerfalse

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 trytry! 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.