Pattern matching makes handling JSON, API responses and nested data both concise and safe.
case/in (stable since Ruby 3.0) matches a value against patterns that check structure and bind variables at the same time:
case response
in { status: 200, body: { user: { name: String => name } } }
greet(name)
in { status: 404 | 410 }
not_found
in { status: Integer => code } if code >= 500
retry_later(code)
endBuilding blocks:
- Value patterns compare with
===, so classes, ranges and regexes work:Integer,1..5,/@/. - Array patterns
[x, y]require exact length;[first, *rest]captures the tail; the find pattern[*, { id: 3 } => hit, *]searches anywhere. - Hash patterns
{ name: }match a subset of keys and bindname. Use**restto capture the others or**nilto forbid extras. They match symbol keys only. |for alternatives,=> varto bind a sub-pattern,if/unlessguards.^varpins an existing variable's value instead of rebinding it:in { id: ^expected_id }.
If no branch matches and there's no else, Ruby raises NoMatchingPatternError, which surfaces unexpected data early.
One-liners: config => { db: { host: } } destructures or raises, and value in { type: :admin } returns true or false. Classes opt in by defining deconstruct and deconstruct_keys; Data and Struct already do.
Example
def describe(event)
case event
in { type: "click", pos: [x, y] } if x.negative? || y.negative?
"click outside"
in { type: "click", pos: [x, y] }
"click at #{x},#{y}"
in { type: "key", key: "a".."z" => k }
"letter #{k}"
in { type: String => t, **rest }
"other #{t} with #{rest.keys}"
end
end
puts describe({ type: "click", pos: [3, 4] }) # click at 3,4
puts describe({ type: "click", pos: [-1, 0] }) # click outside
puts describe({ type: "key", key: "q" }) # letter q
puts describe({ type: "scroll", dy: 5 }) # other scroll with [:dy]
config = { db: { host: "localhost", port: 5432 } }
config => { db: { host:, port: } }
puts "#{host}:#{port}" # localhost:5432
expected = 2
found = ([{ id: 1 }, { id: 2 }] in [*, { id: ^expected }, *])
p found # trueRemember
- case/in checks structure and binds variables in one step
- Hash patterns match a subset of symbol keys; array patterns match exact length
- Use ^ to pin an existing value; unmatched cases raise NoMatchingPatternError
Watch out for
- Expecting in { x: } to match string-keyed hashes from JSON.parse; parse with symbolize_names: true.