What you (probably) don't know yet about pattern matching in Ruby?
Tags: patternmatching, ruby, rails, programming
Published: 2023-01-30 17:38:36

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](https://docs.ruby-lang.org/en/3.0/syntax/pattern_matching_rdoc.html):
> 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.
> ```ruby
> 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>
```
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.
```ruby
result = {mail: "me@nj.test"}
return mail_address if result in {mail: mail_address}
```
or even shorter:
```ruby
result = {mail: "me@nj.test"}
return mail if result in {mail:}
```
### Testing with `=>`
Let's create method:
```ruby
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
```
Let's write simple test for it:
```ruby
# 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
```
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:

```ruby
# 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
```
```
  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
```
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.
```ruby
# 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
```
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. 🤗 🗣 ⤵️