Skip to content
AgileLogics
6 min read

Pattern Matching

Prerequisites: case expressions, Hashes

Pattern matching (Ruby 3.0+) checks a value's shape and pulls out the pieces you need in one step. It uses case with in instead of when.

Matching hashes

def describe(event)
  case event
  in { type: "signup", user: { name: String => name } }
    "New user: #{name}"
  in { type: "purchase", amount: Integer | Float => amount } if amount > 100
    "Big purchase: #{amount}"
  in { type: "purchase" }
    "Small purchase"
  else
    "Unknown event"
  end
end

puts describe({ type: "signup", user: { name: "Ada" } })
puts describe({ type: "purchase", amount: 250 })
puts describe({ type: "purchase", amount: 20 })
puts describe({ type: "refund" })
New user: Ada
Big purchase: 250
Small purchase
Unknown event

Reading the first pattern: "a hash whose :type is "signup", whose :user is a hash with a :name that is a String — and bind that string to the variable name."

  • String => name checks the type and binds the value to a variable.
  • Integer | Float means either.
  • if amount > 100 is a guard — an extra condition.
  • Hash patterns ignore extra keys, so { type: "purchase" } matches any purchase.

Matching arrays

def parse(command)
  case command.split
  in ["add", item] then "Adding #{item}"
  in ["remove", item] then "Removing #{item}"
  in ["move", from, to] then "Moving #{from} to #{to}"
  in [] then "Empty command"
  in [cmd, *] then "Unknown command: #{cmd}"
  end
end

puts parse("add milk")
puts parse("move a b")
puts parse("jump high")
Adding milk
Moving a to b
Unknown command: jump

Unlike hash patterns, array patterns must match the length exactly; * matches "any remaining elements".

Destructuring shortcut

=> on its own unpacks a value or raises NoMatchingPatternKeyError if the shape is wrong — handy for reading JSON you expect to have certain keys:

config = { db: { host: "localhost", port: 5432 } }
config => { db: { host:, port: } }
puts "#{host}:#{port}"
localhost:5432

host: with no value binds the key's value to a variable of the same name.

Common mistakes

  • Forgetting the else branch: if nothing matches, case ... in raises NoMatchingPatternError (unlike case ... when, which returns nil).
  • Pattern-matching string keys with symbol syntax. { name: } matches :name, not "name". Parse JSON with symbolize_names: true (see the JSON lesson).