Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails 7: Active Modelにパターンマッチングが追加(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

参考: 週刊Railsウォッチ20220516 Active Modelでパターンマッチングを利用可能に

#45035は、現時点の7-0-stableブランチにはまだ含まれていません。

追記(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モデルを例に説明します。

  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

ここで、Userのフルネームを取得するメソッドを定義する必要があり、しかも以下の条件を満たさなければならないとします。

  1. typeが"Employer"の場合は"Name not required"を返す
  2. typeが"Employee"、かつpreferred_full_nameが指定されている場合は、preferred_full_nameを返す
  3. typeが"Employee"、かつfirst_namelast_nameが指定されている場合は、first_namelast_nameを元にフルネームを返す
  4. 上記のいずれにもマッチしない場合は"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を参照してください。

関連記事

Ruby 3のパターンマッチング応用(1)ポーカーゲーム(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。