10

I am using Ruby 1.9.2 and Ruby on Rails 3.2.2. I have the following method:

# Note: The 'class_name' parameter is a constant; that is, it is a model class name.
def my_method(class_name)
  case class_name
  when Article then make_a_thing
  when Comment then make_another_thing
  when ...     then ...     
  else raise("Wrong #{class_name}!")
  end  
end

I would like to understand why, in the case statement above, it always runs the else "part" when I execute method calls like my_method(Article), my_method(Comment) and so on.

How can I solve the issue? Does someone have advice how to handle this?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
user12882
  • 4,702
  • 9
  • 39
  • 54
  • Would it be because `case` statement use the `===` and that a class cannot be subsumed to itself ? – oldergod Oct 23 '12 at 00:42
  • How to you invoke this method? That is, what arguments are you passing to it? This makes all the difference. – Alex Wayne Oct 23 '12 at 00:45
  • @Alex Wayne - I am passing constant names (model class names). As wrote in the question, I run methods as `my_method(Article)`, `my_method(Comment)` and so on. – user12882 Oct 23 '12 at 00:46
  • @user12882 you should rename your `class_name` variable to `klass` to avoid confusion. What you call a model class name is a name, hence a String. What you are actually passing is `Class`. – oldergod Oct 23 '12 at 00:50

3 Answers3

5

This is because case calls ===, and === on Class (or specifically Module, which Class descends from) is implemented like so:

mod === objtrue or false

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.

This means that for any constant except Class & Module (e.g. Foo), Foo === Foo always returns false. As a result, you always get the else condition in your case statement.

Instead just call case with the object itself, instead of its class, or use if statements.

Community
  • 1
  • 1
Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
  • This is actually wrong. `Class === Class` is `true`. `Module === Module` is also `true`. – sawa Oct 23 '12 at 10:19
  • @sawa Indeed. Updated. Those are true because `Class` & `Module` are in fact both instances of `Class`, which descends from `Module`. (Isn't that so mind-bogglingly meta?) – Andrew Marshall Oct 23 '12 at 11:02
4

Pass an object reference to the method, as in the background it uses the === operator, so these will fail. e.g.

obj = 'hello'
case obj.class
when String
  print('It is a string')
when Fixnum
  print('It is a number')
else
  print('It is not a string')
end

This on the other hand, works fine:

obj = 'hello'
case obj  # was case obj.class
when String
  print('It is a string')
when Fixnum
  print('It is a number')
else
  print('It is not a string')
end

See a relevant answer to "How to write a switch statement in Ruby" https://stackoverflow.com/a/5694333/1092644

Community
  • 1
  • 1
Rym
  • 650
  • 4
  • 16
2

You can add to_s to the class constant if you just want to compare the equality of names.

def my_method(class_name)
  case class_name.to_s
  when 'Article'
    make_a_thing
  when 'Comment'
    make_another_thing

  ... ...

  end  
end
Steven Yue
  • 966
  • 1
  • 8
  • 16