努力理解为什么当模型中存在关联时需要连接/包含



我正在尝试创建一个非常复杂的查询,但遇到了麻烦 - 所以我要回到基础,试图找出我错过了什么。 我一直在阅读Active Record AssociationsActive Record Query InterfaceRails Guides(特别是第 12 节 - 连接),但我不明白它们是如何相关的以及为什么需要连接/包含。

关联页面显示"通过Active Record关联,我们可以通过声明性地告诉Rails两个模型之间存在连接来简化这些和其他操作。 "查询"页的第 12.2 节指出"Active Record 允许您使用模型上定义的关联的名称作为快捷方式,以便在使用 joins 方法时为这些关联指定 JOIN 子句。

在我看来,这两种说法似乎有些不一致。 如果我创建为belongs_to关联,如果我尝试从两个表中提取数据,为什么需要联接? 换个角度看:

class Customer < ActiveRecord::Base
has_many :orders
end
class Order < ActiveRecord::Base
belongs_to :customer
end

如果我这样做@orders = Order.all我可以通过执行@orders.first.customer.name来输出客户名称。 但是,如果我想选择名称中带有"smith"的所有订单,我会做类似@orders=Order.where('customer.name ilike "%smith%"').joins(:customer)

为什么这种"关系"在前半段起作用,但在后半段需要加入?

您不需要加入,但是在您调用关联之前,不会加载您的数据。

这是一种称为延迟加载ActiveRecord::Base质量。

您可以在控制台的 SQL 输出中看到这一点。

user = User.find(1)
User Load (0.2ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1

这个特定的用户模型有超过一百个关联。

为什么没有加载任何内容?

因为我们还没有打电话给他们。

user.articles
Article Load (0.3ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 1

现在我们看到查询已执行。

按顺序,这在使用普通的旧 Ruby 时成为一个问题。

例如,请考虑以下事项:

users.each do |user|
puts user.articles.first.title
end

运行以下代码是有问题的,因为每次 Ruby 迭代用户时,它只为该用户调用文章。

您最终会重复查询执行以下 SQL 的每个用户:

Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 1 LIMIT 1
Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 2 LIMIT 1
Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 3 LIMIT 1
Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 4 LIMIT 1
Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 5 LIMIT 1
Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 6 LIMIT 1
etc.

我们可以通过在单个查询中最初加载所有数据来解决此问题。

users.joins(:articles).each do |user|
puts user.articles.first.title
end

这将在枚举开始之前执行以下 SQL:

Article Load (0.5ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` IN(1, 2, 3, 4, 5, 6, etc.)

这就是includesjoinsActiveRecord::Base方法发挥作用的地方。

这里有两篇关于此事的好文章:

http://blog.arkency.com/2013/12/rails4-preloading/

https://rubyinrails.com/2014/01/08/what-is-lazy-loading-in-rails/

最新更新