Netskin Logo

Pattern matching and deconstructing

#ruby
by Thomas Rudolf on 22.08.2022

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!

❮ Validation on String Inputs in Rails
RSpec Quick Tip - be_done? ❯
Netskin Logo