From Complexity to Clarity: Mastering Ruby’s Pattern Matching Features

An amazing new feature is now available in Ruby, it’s called pattern matching & it lets you extract specific values from complex data structures like arrays, hashes, and objects using concise syntax.

In addition, it helps you check that these objects match a specific structure, so it works as a form of object validation.

Since Ruby 3.0, this feature is no longer experimental (there is an exception, as we will see later) so now you’re free to start using it in all your projects! 🙂

Matching Hash Structure

In this example, we’ll do something very interesting that, before pattern matching, took some extra work to accomplish.

And that is…

Checking if an array has certain keys & if the values of these keys comply with certain rules / patterns.

Example 1:

user = { name: "Mr. Potato", age: 20 }

case user
in { name: x, age: y }
  puts "Hello #{x}!"
else
  puts "Invalid user"
end

The most important thing to notice here is that we’re using a casestatement with a the in keyword, this keyword tells Ruby 3.0+ to search for patterns.

It’s important to note that, since we’re dealing with a hash, our in statement mirrors the expected structure.

So what will happen in this example is:

If user has a name & age keys (it can have any other keys/values too), there will be a match and "Hello #{x}!" will be printed; otherwise, you’ll see Invalid user.

In addition to checking for the keys, you can also check the values for specific class types & even if dealing with integers, you can check for a range of values.

Like this:

user = { name: "Mr. Potato", age: 20 }

case user
in { name: String => x, age: 20.. }
  puts "Hello #{x}!"
else
  puts "Invalid user"
end

Pay attention to line 4, where the in keyword is, you’ll notice this:

  • “String => x”
  • “age: 20..”

These will check that name contains a String & that agevalue is 20 or greater.

Pretty cool! Right??

Matching Arrays & Using Extra Conditions

Please remember that pattern matching is not restricted to hashes, it’s possible to match arrays & other kinds of objects.

Example:

numbers = [2,3,4,5,6,7,8,9]

case numbers
in [*all] if all.sum > 10
  puts "Valid amount."
else
  puts "Invalid amount."
end

What I’m doing here is…

Grabbing all the values inside the array, saving them inside a variable named all, then I check whether the sum of the values is greater than 10, and if it’s, then we’ve got a match!

Another example:

numbers = [2,3,4,5,6,7,8,9]

case numbers
in [first, *rest] if first.even?
  puts "Valid amount."
else
  puts "Invalid amount."
end

Here I’m grabbing the first number from the array & checking if it’s even. As you can see, your imagination is the limit 🙂

Find Patterns

Initially, I mentioned that a portion of this new feature was considered experimental.

Good news.

  • Ruby 3.2: “The find pattern is no longer experimental.” [Feature #18585]”

So what is this “find pattern” feature? It’s a way to match patterns without having to use a case statement.

Here is how it works:

[1, "b", 2] in [Integer, Integer, Integer]

# false

[1, "b", 2] in [Integer, String, Integer]

# true

[1, "b", 2] in [Integer, x, Integer]

# x = "b"

More examples:

[1,"a",2,3,"b",4,"c"] in [*, String => str, *]

# true
# str = "a"

["banana", "cookies", "milk"] in [*, /^c/ => str, *]

# true
# str = "cookies"

This is helpful when you just want to check a single object without writing a single line of code & get a boolean value (match or no match) + the values you want from the structure (str & xin the last two examples).

Notice that you can even use regular expressions here.

Video Tutorial

Conclusion

In conclusion, diving into the world of pattern matching in Ruby 3.0 and beyond unveils a powerful tool for simplifying your code and enhancing its readability.

With the ability to extract specific values from complex data structures and validate object structures effortlessly, pattern matching becomes a game-changer in your programming toolkit.

As you embrace pattern matching, remember that your creativity is the limit. Crafting elegant and efficient code is not just about functionality; it’s about making your intentions explicit, and pattern matching empowers you to achieve just that.