Pattern matching with Ruby

Ruby 2.7 introduced a pattern matching feature. It allows to match a value based on its structure with the ability to destructure the values.

Matching against array or hashes

Pattern matching in Ruby is done via:

  • one line with in / => operator (still experimental in Ruby 3.0):

      result = { success: true } 
      result => { success: }
      success # true
    
  • case statement with in operator:

      # assuming api.get returns hashes like:
      #   - { success: true|false, data: Object }
      #   - { error: true, message: String }
      case api.get('user', 1)
      in { success: true, data: user }
        p user
      in { success: false, data: error }
        p error
      end
    

To match multiple patterns within one block, use |:

case api.get('user', 1)
in { success: true, data: user }
  p user
in { success: false, data: error } | { error: true, message: error }
  p error
end

To assign matched value to a variable, use =>:

case api.get('user', 1)
in { success: true|false, data: Object } => response
  p response[:data]
end

To match the same value across pattern, use ^ (pin operator):

case [api.count('user', 'active'), api.count('user', 'invited')]
in [number, ^number]
  puts "The same number of active users as invited"
else
  puts "Number are not the same"
end

To ignore value, use _:

case api.get('user', 'active')
in [_, user]
  puts "A second active user is: #{user}"
end

Matching against classes

To make class available for pattern matching, it needs to implement 2 methods:

  • deconstruct (for array match)
  • deconstruct_key (for hash match)
Response = Struct.new(:success, :result) do
  def deconstruct
    [success, result]
  end

  def deconstruct_key
    { success: success, result: result }
  end
end

response = Response.new(true, { id: 1, name: "Jon Snow" })

case response
in [true, user]
  p user
end

case response
in { success: true, result: user }
  p user
end