Imagine you need a way to check the value of an object, and perform a different action based on that. The Object-Oriented Programming way is to use polymorphism, and we will see how to do that in a second.

But first, let’s look at how the case expression works, and how you can use it to achieve the goal mentioned above.

How does case work?

There are two main parts to the case expression. The case clause, and the when clause.

The when clause is the expression that defines the receiver of the operator (defaulting to === ), and the case clause defines the argument passed to it.

Let’s see an example.

case a when String #.. end

So you can think about the above example as String === a , or String.===(a) .

Finally, there’s also an else clause that you can use for when there is no match.

case my_object when String "This is a string" else "I have no idea what this is" end

The triple equals operator (===)

By default, case uses the triple equals ( === ) operator to do the comparison.

The thing with === is it has nothing to do with equality. By default, it’s aliased to the double equals operator ( == ) which normally checks if two objects have equivalent value, but it can be defined to mean anything.

For example, a range defines it as an alias for includes? , a regex defines it as an alias for match , a class for is_a? , a proc for call . You get the idea.

That operator works as expected with literals, but not with classes:

1 === 1 # => true Numeric === Numeric # => false

That is because the === is an alias for kind_of? . And here are the docs for kind_of? .

kind_of?(class) → true or false Returns true if class is the class of obj, or if class is one of the superclasses of obj or modules included in obj.

This means that if you want to do a case ... when over an object’s class, this will not work:

obj = 'Hi' case obj.class when String "It's a string" end

That is because, it translates to String === String . And that returns false .

The default operator

The case expression has a default operator. And that means you don’t have to specify it. That’s very nice, because you can just write when String and it defaults to the === operator.

But, there are times when you want to use a different operator. And Ruby allows you to do it. Here’s how.

case when a < 3 "Smaller than 3" end

Case is an expression

Just like the name says, the whole thing is an expression. That means, it evaluates to a value. So you can assign the result of the entire case expression to a variable, like so.

value = case when a < 3 "Smaller than 3" end

There is no fall-through

If you’re coming from other languages, you’ve probably used a switch statement or something similar, and you’ve noticed that case doesn’t use keywords like break to break the flow.

That is because there is no fall-through with case . It just returns the value of the expression that was matched and that’s it.

Multiple matches

Up until now, you’ve only used one value for the when clause. But you can use multiple values.

case a when 1..3 "Small number" end

Matching regexes

You can use case expressions to match anything. But just in case it’s not obvious, you could also match regexes, or lambdas. Here’s an example.

even = ->(x) { x % 2 == 0 } case a when even "It's even" when /^[0-9]+$/ "It's an integer" end

Define your own

On of the lesser known facts is that you can define your own comparators.

Text = Struct.new(:min_length) do def ===(string) string.size > min_length && string.is_a?(String) end end case a when Text.new(100) "It's text" end

In the example above, if a is a string of more than 100 characters, then the case expression will return It's text .

Use polymorphism instead

I’m not going to get into the benefits of OOP here, you can read more about that in the Object-Oriented Programming with Ruby article, but I am going to show you how you can use polymorphism to replace a case expression.

The case version

class Person attr_reader :country def initialize(country) @country = country end def nationality case country when "USA" "This guy is an American" when "Romania" "This guy is a Romanian" end end end john = Person.new("USA") puts john.nationality # => This guy is an American

The OOP version

class Person attr_reader :country def initialize(country) @country = country end def nationality country.nationality end end class America def nationality "This guy is an American" end end class Romania def nationality "This guy is a Romanian" end end john = Person.new(America.new) puts john.nationality # => This guy is an American