web-dev-qa-db-ja.com

Rails with with conditions

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以降廃止されていることに注意してください。

52
gusa

このガイドによると 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)
28
Leo Correa

Rails 5以降の構文:

Person.includes(:notes).where(notes: {important: true})

ネスト:

Person.includes(notes: [:grades]).where(notes: {important: true, grades: {important: true})
20
Graham Slick

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" = ?
14
Daniel Loureiro

レオコレアの答えのような条件では、インクルードを使用できませんでした。使用したい

Lead.includes(:contacts).where("contacts.primary" =>true).first

またはあなたもできます

Lead.includes(:contacts).where("contacts.primary" =>true).find(8877)

この最後のものは、ID 8877のリードを取得しますが、その主な連絡先のみが含まれます

4
juliangonzalez

同じことは、日本のstackoverflowでも議論されました。かなりハッキーですが、少なくともRails 5。

Person.eager_load(:notes).joins("AND notes.important = 't'")

1つの重要な側面は、この方法により、任意の結合条件を記述できることです。欠点は、プレースホルダーを使用できないことです。そのため、結合条件としてparamsを使用する場合は注意が必要です。

https://ja.stackoverflow.com/q/22812/754

2
Yuki Inoue

1つの方法は、結合を使用して自分でLEFT JOIN句を記述することです。

Person.joins('LEFT JOIN "notes" ON "notes"."person_id" = "people.id" AND "notes"."important" IS "t"')

しかし、きれいではありません。

0
kirstu

興味のある人のために、私はレコード属性が偽であるときにこれを試しました

Lead.includes(:contacts).where("contacts.primary" => false).first

これは機能しません。何らかの理由でブール値に対してのみtrueが機能するため、where.not

Lead.includes(:contacts).where.not("contacts.primary" => true).first

これは完全に機能します

0
Yoko