追記(2022/07/14)
その後、#45035は議論を煮詰める必要があるということでいったんマージが取り消されました。
Rails 7: Active Modelにパターンマッチングが追加(翻訳)
Ruby 2.7でリリースされた最も重要な機能といえばパターンマッチングです。パターンマッチングは、ScalaやElixirなどほとんどの関数型プログラミング言語で一般的です。
Rubyのパターンマッチングは、データのパターンを認識して、そのデータがパターンに一致する場合に何らかの操作を実行する手法です。Rubyではcase
を使いますが、通常のwhen
の代わりに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"
Ruby 2.7でこの機能が導入されて以来、Rubyコミュニティーはコードを読みやすくできるこの機能に惚れ込みました。Railsでも、Active Modelオブジェクト(およびActive Recordオブジェクト)に対して同じパターンマッチングが使えるようになりました。
パターンマッチングがRailsコードベースでどう役立つかを理解するために、以下の属性を持つUserモデルを例に説明します。
type
preferred_full_name
first_name
last_name
class User
include ActiveModel::AttributeMethods
attr_accessor :type, :preferred_full_name, :first_name, :last_name
end
ここで、Userのフルネームを取得するメソッドを定義する必要があり、しかも以下の条件を満たさなければならないとします。
type
が"Employer"の場合は"Name not required"を返すtype
が"Employee"、かつpreferred_full_name
が指定されている場合は、preferred_full_name
を返すtype
が"Employee"、かつfirst_name
とlast_name
が指定されている場合は、first_name
とlast_name
を元にフルネームを返す- 上記のいずれにもマッチしない場合は"Name not found"を返す
改修前
最新バージョンより前は、以下のようなコードでメソッドを作成していました。
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
改修後
最新バージョンのRailsのリリース後には、パターンマッチングを用いて上のメソッドを以下のように読みやすくできます。
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"
上のメソッドは読みやすくて理解も簡単です。この拡張は、メソッドの複雑なロジックを楽に書けるようにするのに大変有用です。
原注: この拡張は、まだ公式のRailsではリリースされていません。
詳しくは#45035を参照してください。
概要
元サイトの許諾を得て翻訳・公開いたします。
参考: 週刊Railsウォッチ20220516 Active Modelでパターンマッチングを利用可能に
#45035は、現時点の7-0-stableブランチにはまだ含まれていません。