訳注
本記事で言及している#40106のマージコミットにはv6.1.1
タグが付けられていますが、2020/08/31の時点でmasterにマージされた#40106は実際にはRails 6.1のコミットリストに含まれており、Rails 6.1 Active RecordのChangelogにも記載されているので、タイトルおよび訳文ではRails 6.1と表記しています。
- commit: Merge pull request #40106 from kamipo/where_references_association_name · rails/rails@57da1d0
- 6.1 Changelogの更新コミット: Add CHANGELOG entry for #40106 [ci skip] · rails/rails@7b9ca16
- コミットリスト: Commits · rails/rails (2020/08/30〜2020/09/03の
6-1-stable
ブランチ)
Rails 6.1: whereの関連付けでjoinedテーブルのエイリアス名を参照可能になった(翻訳)
Rails 6.1で、where
の中でモデル名のエイリアスを指定できる機能が追加されました(#40106)。
たとえば以下のようなEmployee
モデルがあるとします。
class Employee < ActiveRecord::Base
has_many :subordinates, class_name: "Employee", foreign_key: :manager_id
belongs_to :manager, class_name: "Employee"
end
管理職が上に付いていない社員のmanager_id
はnil
になります。
変更前
上に付く管理職の名前がSam
である社員をすべてフェッチするシナリオで考えてみましょう。
この場合、フェッチ方法は2とおりあります。
Employee.find_by(name: "Sam").subordinates
SELECT "employees".* FROM "employees" WHERE "employees"."name" = $1 LIMIT $2 [["name", "Sam"], ["LIMIT", 1]]
SELECT "employees".* FROM "employees" WHERE "employees"."manager_id" = $1 /* loading for inspect */ LIMIT $2 [["manager_id", "e4f2c753-6057-4952-9947-d1a543b2d1c7"], ["LIMIT", 11]]
=> #<ActiveRecord::AssociationRelation [#<Employee id: "3e584548......
Employee.joins(:manager).where(manager: { name: "Sam" })
ActiveRecord::StatementInvalid (PG::UndefinedTable: ERROR: missing FROM-clause entry for table "manager")
LINE 1: ..._employees"."id" = "employees"."manager_id" WHERE "manager"....
2番目のクエリはmissing FROM-clause entry
エラーになります。
ここではmanager
がemployees
テーブルを参照することを期待していますが、Railsはそのことを認識できませんでした。
変更後
この問題を修正するため、Rails 6.1ではwhere
でemployees
テーブルへの参照にエイリアス名を使えるようにする修正が加えられました。
Employee.find_by(name: "Sam").subordinates
SELECT "employees".* FROM "employees" WHERE "employees"."name" = $1 LIMIT $2 [["name", "Sam"], ["LIMIT", 1]]
SELECT "employees".* FROM "employees" WHERE "employees"."manager_id" = $1 /* loading for inspect */ LIMIT $2 [["manager_id", "e4f2c753-6057-4952-9947-d1a543b2d1c7"], ["LIMIT", 11]]
=> #<ActiveRecord::AssociationRelation [#<Employee id: "3e584548......
Employee.joins(:manager).where(manager: { name: "Sam" })
SELECT "employees".* FROM "employees" INNER JOIN "employees" manager ON manager."id" = "employees"."manager_id" WHERE "manager"."name" = $1 /* loading for inspect */ LIMIT $2 [["name", "Sam"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Employee id: "3e584548......
このユースケースは、自己参照的なモデルとエイリアス化されたテーブルで多くの場合有用です。
概要
概要原著者の許諾を得て翻訳・公開いたします。