【问题标题】:Struggling to understand why joins/includes are needed with Rails when an association exists in the model当模型中存在关联时,努力理解为什么 Rails 需要加入/包含
【发布时间】:2017-04-10 01:48:10
【问题描述】:

我正在尝试创建一个非常复杂的查询,但遇到了麻烦 - 所以我要回到基础来尝试找出我缺少的东西。 我一直在阅读Rails GuidesActive Record AssociationsActive Record Query Interface(特别是section 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)

这种“关系”是怎么上半场起作用的,下半场却需要加入?

【问题讨论】:

    标签: sql ruby-on-rails postgresql ruby-on-rails-4 activerecord


    【解决方案1】:

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

    这是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.)
    

    这就是 ActiveRecord::Base 方法如 includesjoins 发挥作用的地方。

    这里有两篇关于这个问题的好文章:

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

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

    【讨论】:

    • 很好的描述!你真的帮助我缩小了差距。此外,第一篇文章确实给了我新的信息——我从来不明白如何混合和匹配所有的关系。希望这将帮助我重新控制我的查询,并让我得到我正在寻找的结果。 :)
    • 延迟加载的顺序是否与select.preload(articles).lastselect.last.preload(articles) 相比?
    • 是的,预加载只能在作用域部分发生。例如:User.joins(:articles).each。您必须直接从范围加载,否则它将失败,可能是未定义的。
    猜你喜欢
    • 2012-10-16
    • 2015-10-03
    • 1970-01-01
    • 2013-07-17
    • 1970-01-01
    • 2022-06-15
    • 2012-06-27
    • 1970-01-01
    • 2010-09-18
    相关资源
    最近更新 更多