Pattern Matching

23.1 case / in Patterns

def handle(response)
  case response
  in { status: 200, body: }                     # hash pattern + binding
    "OK: #{body}"
  in { status: 301 | 302, headers: { location: } }   # alternative + nested
    "Redirect to #{location}"
  in { status: 400..499 => code }               # range + bind with =>
    "Client error #{code}"
  in { status: Integer => code } if code >= 500 # guard clause
    "Server error #{code}"
  in [Symbol => type, *rest]                    # array pattern with splat
    "Tuple #{type} with #{rest.size} items"
  in String | Symbol => name
    "Name: #{name}"
  in nil
    "Nothing"
  else
    "Unknown"
  end
end

23.2 Pattern Types

in 0, in "ok", in:admin in Integer, in 1..10, in /\A\d+\z/

in [x, y], in [first, *], in [*, last] in [*, { role::admin } => admin, *]

in { name: String => name, age: }, in { role::admin, rest }, in { nil } (no extra keys) in Integer => n

in ^expected (match an existing variable's value), in ^(1 + 1) in [x, y] if x > y

23.3 Standalone Pattern Matching

config = { db: { user: "admin", port: 5432 } }
config => { db: { user:, port: } }          # rightward destructuring (raises NoMatchingPatternKeyError if no match)
puts user, port

if config in { db: { port: Integer } }      # boolean check
  puts "valid port"
end

23.4 Custom Classes in Patterns

Struct and Data support deconstruct/deconstruct_keys automatically.

class Point
  attr_reader :x, :y
  def initialize(x, y) = (@x, @y = x, y)
  def deconstruct = [x, y]                       # array patterns
  def deconstruct_keys(keys) = { x:, y: }        # hash patterns
end

case Point.new(0, 5)
in Point(x: 0, y:) then "On the y-axis at #{y}"
in Point[x, y] if x == y then "Diagonal"
end