I have some code like this:
case Product.new.class # ActiveRecord instance class => Product
when Module
'this condition will always be true'
when Product
'i need this to be true, but first condition is always true, so it never happens'
end
Here, when Module is always true. Why? Is this unexpected behaviour?
Ah, such a seemingly simple queston. But is it?
Here Product is some class, say:
Product = Class.new
Since
Product.new.class
#=> Product
your case statement can be simplified to
case Product
when Module
'this condition will always be true'
when Product
'i need this to be true, so it never happens'
end
Recall that the case statement uses the method === to determine which object to return, meaning that your case statement is equivalent to
if Module === Product
'this condition will always be true'
elsif Product === Product
'i need this to be true, so it never happens'
end
Let's see how the two logical expressions evaluate:
Module === Product #=> true
Product === Product #=> false
Note this is syntactic sugar for
Module.===(Product) #=> true
Product.===(Product) #=> false
Examine the docs for the method Module#=== to see how it works: it returns true if Product is an instance of Module or of one of Module's descendents. Well, is it?
Product.class #=> Class
Class.ancestors #=> [Class, Module, Object, Kernel, BasicObject]
It is! Now what about:
Product === Product
Does Product have a method ===?:
Product.methods.include?(:===)
#=> true
Where did it come from (we didn't define it, after all)? Let's first look at:
Product.ancestors
#=> [Product, Object, Kernel, BasicObject]
Does Object have a method ===? Checking the docs we see that it does: Object#===.1
So Object#=== is invoked. Right? Let's just confirm that:
Product.method(:===).owner
#=> Module
Whoops! It comes from Module (which is both a module and a class), not from Object. As we saw above, Product is an instance of Class and Class is a subclass of Module. Note also that here === is an instance method of Class (and of Module)2:
Class.instance_method(:===).owner
#=> Module
So Product is in a quandary. Should it use Module#===, an instance method supplied by it's parent (Class), who inherited it from Module, or should it go with Object#===, that it inherits from its superclass, Object? The answer is that precedence is with the former.
This is at the heart of Ruby's "object model". I will say no more about that, but I hope I have provided readers with some tools they can use to figure out what's going on (e.g., Object#method and Method#owner.
Since Product === Product uses Module#=== just as Module == Product does, we determine if the former returns true by answering the question, " is Product an instance of Product or of one of Product's decendents?". Product has no descendents and
Product.class #=> Class,
so the answer is "no", meaning Product === Product returns false.
Edit: I see I forgot to actually answer the question posed in the title. This calls for an opinion I suppose (a SO no-no), but I think case statements are the greatest thing since sliced bread. They are particularly useful when one needs to compare various values to a reference value (e.g., the contents of a variable or to a value returned by a method) using === or ==. For example (see Fixnum#===, where === is equivalent to ==--note the typo in the docs, Regexp#=== and Range#===):
str =
case x
when 1 then 'cat'
when 2,3 then 'dog'
when (5..Float#INFINITY) then 'cow'
else 'pig'
end
result =
case obj
when String
...
when Array
...
end
case str
when /\d/
...
when /[a-z]/
...
end
Beyond that, however, I often use a case statement in place of if..elsif..else..end just because I think it`s tidier and more esthetically pleasing:
case
when time == 5pm
feed the dog
when day == Saturday
mow the lawn
...
end
1 In fact, this method is available to all objects, but is not generally invoked because the method is also defined for a descendant.
2 To be thoroughly confusing, Class also has a triple-equals class method:
Class.method(:===).owner #=> Module.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With