Whenever you need to use some if / elsif
statements you could consider using a Ruby case statement instead. In this post, you will learn a few different use cases and how it all really works under the hood.
Note: In other programming languages this is known as a switch statement.
The components of a case statement in Ruby:
Keyword | Description |
---|---|
case | Starts a case statement definition. Takes the variable you are going to work with. |
when | Every condition that can be matched is one when statement. |
else | If nothing matches then do this. Optional. |
Ruby Case & Ranges
The case
statement is more flexible than it might appear at first sight. Let’s see an example where we want to print some message depending on what range a value falls in.
case capacity when 0 "You ran out of gas." when 1..20 "The tank is almost empty. Quickly, find a gas station!" when 21..70 "You should be ok for now." when 71..100 "The tank is almost full." else "Error: capacity has an invalid value (#{capacity})" end
I think this code is pretty elegant compared to what the if / elsif
version would look like.
Ruby Case & Regex
You can also use regular expressions as your when
condition. In the following example we have a serial_code
with an initial letter that tells us how risky this product is to consume.
case serial_code when /\AC/ "Low risk" when /\AL/ "Medium risk" when /\AX/ "High risk" else "Unknown risk" end
When Not to Use Ruby Case
When you have a simple 1:1 mapping, you might be tempted to do something like this.
case country when "europe" "http://eu.example.com" when "america" "http://us.example.com" end
In my opinion it would be better to do this instead:
SITES = { "europe" => "http://eu.example.com", "america" => "http://us.example.com" } SITES[country]
The hash solution is more efficient and easier to work with. Don’t you think?
How case works: the === method
You may be wondering how case
works under the hood. If we go back to our first example, this is what is happening:
(1..20) === capacity (21..70) === capacity (71..100) === capacity
As you can see, the condition is reversed because Ruby calls ===
on the object on the left. The ===
is just a method that can be implemented by any class. In this case, Range
implements this method by returning true only if the value is found inside the range.
This is how ===
is implemented in Rubinius (for the Range
class):
def ===(value) include?(value) end
Source: https://github.com/rubinius/rubinius/blob/master/core/range.rb#L178
Procs + Case
Another interesting class that implements ===
is the Proc
class.
Related post: Learn more about procs & lambdas.
In this example I define two procs
, one to check for an even
number, and another for odd
.
odd = proc(&:odd?) even = proc(&:even?) case number when odd puts "Odd number" when even puts "Even number" end
This is what is really happening:
odd.===(number) even.===(number)
Using ===
on a proc has the same effect as using call
.
Conclusion
You have learned how the Ruby case statement works and how flexible it can be. Now it’s your turn to start making the best use of it in your own projects.
I hope you found this article useful!
Please share this post so more people can learn! 🙂
There are some more usages I’ve found useful:
* typecheck: case x; when Numeric ….
* define
===
method for your own class: case x; when MyCoolPattern.new(…) …* using case without checked variable, just as “structured conditions check”: case; when x > 1; … when something_happened …
Nice write up!
Thank you 🙂
There is another cool feature with checking object’s class:
Yeah this works because Module implements
===
. Just be careful with this one, you want to use polymorphism if possible instead of checking on the class.Ok, thanks!
I’ve never seen a Proc defined that way
proc(&:odd?)
. That’s pretty genius! ^_^You can use case with arrays:
Instead of
proc(&:odd?)
you can also use:odd?.to_proc
.Btw. odd and even should really be constants IMO.