Kristaps A.
Kristaps A.

Reputation: 187

Case statement for class fails, converting to string succeeds

Using case/when statements for checking an instance's class fails. If I compare an Answer instance's class as follows, it fails:

subject = Answer.new
case subject.class
when Answer  
  true
else  
  false
end  
# => false

If I convert an Answer instance's class to string, it works:

subject = Answer.new
case subject.class.to_s
when 'Answer'  
  true
else  
  false
end  
# => true

This isn't how it's supposed to be, right? Additionally, when I run a simple comparison, it works:

subject.class == Answer
# => true

Edit: Using the newly-learned properties of Ruby's case and ===, this code is valid and works.

subject = Answer.new
case subject
when Answer  
  true
else  
  false
end  
# => true

Upvotes: 2

Views: 106

Answers (2)

Mihai Dinculescu
Mihai Dinculescu

Reputation: 20033

Answer returns the generic class definition.
Answer.new.class returns a hash of an actual instance.

> Answer
 => Answer(id: integer, created_at: datetime, updated_at: datetime) 
> Answer.new
 => #<Answer id: nil, created_at: nil, updated_at: nil>

Notice the # and the different formatting.

As lurker kindly pointed out in his answer, case uses the === operator, not ==.

Using this information, you can rewrite your case statement like this

 > subject = Answer.new
 > case subject      
?>   when Answer
?>   true
?>   else
?>     false
?>   end
 => true 

The above case translates to "does subject belong to Answer?". And the answer is yes, because subject is an instance of Answer.

Upvotes: 1

lurker
lurker

Reputation: 58294

The case in Ruby is based upon the === operator. When you have:

case x
when foo
...
when bar
...
end

It's behavior is:

if foo === x
   ...
elsif bar === x
   ...
end

In a general sense, a === b means that b belongs to or is subsumed by what a represents. See the definition given here:

Case Equality—Returns true if obj is an instance of mod or one of mod’s descendants. Of limited use for modules, but can be used in case statements to classify objects by class.

For example, (2..7) === 3 is true because 3 is contained in the range, 2..7. Based upon how objects define ===, it is also true that 2 === 2 Since 2 belongs to the class of 2. It's not a commutative operator (for example, a === b true doesn't mean b === a is true). An expression such as Integer === 3 is true because a 3 is an Integer. But it is not true that 3 === Integer because 3 doesn't represent or subsume the entire class of Integer. So the === operator makes the Ruby case statement do some powerful and useful matching.

In this particular case statement, you're checking if Answer === subject.class is true. The operator === is not defined on a class object in such a way as to make this true because subject.class does not belong to the class of Answer. However, s.class == Answer is true because s.class is indeed Answer. So the case doesn't match. However, Answer === s is true because s is a type of Answer.

Upvotes: 1

Related Questions