Triple Equals in Ruby
Bài đăng này đã không được cập nhật trong 3 năm
Với một số thành phần mà dùng ===
(Triple equal sign) thường bỏ qua hoặc sử dụng trong nền câu lệnh điều kiện. Bây giờ có một số bài viết về toán tử ===
nhưng câu hỏi đặt ra là ===
cho chúng ta có thể sử dụng nó đến đâu?
Triple equal là gì?
Trong một số trường hợp mặc định, toán tử ===
chỉ là một alias cho toán tử ==
. Đặc điểm của nó được thể hiện khi các class khác ghi đè nó cho hành vi chính nó, ví dụ
Ranges
(1..10) === 1
Đối với một range, toán tử ===
là một alias cho includes?
.
Bây giờ một lưu ý về range là nó có thể làm nhiều hành vi bằng chính nó. Đó chính là chủ đề của một bài viết mới hoàn toàn nhưng bây giờ để biết nó như thế nào chúng ta có thể thử vài lệnh sau:
'a'..'z'
'1.0.0'..'2.0.0'
Regex
/abc/ === 'abcdef'
Classes
Chắc chắn lúc bạn muốn so sánh class sẽ dùng is_a?
nhưng đối với toán tử ===
chúng ta có thể dùng nó như sau:
String === 'foo'
Procs
Đối với proc, chúng ta nhận được call
, đây là một cái đặc biệt.
-> a { a > 5 } === 6
=> true
IPAddr
IPAddr.new('10.0.0.0/8') === '10.0.0.1'
IPAddr
ghi đè toán tử ===
để check xem subnet có nằm trong khoảng đó hay không và nó đủ để bất cứ cái gì bạn đưa 1 sẽ cho vào IPAddr
để so sánh.
Bây giờ chúng ta có vài trường hợp để biết nó hoạt động ra sao, trường hợp thường gặp nhất là dùng với câu lệnh case
trong Ruby:
case '10.0.0.1'
when IPAddr.new('10.0.0.0/8') then 'wired connection'
when IPAddr.new('192.168.1.0/8') then 'wireless connection'
else 'unknown connection'
end
Querying
Đối với ActiveRecord chắc một số người từng gặp câu truy vấn như dưới đối với model Person
Person.where(name: 'Bob', age: 10..20)
Nếu chúng ta có thể làm điều gì đó tương tự như các mảng và nhớ lại toán tử ===
thực hiện như thế nào với ranges và regexes thì rất thú vị với nó.
Tiếp đến chúng ta sẽ test với một mảng của hash:
people = [
{name: 'Bob', age: 20},
{name: 'Sue', age: 30},
{name: 'Jack', age: 10},
{name: 'Jill', age: 4},
{name: 'Jane', age: 5}
]
Mong muốn của chúng ta là lấy người có độ tuổi 20 và cao hơn. Trong Ruby chúng ta có thể làm như sau
people.select { |person| person[:age] >= 20 }
Hãy nghĩ rằng nếu có thêm vài điều kiện sẽ trở nên phức tạp? Nó sẽ càng phức tạp hơn nếu đi qua nhiều JSON vậy để giải quyết chúng ta sẽ tận dụng ActiveRecord.
def where(list, conditions = {})
return list if conditions.empty?
list.select { |item|
conditions.all? { |key, matcher| matcher === item[key] }
}
end
Với hàm trên không chỉ dùng thay vì toán tử ==
mà mỗi một cái được so sánh match nữa.
JSON Packet Dump
Trong trường hợp cần query với JSON:
where(packets,
source_ip: IPAddr.new('10.0.0.0/8'),
dest_ip: IPAddr.new('192.168.0.0/16')
ttl: -> v { v > 30 }
)
Chú ý rằng JSON sẽ cho keys là String trừ khi bạn set trước lúc parse.
Chưa đủ mạnh? chúng ta có thể truy cập tới Proc
có nghĩa là chúng ta có thể sử dụng thành phần để có được một số truy vấn phức tạp bằng cách sử dụng một cái gì đó như Ramda trong Ruby.
Bây giờ chúng ta có thể cải tiến như sau:
where(packets,
source_ip: IPAddr.new('10.0.0.0/8'),
dest_ip: IPAddr.new('192.168.0.0/16')
ttl: R.gt(30)
)
Tóm lại Ruby cho ===
với Proc
cho phép chúng ta có thể thực hiện như Ramda.
Objects
Với vài thứ trên đã cho nhiều cách sử dụng nhưng thay vì đó chúng ta có các object vậy chỉ cần thay item[key]
với item.public_send(key)
def where(list, conditions = {})
return list if conditions.empty?
list.select { |item|
conditions.all? { |key, matcher|
matcher === item.public_send(key)
}
}
end
Nếu như muốn thực hiện cụ thể hơn thì Ruby Hash có thể làm được như sau:
def where(list, conditions = {})
return list if conditions.empty?
list.select { |item|
conditions.all? { |key, matcher|
matcher === item.public_send(*key)
}
}
end
Chúng ta còn có thể viết
where([[1,2,3], [20,30,40]],
[:reduce, 0, :+] => R.gt(20)
)
Wrapping up
Có rất nhiều thức khác bạn có thể sử dụng với toán tử ===
nhưng ở đây chỉ là vài mẹo. Nếu như kết hợp với functional composition thì còn có rất nhiều mẹo khác.
Bài viết này dịch từ source
All rights reserved