Pattern matching is a nifty feature that was introduced to Ruby in version 2.7 and got extended in subsequent versions. It is quite powerful and can come in handy if we have to parse and/or deconstruct nested data structures. Let’s have a look at an example, assuming we are parsing database settings:
case db_settings
in type: "ms_sql", user:
puts "Using MS Sql adapter with user #{user}" # matches { type: "ms_sql" user: anything and assigns the value of user to user}
in connection: {username: }
puts "Using username #{username}"
end
It even allows us to match against the data type, providing a convenient way to implement to provide hints on wrong data, so the example from above could be extended like so:
case db_settings
in type: "ms_sql", user:
puts "Using MS Sql adapter with user #{user}" # matches { type: "ms_sql" user: anything and assigns the value of user to user}
in String
puts "It seems the settings JSON was not properly deserialized"
else
puts "Unknown settings structure"
end
A pattern can be any Ruby object, array pattern, hash pattern or find pattern. This is similar to expressions used in when
.
Different patterns can be combined using a |
(pipe). An important difference between array & hash patterns is
that arrays only match on the whole array, while hash patterns match even if there are other keys.
Additionally, guard clauses can be used to refine the matching:
case db_settings
in type: "ms_sql" user: user if support_mssql == true
puts "Using MS Sql adapter with admin user"
end
Variable assignment
Variable assignments can also be done - we already saw this in the first example, but there are a few more ways to do it.
case [1,2,3]
in Integer => a, Integer, Integer => b
puts "First element was #{a}, last element was #{b}"
else
puts "No match found"
end
# => "First element was 1, last element was 3"
We can also do this with nested patterns and named variables:
case {posts: [{author: "John Doe", title: "Patterns"}, {author: "Jane Doe", title: "Matching"}]}
in posts: [{author: author_name}, *]
puts "The first post was by #{author_name}"
else
puts "No match found"
end
# => "The first post was by John Doe"
Or even assign the rest:
case {posts: [{author: "John Doe", title: "Patterns"}, {author: "Jane Doe", title: "Matching"}]}
in posts: [{author: author_name}, *rest]
puts "The first post was by #{author_name}, the other posts were: #{rest}"
else
puts "No match found"
end
# => "e first post was by John Doe, the other posts were: [{:author=>"Jane Doe", :title=>"Matching"}]"
More advanced examples and further reading can be found in the Ruby documentation
Happy Coding!