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.

You might also like