Ruby's === equality operator
=== 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
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