【问题标题】:How to Determine if Rails Association is Eager Loaded?如何确定 Rails 关联是否是急切加载的?
【发布时间】:2009-09-03 22:03:35
【问题描述】:

有谁知道确定 Rails 关联是否已被预先加载的方法?

我的情况:我有一个结果集,有时其中一个关联是预先加载的,有时不是。如果它不是预先加载的,那么我想使用 ActiveRecord 的查找来查找关联。如果它是急切加载的,我想使用检测。

例如,假设我的项目模型中有一个 shipping_info 对象的“has_many”数组。那么:

如果项目是预先加载的,最有效的加载是:

item.shipping_infos.detect { |si| si.region == "United States" }

如果项目不是预先加载的,最有效的加载是:

item.shipping_infos.where(region: "United States").first

但是除非我知道它是否被急切加载,否则我不知道调用哪个代码来有效地获取记录。如果我在没有急切加载的情况下使用第一种方法,那么我必须查找比必要更多的数据库记录。如果我在急切加载时使用第二种方法,那么我急切加载的对象将被忽略。

【问题讨论】:

    标签: ruby-on-rails activerecord eager-loading


    【解决方案1】:

    在记录上使用.association(name).loaded?


    对于 Rails loaded_foo?。

    (自 Rails 3.1 起已弃用。请参阅:https://github.com/rails/rails/issues/472。)

    【讨论】:

      【解决方案2】:

      item.shipping_infos.loaded? 会告诉你的。

      不过我得说:这条路会导致疯狂......在编写测试loaded? 以在#detect#find 之间做出决定的代码之前,请确保此实例真的 很重要,相对于正在发生的一切。

      如果这不是您的应用执行的最慢的操作,那么添加额外的代码路径会增加不必要的复杂性。仅仅因为您可能会浪费一点数据库工作,并不意味着您需要修复它——它可能以任何可衡量的方式都无关紧要。

      【讨论】:

      • 谢谢布莱恩。我明白你来自哪里。不过,这个特定的代码恰好位于我们整个应用程序中最常被调用的部分代码中,因此即使减少几微秒也会产生可衡量的差异。我会给#loaded?试一试。
      • 这仅适用于 has_many 关联!在 belongs_to 的情况下,这个调用实际上会加载另一个对象(所以它总是正确的)
      • @reto 在has_one/belongs_to 关联中检查这一点,您可以执行item.association(:shipping_info).loaded? 类似的工作。在我的回答中添加了示例和更多详细信息:stackoverflow.com/a/69349863/1454001
      • 好点,虽然已经晚了 12 年! :D
      【解决方案3】:

      我建议使用 item.association_cache.keys 来提供急切加载的关联列表。所以你item.association_cache.keys.include?(:name_of_association)

      【讨论】:

      • 完美解决方案。达博格特你是国王 :)
      • 哇,不敢相信我错过了这个。这适用于未加载关联但存在内存缓存的情况(例如,当您使用关联构建对象时)。
      • 此语法在 rails 5 中不再有效(不确定何时更改)。现在是item.association_cached? :name_of_association
      【解决方案4】:

      association_cached? 可能很合适:

      item.association_cached?(:shipping_infos)
      

      【讨论】:

        【解决方案5】:

        您可以检测单个关联是否已加载加载_foo?。例如,如果 shipping_info 是 belongs_to 关联,那么 item.loaded_shipping_info?当它被急切加载时将返回true。奇怪的是,它似乎在未加载时返回 nil(而不是 false)(无论如何在 Rails 2.3.10 中)。

        【讨论】:

          【解决方案6】:

          这个问题的解决方案应该是foo.association(:bla).loaded?,但它工作不正确 - 它检查关联并将关联标记为脏:

          class Foo; has_one :bla, :autosave => true end
          foo.association(:bla).loaded? #=> false
          foo.save # saves foo and fires select * from bla
          

          所以我在 ActiveRecord 中添加了以下扩展:

          module ActiveRecord
            class Base
              def association_loaded?(name)
                association_instance_get(name).present?
              end
            end
          end
          

          现在:

          class Foo; has_one :bla, :autosave => true end
          foo.association_loaded?(:bla) #=> false
          foo.save # saves foo
          

          【讨论】:

          • 您应该仔细检查这仍然是一个问题,并更新答案以将其范围限定为适当的 Rails 版本。我无法复制。
          • 从 Rails 4.2.6 开始仍然是一个问题。不确定 Rails 5 是否有它。
          • 将其标记为脏的后果是什么?只有一个额外的选择语句?还是有更大的危害?
          • 关系被错误地标记为脏的后果是 Rails 通常无操作保存没有脏属性的模型上的调用。因此,您将引入额外的保存,如果您在模型上使用 rails 时间戳,它将导致 updated_at 时间在模型中没有实际更改的属性时更新。
          【解决方案7】:

          看看Bullet gem.。这将告诉您何时应该和不应该使用预加载。

          【讨论】:

          • Bullet 已被证明非常有用,尽管我还很少进行其他数据库优化。至少它消除了控制台输出中一些不必要的行。 ;)
          • Bullet 插件链接已失效。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多