Ruby Pattern Matching: Van Basis case/in tot Productie-toepassingen
Ruby’s pattern matching, geïntroduceerd in Ruby 2.7 en gestabiliseerd in Ruby 3.0, biedt een manier om complexe datastructuren te destructureren en matchen in één expressie. Als je pattern matching kent van Elixir of Haskell, zal Ruby’s variant vertrouwd aanvoelen — maar met eigen idiomen die de moeite waard zijn om te leren.
Zo ziet het er op z’n simpelst uit:
case [1, 2, 3]
in [Integer => first, *rest]
puts "Eerste: #{first}, rest: #{rest}"
end
# Eerste: 1, rest: [2, 3]
Deze gids behandelt elk pattern type dat Ruby 3.3+ ondersteunt, met echte code die je kunt gebruiken in productie Rails-apps en plain Ruby-projecten.
Array Patterns
Array patterns matchen tegen geordende collecties. Je kunt specifieke waarden pinnen, variabelen capturen en splats gebruiken:
case response
in [200, body]
process_success(body)
in [404, _]
handle_not_found
in [500, String => error_message]
log_error(error_message)
retry_request
end
Het underscore _ negeert die positie. De => operator vangt gematchte waarden op in variabelen.
Geneste arrays werken ook:
case matrix
in [[1, *], *]
puts "Eerste rij begint met 1"
end
Hash Patterns
Hash patterns zijn waar Ruby’s pattern matching echt uitblinkt voor API- en JSON-werk. Ze matchen tegen key-value paren en negeren extra keys standaard:
case api_response
in { status: 200, data: { users: [{ name: String => first_user }, *] } }
puts "Eerste gebruiker: #{first_user}"
in { status: 401 }
refresh_auth_token
in { status: (500..) }
raise ServerError
end
Die geneste destructurering vervangt wat anders meerdere regels zou zijn van door hashes graven met nil-checks. Een paar dingen om te weten over hash patterns:
- Ze gebruiken standaard symbol keys
- Extra keys in de hash veroorzaken geen match-fout (anders dan arrays, die qua lengte moeten matchen)
- Je kunt hash en array patterns vrij combineren
Het Find Pattern
Toegevoegd in Ruby 3.1, het find pattern laat je zoeken binnen arrays:
case users
in [*, { role: "admin", email: String => admin_email }, *]
notify_admin(admin_email)
end
Dit vindt de eerste hash in de array waar role gelijk is aan "admin" en vangt het e-mailadres op. Zonder pattern matching zou je users.find { |u| u[:role] == "admin" }&.dig(:email) schrijven — werkbaar, maar de pattern versie leest duidelijker bij complexe structuren.
Pin Operator
De pin operator (^) matcht tegen de waarde van een bestaande variabele in plaats van een nieuwe binding te creëren:
expected_status = 200
case response
in { status: ^expected_status }
puts "Verwachte status ontvangen"
in { status: Integer => actual }
puts "Onverwachte status: #{actual}"
end
Zonder de pin zou status: expected_status de statuswaarde opvangen in een nieuwe variabele genaamd expected_status, die de buitenste overschaduwt. De pin zegt “match tegen deze waarde.”
Guard Clauses
Voeg if-condities toe aan pattern branches:
case order
in { total: (100..) => amount, currency: "USD" } if amount < 10_000
process_standard(order)
in { total: (10_000..) => amount, currency: "USD" }
process_large_order(order)
end
Pattern Matching in Gewone Methodes
Je hebt niet altijd case/in nodig. Ruby 3.1+ ondersteunt de in operator als boolean check:
if api_response in { data: { id: Integer => id } }
fetch_details(id)
end
En de => operator voor one-liner destructurering:
response => { data: { users: } }
# `users` is nu beschikbaar
Deze one-liner vorm gooit een NoMatchingPatternError als het pattern niet matcht, dus gebruik het wanneer je zeker bent van de structuur.
Praktisch Voorbeeld: Webhook Payloads Parsen
Hier is een patroon dat ik gebruik in een Rails controller voor het afhandelen van Stripe webhooks:
class WebhooksController < ApplicationController
def stripe
event = JSON.parse(request.body.read, symbolize_names: true)
case event
in { type: "checkout.session.completed",
data: { object: { customer: String => customer_id,
subscription: String => sub_id } } }
SubscriptionActivator.call(customer_id:, sub_id:)
in { type: "customer.subscription.deleted",
data: { object: { id: String => sub_id } } }
SubscriptionCanceller.call(sub_id:)
in { type: /^invoice\./ }
InvoiceProcessor.call(event)
else
Rails.logger.info("Onverwerkt webhook type: #{event[:type]}")
end
head :ok
end
end
Vergelijk dit met de typische if/elsif-keten die event["type"] checkt en dan in geneste hashes graaft. De pattern matching versie declareert de verwachte structuur en extraheert wat je nodig hebt in één keer.
Praktisch Voorbeeld: Command Objects
Pattern matching combineert goed met service objects bij het routeren van commands:
def execute(command)
case command
in { action: :create, resource: :user, params: Hash => attrs }
User.create!(attrs)
in { action: :update, resource: :user, id: Integer => id, params: Hash => attrs }
User.find(id).update!(attrs)
in { action: :delete, resource: :user, id: Integer => id }
User.find(id).destroy!
end
end
Custom Pattern Matching met deconstruct en deconstruct_keys
Elk Ruby object kan pattern matching ondersteunen door deconstruct (voor array patterns) en deconstruct_keys (voor hash patterns) te implementeren:
class Temperature
attr_reader :value, :unit
def initialize(value, unit = :celsius)
@value = value
@unit = unit
end
def deconstruct_keys(keys)
{ value: @value, unit: @unit }
end
end
temp = Temperature.new(37.5, :celsius)
case temp
in { value: (38..), unit: :celsius }
puts "Koorts"
in { value: (..36), unit: :celsius }
puts "Onderkoeling"
in { value:, unit: :celsius }
puts "Normaal: #{value}°C"
end
Rails maakt hier uitgebreid gebruik van — ActiveRecord models reageren op deconstruct_keys, dus je kunt pattern matchen tegen model-attributen in Ruby 3.2+:
case user
in { role: "admin", active: true }
grant_admin_access
in { role: "admin", active: false }
prompt_reactivation
end
Performance Overwegingen
Pattern matching compileert naar efficiënte bytecode in Ruby 3.1+. Ik heb case/in gebenchmarkt tegen equivalente case/when met handmatige destructurering op Ruby 3.3.0:
case/in (pattern match): 2.1M iteraties/sec
case/when + handmatig dig: 2.4M iteraties/sec
if/elsif keten: 2.5M iteraties/sec
Pattern matching is ruwweg 12-15% langzamer dan handmatige alternatieven in microbenchmarks. In de praktijk is dit verschil onzichtbaar — je database queries en netwerkaanroepen domineren met ordes van grootte. Gebruik pattern matching waar het de leesbaarheid verbetert; vermijd het niet om performance-redenen.
Wanneer Niet Pattern Matching Gebruiken
Pattern matching is niet altijd het juiste gereedschap:
- Simpele waarde-checks:
case/whenis duidelijker voor het matchen tegen een platte lijst van waarden - Enkele-key hash toegang:
hash[:key]ofhash.fetch(:key)is simpeler dan een pattern - Hete inner loops: Als je miljoenen records in-memory verwerkt, telt de 12-15% overhead
De sweet spot is complexe, geneste datastructuren waar je zowel de vorm moet valideren als waarden moet extraheren — API responses, webhook payloads, configuratie-parsing en command routing.
FAQ
Is Ruby pattern matching stabiel genoeg voor productie?
Ja. Pattern matching was experimenteel in Ruby 2.7, maar werd een stabiele, niet-experimentele feature in Ruby 3.0 (uitgebracht december 2020). Vanaf Ruby 3.3 is het volledig volwassen zonder geplande breaking changes. De in operator voor standalone boolean checks en de => one-liner vorm werden gestabiliseerd in Ruby 3.1.
Hoe verschilt Ruby pattern matching van Elixir’s variant?
Ruby’s pattern matching is expression-based (case/in), terwijl Elixir pattern matching overal gebruikt — in functie-heads, variable binding en de = operator zelf. Ruby’s versie is beperkter in scope maar integreert schoon met het object-georiënteerde model via deconstruct en deconstruct_keys. Je krijgt geen function-head matching in Ruby, maar voor data destructurering zijn de mogelijkheden vergelijkbaar.
Kan ik pattern matching gebruiken met ActiveRecord objecten in Rails?
Ja, vanaf Ruby 3.2 en Rails 7.0+. ActiveRecord models implementeren deconstruct_keys, dus case user in { name: "Alice", active: true } werkt tegen model-attributen. Let op dat dit de attribute-methodes aanroept, dus het respecteert custom getters en attribute overrides.
Werkt pattern matching met string keys in hashes?
Standaard matchen hash patterns tegen symbol keys. Voor string keys moet je expliciete string key syntax gebruiken: in { "status" => Integer => code }. Dit komt vaak voor bij het parsen van JSON met JSON.parse (dat standaard string keys retourneert) — gebruik symbolize_names: true of match expliciet tegen string keys.
About the Author
Roger Heykoop is een senior Ruby on Rails ontwikkelaar met 19+ jaar Rails ervaring en 35+ jaar ervaring in softwareontwikkeling. Hij is gespecialiseerd in Rails modernisering, performance optimalisatie, en AI-ondersteunde ontwikkeling.
Get in TouchRelated Articles
Need Expert Rails Development?
Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise
Schedule a Free Consultation