Рельсы включают в себя условия
Возможно ли в Rails > 3.2 добавить условия в оператор объединения, сгенерированный методом includes
?
Скажем, у меня две модели: Person и Note. У каждого человека есть много заметок, и каждая нота принадлежит одному человеку. Каждая нота имеет атрибут 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'
Пожалуйста, обратите внимание, что первый набор результатов включает всех людей, а второй - только людей, связанных с важными заметками.
Также обратите внимание, что: условия устарели с 3.1.
Ответы
Ответ 1
В соответствии с этим руководством Запрос активной записи
Вы можете указать условия для включений для активной загрузки, например
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)
Ответ 2
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" - один огромный выбор, использует "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" = ?
Ответ 3
Я не смог использовать входящие с условием, например, ответить Лео Корреа.
Принудительно использовать:
Lead.includes(:contacts).where("contacts.primary" =>true).first
или вы также можете
Lead.includes(:contacts).where("contacts.primary" =>true).find(8877)
Этот последний будет извлекать Lead с идентификатором 8877, но будет включать только его первичный контакт
Ответ 4
Синтаксис Rails 5+:
Person.includes(:notes).where(notes: {important: true})
Вложенные:
Person.includes(notes: [:grades]).where(notes: {important: true, grades: {important: true})
Ответ 5
Один из способов - написать предложение LEFT JOIN самостоятельно, используя соединения:
Person.joins('LEFT JOIN "notes" ON "notes"."person_id" = "people.id" AND "notes"."important" IS "t"')
Не очень, но.
Ответ 6
Для заинтересованных людей я пробовал это, когда атрибут записи был ложным
Lead.includes(:contacts).where("contacts.primary" => false).first
и это не сработает. Как-то для booleans работает только true
, поэтому я включил его, чтобы включить where.not
Lead.includes(:contacts).where.not("contacts.primary" => true).first
Это прекрасно работает