Ruby’s and Rails’ case gotcha when comparing classes
While pairing yesterday I ran into quite an interesting problem when using the Ruby case statement. Now this should not come to a surprise because the case statement uses the === operator rather than the == operator (that is common in other languages). We were refactoring some code like this.
[sourcecode language="ruby"]
variable = group_class == Animal ? 8 : 2
[/sourcecode]
initially this turned into
[sourcecode language="ruby"]
if group_class == Animal
variable = 8
elsif group_class == Water
variable = 2
else
variable = nil
end
[/sourcecode]
This code was simply meant to check against class type since group_class is holding the type of this object. We decided to change this into a case statement as we could see more group_class types coming down the road in the next iteration.
[sourcecode language="ruby"]
variable = case group_class
when Animal: 3
when Water: 8
….
[/sourcecode]
So this code was assumingly supposed to work, however does not. Apparently the valid way of writing this class is to do:
[sourcecode language="ruby"]
variable = case group_class.name
when “Animal”: 3
when “Water”: 8
….
[/sourcecode]
Then this code works as designed. Tracking down the reason for this is ActiveRecord’s overloaded == and === operators.
[sourcecode language="ruby"]
# File activerecord/lib/active_record/base.rb, line 1269
1269: def ===(object)
1270: object.is_a?(self)
1271: end
—–
# File activerecord/lib/active_record/base.rb, line 2421
2421: def ==(comparison_object)
2422: comparison_object.equal?(self) ||
2423: (comparison_object.instance_of?(self.class) &&
2424: comparison_object.id == id &&
2425: !comparison_object.new_record?)
2426: end
[/sourcecode]
Just a quirk I noticed. Enjoy


October 15th, 2008 at 12:45 pm
One lesson might be to avoid writing conditionals on classes. An alternative is a method on Animal and Water that returns the right number (use respond_to? to see if that method exists). If the number returned is directly related to the class, it should be managed by the class. This helps prevent a proliferation of conditionals on classes throughout the client code.
But that has nothing to do with the gotcha you pointed out.