The === (case equality) operator in Ruby
… and check why 5600+ Rails engineers read also this
The === (case equality) operator in Ruby
Recently I’ve been working on adding more exercises to DevMemo.io about Ruby’s Enumerable module. And I try to balance learning most popular APIs (which you might already know) with some less popular but very useful.
And my attention was captured by Enumerable#grep
.
# grep(pattern) → array
# Returns an array of every element in enum
# for which Pattern === element.
(1..100).grep(38..44)
#=> [38, 39, 40, 41, 42, 43, 44]
names = %w(
William
Kate
Adam
Alexa
James
Natasha
)
names.grep(/am/)
# => %w(William Adam James)
As you can see it works with any class which implements ===
operator. So I was curious as to which classes implement it and in what way. Let’s see.
Class / Module
mod === obj #→ true or false
===
returns true if obj
is an instance of mod
or one of mod
’s descendants. Of limited use for modules, but can be used to classify objects by class.
Basically implemented as
obj.kind_of?(mod)
Example
"text".class.ancestors
# => [String, Comparable, Object, Kernel, BasicObject]
String === "text"
# => true
Object === "text"
# => true
Comparable === "text"
# => true
Numeric === "text"
# => false
Regexp
rxp === str #→ true or false
Basically implemented as:
rxp =~ str >= 0
Example
/^[a-z]*$/ === "HELLO"
#=> false
/^[A-Z]*$/ === "HELLO"
#=> true
Range
rng === obj #→ true or false
Returns true if obj
is an element of the range, false otherwise.
Example
(Date.new(2017, 8, 21)..Date.new(2017, 8, 27)) === Date.new(2017, 8, 27)
# => true
(Date.new(2017, 8, 21)..Date.new(2017, 8, 27)) === Date.new(2017, 8, 29)
# => false
("a".."z") === "a"
# => true
("a".."z") === "abc"
# => false
Proc
proc === obj # → result_of_proc
Invokes the block with obj
as the proc
‘s parameter just like #call
.
Example
is_today = -> (date) { Date.current === date }
is_today === Date.current
# => true
is_today === Date.tomorrow
# => false
is_today === Date.yesterday
# => false
Object
For most of other objects the behavior of ===
is the same as ==
.
Your class
You can define your own class and it’s own ===
which might be as complex (or as simple) as you want. And you can use instances of such class as matchers in case..when
statements or as arguments to Array#grep
.
class State
def initialize(expected_state)
@expected_state = expected_state
end
def ===(obj)
obj.state.to_s == @expected_state.to_s
end
end
class Order < Struct.new(:id, :state, :customer_name)
end
p = Order.new(1, "placed", "Robert")
v = Order.new(2, "verified", "Anne")
s = Order.new(3, "shipped", "Kate")
orders = [p,v,s]
verified = State.new(:verified)
placed = State.new(:placed)
verified === p
# => false
orders.grep(verified)
# => [#<struct Order id=2, state="verified", customer_name="Anne">]
message = case v
when verified
"Your order has been verified and is awaiting shippment"
when placed
"Please wait for verification"
else
"---"
end
# => "Your order has been verified and is awaiting shippment"
Of course this is only for the cases when you don’t feel that checking those conditions is a responsibility of the tested object and you don’t want to implement it as a method in its class.
Your object
As Ruby allows you to define singleton method which affect only a single object’s behavior, you don’t even need a class.
VERIFIED = Object.new
def VERIFIED.===(obj)
obj.state.to_s == "verified"
end
class Order < Struct.new(:id, :state, :customer_name)
end
VERIFIED === Order.new(1, "placed", "Robert")
# => false
VERIFIED === Order.new(2, "verified", "Rita")
# => true
But frankly, I would rather go with Proc
in such case.
VERIFIED = -> (obj){ obj.state.to_s == "verified" }
P.S.
If you don’t want to forget about Enumerable#grep
try DevMemo.io. We’ve been recently working on a Beta version which includes scheduling flashcards repetitions and reminders.
Also, subscribe to our newsletter to receive weekly free Ruby and Rails lessons.