Rails:在连接上使用.references,即使它不是必需的

我知道当你使用includeswhere .references表上指定where子句时,你应该使用.references

例:

 # will error out or throw deprecation warning in logs customers = Customer.includes(:orders).where("Orders.cost < ?", 100) 

否则,在rails 4或更高版本中,您将收到如下错误:

Mysql2 ::错误:’where子句’中的未知列’Orders.cost’:SELECT customers 。* FROM customers WHERE(Orders.cost <100)

或者您将收到弃用警告:

弃用警告:您似乎急于加载在字符串SQL片段中引用的表(用户,地址之一)。 例如:

 Post.includes(:comments).where("comments.title = 'foo'") 

目前,Active Record识别字符串中的表,并且知道将comments表连接到查询,而不是在单独的查询中加载注释。 但是,在没有编写完整的SQL解析器的情况下执行此操作本质上是有缺陷的。 由于我们不想编写SQL解析器,因此我们将删除此function。 从现在开始,当您从字符串引用表时,必须明确告诉Active Record:

Post.includes(:comments).where(“comments.title =’foo’”)。references(:comments)

如果您不依赖隐式连接引用,则可以通过设置config.active_record.disable_implicit_join_references = true完全禁用该function。 (

SELECT“users”。“id”AS t0_r0,“users”。“name”AS t0_r1,“users”。“email”AS t0_r2,“users”。“created_at”AS t0_r3,“users”。“updated_at”AS t0_r4 ,“地址”。“id”AS t1_r0,“地址”。“user_id”AS t1_r1,“地址”。“country”AS t1_r2,“地址”。“street”AS t1_r3,“地址”。“postal_code”AS t1_r4 ,“地址”。“city”AS t1_r5,“地址”。“created_at”AS t1_r6,“地址”。“updated_at”AS t1_r7 FROM“用户”LEFT OUTER JOIN“地址”ON“地址”。“user_id”=“用户“。”id“WHERE(addresses.country =’Poland’)

说得通。 当您使用includes时,Rails不希望成为SQL解析器。 因此,为了让铁路开心,我们这样做:

 # No error and no deprecation warning because we are explicitly specifying what table is referenced within that where clause customers = Customer.includes(:orders).where("Orders.cost < ?", 100).references(:orders) 

但是,当您在连接表上使用joinswhere子句时:Rails不会出错,并且rails也不会抛出弃用警告,因为它includes

 # No error and no deprecation warning. What???? customers = Customer.joins(:orders).where("Orders.cost < ?", 100) 

这对我来说没有意义。 我认为如果rails不想成为包含的SQL解析器,那么它也不希望成为joins的SQL解析器。 我认为rails更喜欢我使用像这样的references

 customers = Customer.joins(:orders).where("Orders.cost < ?", 100).references(:orders) 

所以我的问题:

  1. 我错过了什么吗? 是否绝对可以不为joins表的where子句指定joins references ,即使它几乎是includes必需的? 如果是这样的话:为什么includesjoins之间的区别?
  2. 我应该指定关于joins references吗? 也许一旦rails的SQL解析器消失了所有这些joins ,连接表上的where子句将不再起作用了?

joinsincludes (两个方法的包装: eager_loadpreload )之间的根本区别在于:

  • joins执行SQL并不加选择地返回所有记录
  • includes将确定哪些记录属于哪个模型并构建模型树: author1 -> [post, post, post], author2 -> [post, post]

例如,如果您加入Order.joins(:product_items)您将获得:

  • 所有ProductItem记录与Orders交织在一起。
  • 而使用Order.includes(:product_items)只能获得每个相应订单中嵌套的product_items订单。

因此,当您执行includes (或eager_loadpreload )以帮助区分SQL连接中的数据并将其分配给正确的模型时,此处的reference仅对Rails有用。

通过joins您可以在SQL世界中独立完成。 大多数时候我会将它与group("orders.id")和一个聚合函数一起使用:

select(orders.*, count(product_items.id) as cart_size)

这里的关键区别是includes两种操作模式:eager_load(每个关联使用一个连接)和preload(为每个关联运行一个单独的查询,因此不支持引用连接表的条件)

在rails 2(或左右)之前,唯一的选择是基于连接的选项。 当新策略被引入时,它被设为默认策略,如果为了减少人们应用中的回归而看起来需要回归旧策略。

这种检测总是有点混乱,并且有一个永无止境的长尾病例未检测到,因此最终决定删除回退并要求明确使用references来指示哪些包含的关联被用于任何条款中。查询。 你可以在eager_loading中看到这个吗? 用于决定使用哪种策略的方法。

您还可以使用preloadeager_load而不是includes来直接指示您想要的策略(或为不同的关联使用不同的策略)。

另一方面, joins总是进行连接 – 它从不需要解析您的查询,因为它根本不需要 – 它将生成连接子句,无论如何。