Rails> 3.2でincludes
メソッドによって生成された結合ステートメントに条件を追加することは可能ですか?
PersonとNoteの2つのモデルがあるとします。各個人には多くのメモがあり、各メモは1人の個人に属します。各メモには属性important
があります。
重要なメモだけをプリロードしているすべての人を見つけたいです。 SQLでは次のようになります。
SELECT *
FROM people
LEFT JOIN notes ON notes.person_id = people.id AND notes.important = 't'
Railsでは、これを行うための唯一の類似した方法は、次のようにincludes
(注:joins
はメモをプリロードしません)を使用することです:
Person.includes(:notes).where(:important, true)
ただし、異なる結果セットを返す次のSQLクエリが生成されます。
SELECT *
FROM people
LEFT JOIN notes ON notes.person_id = people.id
WHERE notes.important = 't'
最初の結果セットにはすべてのユーザーが含まれ、2番目の結果セットには重要なメモに関連付けられたユーザーのみが含まれることに注意してください。
また、:conditionsは3.1以降廃止されていることに注意してください。
このガイドによると Active Record Querying
このように、積極的な読み込みのインクルードの条件を指定できます
Person.includes(:notes).where("notes.important", true)
とにかくjoins
を使用することをお勧めします。
これの回避策は、このような別の関連付けを作成することです
class Person < ActiveRecord::Base
has_many :important_notes, :class_name => 'Note',
:conditions => ['important = ?', true]
end
その後、これを行うことができます
Person.find(:all, include: :important_notes)
Rails 5以降の構文:
Person.includes(:notes).where(notes: {important: true})
ネスト:
Person.includes(notes: [:grades]).where(notes: {important: true, grades: {important: true})
Rails 4.2以降:
オプションA-「プリロード」-複数選択、「id IN(...)」を使用)
class Person < ActiveRecord::Base
has_many :notes
has_many :important_notes, -> { where(important: true) }, class_name: "Note"
end
Person.preload(:important_notes)
SQL:
SELECT "people".* FROM "people"
SELECT "notes".* FROM "notes" WHERE "notes"."important" = ? AND "notes"."person_id" IN (1, 2)
オプションB-"eager_load"-1つの巨大な選択、 "LEFT JOIN"を使用)
class Person < ActiveRecord::Base
has_many :notes
has_many :important_notes, -> { where(important: true) }, class_name: "Note"
end
Person.eager_load(:important_notes)
SQL:
SELECT "people"."id" AS t0_r0, "people"."name" AS t0_r1, "people"."created_at" AS t0_r2, "people"."updated_at" AS t0_r3, "notes"."id" AS t1_r0, "notes"."person_id" AS t1_r1, "notes"."important" AS t1_r2
FROM "people"
LEFT OUTER JOIN "notes" ON "notes"."person_id" = "people"."id" AND "notes"."important" = ?
レオコレアの答えのような条件では、インクルードを使用できませんでした。使用したい
Lead.includes(:contacts).where("contacts.primary" =>true).first
またはあなたもできます
Lead.includes(:contacts).where("contacts.primary" =>true).find(8877)
この最後のものは、ID 8877のリードを取得しますが、その主な連絡先のみが含まれます
同じことは、日本のstackoverflowでも議論されました。かなりハッキーですが、少なくともRails 5。
Person.eager_load(:notes).joins("AND notes.important = 't'")
1つの重要な側面は、この方法により、任意の結合条件を記述できることです。欠点は、プレースホルダーを使用できないことです。そのため、結合条件としてparamsを使用する場合は注意が必要です。
1つの方法は、結合を使用して自分でLEFT JOIN句を記述することです。
Person.joins('LEFT JOIN "notes" ON "notes"."person_id" = "people.id" AND "notes"."important" IS "t"')
しかし、きれいではありません。
興味のある人のために、私はレコード属性が偽であるときにこれを試しました
Lead.includes(:contacts).where("contacts.primary" => false).first
これは機能しません。何らかの理由でブール値に対してのみtrue
が機能するため、where.not
Lead.includes(:contacts).where.not("contacts.primary" => true).first
これは完全に機能します