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.