Rails 包括一些条件

在 Rails > 3.2中是否可以向由 includes方法生成的 join 语句添加条件?

假设我有两个模型,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开始就不推荐使用。

69718 次浏览

根据本指南 活动记录查询

您可以像下面这样在 include 上指定急切加载的条件

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)

一种方法是自己编写 LEFT JOIN 子句,使用联接:

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

虽然不太好看。

我不能使用包括在里奥 · 科雷亚的回答这样的条件。 我需要使用:

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

或者你也可以

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

最后一个将检索 id 为8877的 Lead,但只包含其主要联系人

Rails 4.2 + :

选项 A-“ preload”: 多个选择,使用“ 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-“ ear _ 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" = ?

对于感兴趣的人,我尝试了记录属性为 false 的情况

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

不知道为什么,对于布尔型,只有 true可以工作,所以我把它包含在 where.not

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

这样很好

Rails 5 + 语法:

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

嵌套:

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

日本的堆栈溢出也是这样讨论的,虽然很古怪,但是跟随似乎是有效的,至少在轨道5上是这样。

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

一个重要的方面是,通过这种方式,您可以编写任意的连接条件。缺点是不能使用占位符,因此在使用 params 作为连接条件时需要小心。

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

一个非常简单的例子:

User.includes(:posts).where(posts: { name: 'John' })

备注:

  • 如果您的关联是 has _ one,将: post 替换为: post (注意复数/单数)。

  • 更多信息 给你


如果你有的话,可以进一步联想

thing.other_things.includes(:even_more_things).where(other_things: {col1: 'value'})

但是如果你想过滤 even_more_things,你可以使用:

thing.other_things.joins(:even_more_things).where(even_more_things: { attribute: value })