Here's the standard version of the switch statement in Ruby.
season = 'winter'
case season
when 'summer'
puts 'it is warm'
when 'winter'
puts 'it is cold'
else
puts 'it is raining'
end
# output
# it is cold
In typical Ruby style of developer happiness, it's called case
and when
, instead of switch
and case
. I find Ruby's version easier to read and understand.
Note: There're no break
statements at the end of each when
clause. Unlike other languages, Ruby's case
doesn't fall through.
In the standard form, we're just comparing the expression provided to case
with the when
expression and executing the code provided. When no matches are found, the else
statement is executed.
In fact, what really happens is that Ruby compares the expression value provided in the when
clause with the value in the case
clause using the ===
operator. This lets you do fancy (but useful) stuff like this:
puts 'enter a number'
number = gets.to_i
case number
when 1..10
puts 'between 1 and 10'
when 11..20
puts 'between 11 and 20'
when 50
puts 'exactly 50'
when 60, 70
puts 'either 60 or 70'
else
puts 'invalid number'
end
Note: The order matters. We're comparing the expression provided to the when
clause with the case
expression, not the other way around (when === case
, not case === when
). This is because Ruby actually calls the ===
method on the left operand, passing the second operand as an argument.
The following example shows the difference.
(1..10) === 5 # true
5 === (1..10) # false
You can also shorten the code by returning on the same line as when
clause, using the then
clause.
def can_drive(age)
case age
when 1..14 then 'no'
when 15..100 then 'yes'
end
end
puts can_drive(18) # yes
Additionally, if you want to do comparisons on a variable, just leave out the case
expression. This helps to simplify a long and complex if-else chain.
def calculate(num)
case
when num > 10
puts 'greater than 10'
when num == 5
puts 'exact 5'
end
end
calculate 12 # greater than 10
Pretty useful. Now, let's learn how you can match data types of objects.
Matching Types
The ===
operator lets you match the types of objects. For example,
Integer === 1 # true
String === 'name' # true
That means you can pass an object in the case
clause and match types in the when
clause, as follows:
def perform(obj)
case obj
when String
puts 'a string'
when Integer
puts 'an integer'
when Vehicle
puts 'a vehicle'
else
puts 'not a string, integer, or vehicle'
end
end
perform 5 # an integer
perform 'name' # a string
class Vehicle
end
perform Vehicle.new # a vehicle
Note: Do not use obj.class
in the case
clause, as Integer === Integer
returns false. This might seem strange at first, but remember that Ruby is actually calling the ===
method on Integer
. This method checks if the second argument is an instance of this module. Hence it returns false
.
It's also possible to evaluate custom, complicated expressions via a lambda or even classes. Let's see how.
Custom Expressions
If it walks like a duck and quacks like a duck, it is a duck.
So far, we have learned that Ruby will match the when
clause with the case
clause using the ===
operator. In fact, it's calling the ===
method on the value returned by the when
clause. Because of the dynamic nature of Ruby, you can pass any object that has a ===
method.
For example, a lambda has a ===
method which simply passes the second operand as an argument to the lambda.
even = ->(x) { x % 2 == 0 }
even === 4 # true
even === 5 # false
This means that you can pass a lambda expression in the when
clause.
is_even = ->(x) { x % 2 == 0 }
is_odd = ->(x) { x % 2 == 1 }
num = 4
case num
when is_even then 'even'
when is_odd then 'odd'
end
In fact, any object that has a ===
method can be used in a when
statement.
num = 5
class Even
def ===(obj)
(obj % 2) == 0
end
end
class Odd
def ===(obj)
(obj % 2) == 1
end
end
case num
when Even.new then puts 'even'
when Odd.new then puts 'odd'
end
This example is not that useful as it's oversimplified. I can't think of a real-life scenario where I'd use custom classes, but it's pretty useful to know that Ruby let's you do this.
Finally, let's see how you can match regular expressions inside case
statement.
Match Regular Expressions
You can use the ===
operator to match a string against a regular expression.
pattern = /hello.+/
pattern === 'hello world' # true
pattern === 'world' # false
As a result, you can use regular expressions in the when
clause in a case
statement.
case word
when /\d/
puts 'digit'
when /[aeiou]/
puts 'a vowel'
end
Couldn't get simpler than this.
Pattern Matching
Pattern Matching is another powerful use case for the case statement in Ruby (Credits: Thanks to the commenter on Hackernews for pointing this out). I recommend reading the official documentation, but here's a gist of it:
Pattern matching allows deep matching of structured values: checking the structure and binding the matched parts to local variables. It uses case .. in
instead of case .. when
.
case <expression>
in <pattern1>
...
in <pattern2>
...
in <pattern3>
...
else
...
end
For example,
config = {db: {user: 'admin', password: 'abc123'}}
case config
in db: {user:} # matches subhash and puts matched value in variable user
puts "Connect with user '#{user}'"
in connection: {username: }
puts "Connect with user '#{username}'"
else
puts "Unrecognized structure of config"
end
# Prints: "Connect with user 'admin'"
It's fascinating and deserves a blog post on its own. Stay tuned!
That's a wrap. Did I miss anything? Please let me know in the comments below.
I hope you liked this article and you learned something new.
As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I look forward to hearing from you.
If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.