Рельсы включают в себя условия

Возможно ли в 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.

6 ответов

В соответствии с этим руководством Запрос активной записи

Вы можете указать условия для включений для активной загрузки, например

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 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" = ?


Синтаксис Rails 5+:

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

Вложенные:

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


Я не смог использовать входящие с условием, например, ответить Лео Корреа. Принудительно использовать:

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

или вы также можете

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

Этот последний будет извлекать Lead с идентификатором 8877, но будет включать только его первичный контакт


Один из способов - написать предложение 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

и это не сработает. Как-то для booleans работает только true, поэтому я включил его, чтобы включить where.not

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

Это прекрасно работает

licensed under cc by-sa 3.0 with attribution.