Ruby's === equality operator

Ruby’s === operator is often called the ‘case equality’ operator, which is indicative of where it is used, but not how it works. I realised I hadn’t quite grokked the how when I read Jon Canady's post.

Jon was confused that the follow switch statement didn’t work as he’d expected - it wasn’t matching even though obj was an instance of ActiveRecord::Base.

case obj.class
  when ActiveRecord::Base
  ...
end

I knew that this was because obj.class evaluates to ActiveRecord::Base, which is an instance of Class, not of ActiveRecord::Base.

But I realised I didn’t know the mechanics of how the correct version (simply ‘case obj’) worked! Looking in the faithful RDocs for , I found that === is normally passed directly to ==, which normally checks if two objects have equivalent value. However overriding === is suggested to provide “provide meaningful semantics in case statements”. So what would these be?

I fired up IRB and had a go. First thing to realise is that in case statements, the receiver of the === operator is the expression in the when clause, not in the case clause. Second is that to remember it’s therefore being called on a Class object - a class method.

So in Jon’s correct example, the method call is ActiveRecord::Base.===(obj). Look at the API docs for ActiveRecord, and you’ll see === returns the result of calling is_a? on the object passed to ===, with the receiver of === itself as argument. Thus in Jon’s incorrect example the value of === would evaluate to the predicate Class.is_a?(ActiveRecord::Base), which of course it isn’t, as Class is a superclass of ActiveRecord.

That’s the common pattern for === AFAIK. As a class method, it’ll normally be equivalent to asking the receiver, ‘is this argument an instance of yourself?’. As an instance method, it’ll normally be equivalent to ==, value equality.

So a quick demo of === as a class method:

>> String === "a"
>> true # "a" is a String
>> "a".is_a?(String)
>> true # different way of asking the above

and for instance methods, demonstrating it is normally a value comparison, rather than an object comparison:

>> "a" === "b"
=> false # different values, different objects
>> "a" === "a"
=> true # same values
>> "a".equal?("a")
=> false # different objects
>> :a.equal? :a
=> true # same objects