DEV Community

Cover image for What you (probably) don't know yet about pattern matching in Ruby?
Jędrzej (NJ) Urbański
Jędrzej (NJ) Urbański

Posted on

What you (probably) don't know yet about pattern matching in Ruby?

So you most probably know about pattern matching in Ruby, right? No...
Well, let's recap what everyone is saying about it.
As for ruby-docs:

Pattern matching is a feature allowing deep matching of structured values: checking the structure and binding the matched parts to local variables.

Yawn... 🥱 Are you sleep? No? Good! Yes? Well, I'm not surprised...
Ok, stay alert now...

This is what typical "Pattern Matching in Ruby 3" article will tell you...

I assume that you already read article like this:

You can pass variable to case statement and match it.

case [1337, 420, 2137]
in foo, *bar
  "matched. foo was: #{foo}"
else
  "not matched."
end
=> "matched. foo was: 1337"

And it's great classic example how you can use pattern matching.

... but are they telling you this?

You can use pattern matching in ruby in other was then just case statement.
Let me introduce you to ✨ => and in ✨ operators, which could be used as standalone expression:

<expression> => <pattern>
<expression> in <pattern>
Enter fullscreen mode Exit fullscreen mode

The only difference between these two is that in returns boolean value, and does not raise an error when pattern is not met. However => raises NoMatchingPatternError or returns nil.

How to use it?

Interesting use case might be when using guard clause. It produces quite powerful ruby one-liner.

result = {mail: "me@nj.test"}
return mail_address if result in {mail: mail_address}
Enter fullscreen mode Exit fullscreen mode

or even shorter:

result = {mail: "me@nj.test"}
return mail if result in {mail:}
Enter fullscreen mode Exit fullscreen mode

Testing with =>

Let's create method:

def create_user(params)
    user = {type: :user, name: params[:name]}
    if params.key?(:mail)
      user[:mail] = params[:mail] unless params[:mail].nil?
    end
    user
end
Enter fullscreen mode Exit fullscreen mode

Let's write simple test for it:

# rspec
describe "create_user/1" do
    it "creates user" do
        user = create_user({name: "Mary"})
        user => {type:, name:}
        # => type = :user, name = "Mary"
    end
    it "creates user with mail if given" do
        user = create_user({name: "John", mail: "john@doe.test"})
        user => {type:, name:, mail:}
        # => type = :user, name = "John", mail = "john@doe.test"
    end
end
Enter fullscreen mode Exit fullscreen mode

We are testing here if method create_user/1 are creating has with expected keys.
The test above will pass, because it is matching all key in our user hash.

Now, let's write test that will intentionally fail:

# rspec
describe "create_user/1" do
    # ...
    it "will fail, becuase mail is nil" do
        user = create_user({name: "John", mail: nil})
        user => {type:, name:, mail:}
        # NoMatchingPatternKeyError
    end
end
Enter fullscreen mode Exit fullscreen mode
  1) create_user/1 creates user with mail if given
     Failure/Error: user => {type:, name:, mail:}

     NoMatchingPatternKeyError:
       {:type=>:user, :name=>"John"}: key not found: :mail
Enter fullscreen mode Exit fullscreen mode

As you can see test case above fails, because it cannot match :mail in user-hash. Value for this key does not exists.
We don't need to use asserts, => will throw NoMatchingPatternKeyError which make test fail.

Testing with in

Okay, let's now rewrite test using in operator.

# rspec
describe "create_user/1" do
    # ...
    it "will fail, becuase mail is nil" do
        user = create_user({name: "Mary", mail: nil})
        user in {type:, name:, mail:}
        # type = :user, name = "Mary", mail = nil
        assert mail
    end
end
Enter fullscreen mode Exit fullscreen mode

As in does not throw any error, we need to assert mail, so we know it's now nil.


Thanks! 👋 Hope you liked. Comments how you will use pattern matching in your code. 🤗 🗣 ⤵️

Top comments (0)