Rails now provides pattern matching support for ActiveModel


The most important feature that was released in Ruby 2.7 was Pattern Matching. This feature is common in most functional programming languages such as Scala, Elixir, etc.

In Ruby, Pattern matching is the way to recognize a pattern of data and execute some actions if the data matches the pattern. Pattern matching in Ruby is performed using the case statement wherein the keyword in is used instead of when to match patterns.

An example of pattern matching in Ruby,

case ['apple', 'mango', 'orange', 'guava']
in ['mango', 'apple', 'orange', 'guava']
  puts "Fruits in incorrect order"
in ['apple', 'mango', 'orange', 'guava']
  puts "Fruits in correct order"
end

#=> "Fruits in the correct order"

case [1, 2, 3, 4]
in [1, 3, *other_numbers]
  puts "Incorrect match"
in [1, *other_numbers]
  puts "Correct match"
  puts other_numbers
end
#=> "Correct match"
#=> 2
#=> 3
#=> 4


point = { x: 1, y: 2, z: 3 }
case point
in { x:, y:, z: }
  puts "The points are #{x}, #{y}, #{z}"
else
  puts "The pattern are not matched correctly"
end

#=> "The points are 1, 2, 3"

This feature was introduced in 2.7 and the Ruby community loves this feature as it makes the code more readable. Rails will now allow the same pattern matching feature against ActiveModel (also ActiveRecord) objects.

To understand how this could help the Rails codebase, let’s take an example of a User model with the following attributes-

  1. type
  2. preferred_full_name
  3. first_name
  4. last_name
class User
  include ActiveModel::AttributeMethods

  attr_accessor :type, :preferred_full_name, :first_name, :last_name
end

We need to define a method to get a User’s full name but with the following conditions

  1. If type is “Employer”, then return “Name not required”
  2. If type is “Employee” and preferred_full_name is given then return the preferred_full_name
  3. If type is “Employee” and first_name and last_name is given, then return the full name with first_name and the last_name
  4. If none of these scenarios are matched, return “Name not found”

Before

Before it’s latest version, the following code was used to create this method-

def get_employee_name(user)
  if user[:type] == "Employer"
    return "Name not required"
  elsif user[:type] == "Employee"
    if user[:preferred_full_name]
      return user[:preferred_full_name]
    elsif user[:first_name] && user[:last_name]
      return "#{user[:first_name]} #{user[:last_name]}"
    end
  end
  return "Name not found"
end

After

After the latest Rails version is released, the above method can become more readable using Pattern Matching.

def get_employee_name(user)
  case user
  in { type: "Employer" }
    return "Name not required"
  in { type: "Employee", preferred_full_name: }
    return preferred_full_name
  in { first_name:, last_name: }
    return "#{first_name} #{last_name}"
  else
    return "Name not found"
  end
end

user = User.new(type: "Employer", preferred_full_name: "Smith John", first_name: "John", last_name: "Smith")
get_employee(user) #=> "Name not required"

user = User.new(type: "Employee", preferred_full_name: "Smith John", first_name: "John", last_name: "Smith")
get_employee(user) #=> "Smith John"

user = User.new(type: "Employee", first_name: "John", last_name: "Smith")
get_employee(user) #=> "John Smith"

user = User.new(type: "Employee")
get_employee(user) #=> "Name not found"

The above method makes the code more readable and easy to understand. This enhancement helps a lot in writing methods with complex logic much easier.

Note: The enhancement is yet to be released in the official Rails version

Check out the PR for more details.

Join Our Newsletter