Sử dụng (&. ) trong Ruby

1.Phương thức Try ()

Chắc hẳn, trong Rails, các bạn sẽ không lại gì phương thức try() này nhỉ. Cơ bản thì try() giúp chúng ta gọi các method của 1 object mà không cần lo lắng về việc object đó có phải là nil hay không và việc gây ra các exception không muốn. Cách sử dụng thì nhìn vào ví dụ dưới đây chúng ta sẽ thấy

user.try(:name)
tương tự
user.nil? ? nil : user.name

2. Safe Navigation Operator (&.)

Safe Navigation Operator được đưa vào từ Ruby 2.3.0 Chúng ta sẽ bắt đầu với ví dụ dưới đây nhé, đây là cách viết an toàn thông thường để đảm bảo không có các exception không mong muốn bắn ra khi object bị nil

if account && account.owner && account.owner.address
...
end

Thật là dài dòng và phiền nhiễu, nhưng với method try() chúng ta có thể rút ngắn như sau

if account.try(:owner).try(:address)
...
end

Có thể thấy cả 2 cách viết trên đều trả về kết quả như nhau

3. Sử dụng (&.)

Chúng ta có thể viết ví dụ trên bằng cách sử dụng (&.) như sau

account&.owner&.address

Có thể thấy cú pháp hơi lạ 1 chút nhưng đoạn code của chúng ta trông sẽ ngắn gọn đi tương đối nhỉ

4. Ví dụ khác

So sánh 3 cách viết ở trên với 1 bài toán khác

account = 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

Nếu tất cả là nil thì kết quả sẽ giống nhau, không có gì đặc biệt cả, vậy nếu owner là false thì sẽ như thế nào?

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`

Có thể thấy cú pháp &. chỉ hoạt động khi object nil còn trường hợp false thì bắn ra exception Vậy chuyện gì nếu owner present nhưng không trả về address

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>`

Có thể thấy chỉ có try() trả về nil, còn 2 cách viết kia đều bắn ra exception. Vậy việc sử dung (.&) hơn gì so với try

4. Tại sao nên sử dụng (&.)

(1) &. giúp code ngắn gọn hơn try(...) Nếu cùng 1 kịch bản thì (&.) sẽ giúp code ngắn gọn hơn tương đối kể (2) &. là cú pháp của Ruby Phương thức try() không được định nghĩa trong thư viện Ruby core mà là của thư viện Rails (3) &. giúp debug dễ dàng hơn (&.) sẽ đưa ra exception nếu 1 phương thức thực sự không tồn tại được gọi, trong khi try() luôn luôn trả về nil

>> "s"&.glubsch
NoMethodError: undefined method `glubsch' for "s":String

Sẽ chỉ trả về nil khi object gọi phương đó nil
>> nil&.glubsch
=> nil

Còn try() thì sao
>> "s".try(:glubsch)
=> nil

Vậy tại sao lại debug nhanh hơn nhỉ??? Thử tưởng tượng nhé, giả sử phương thức glubsch là tồn tại. Sau đó, bạn quyết định đổi tên phương thức đó nhưng lại bị sót ở 1 số nơi. Thì với (&.) sẽ bắn ra exception ngay lập tức khi gọi đến dòng lệnh chứa phương thức sai đó. Trong khi đó phương thức try() thì ngược lại, luôn luôn trả về nil. Điều này sẽ khiến chúng ta mất thời gian để debug tìm ra lỗi đó

Tài liệu tham khảo

http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/