我想通过#includes
调用触发关系的急切加载,这是通过使用Rails 7应用Ruby 3.2上的ActiveRecord::Associations::Preloader
的自定义预加载器修改的:
class Product < ApplicationRecord
belongs_to :vendor
has_many :taxons
end
class Vendor < ApplicationRecord
has_many :products
end
records = Product.where(id: [1,2,3,4])
scope = Vendor.where.not(id: [5,6])
preloader = ActiveRecord::Associations::Preloader.new(records:, associations: :vendor, scope:).tap(&:call)
puts records.first.association_cached?(:vendor)
=> true
puts records.includes(:taxons).first.association_cached?(:vendor)
=> false
我需要scope:
参数,因为有些用户不允许访问某些资源。
如果没有includes(:taxons)
调用,关联:vendor
会正确地立即加载和缓存,但在第二种情况下,似乎Preloader
的整个立即加载都被丢弃了。
有没有办法将自定义预加载器作为链参数与其他includes
沿着包含?当我尝试包含预加载器本身时,我得到了一个错误,如下所示:
records.includes(preloader).to_a
ActiveRecord::AssociationNotFoundError: Association named '#<ActiveRecord::Associations::Preloader:0x00007f573bc31ab0>' was not found on Product; perhaps you misspelled it?
from /usr/local/bundle/gems/activerecord-7.0.4.2/lib/active_record/associations.rb:302:in `association'
2条答案
按热度按时间5t7ly7z51#
这里发生的事情是
ActiveRecord::Associations::Preloader
需要初始化对象来填充数据,但是在ActiveRecord::Relation
上调用任何查询都将重新加载对象。在
records
上调用ActiveRecord::Relation
的重载方法后,必须调用Preloader
。它应该与将自定义预加载器添加到链中没有任何性能差异,因为它仍然必须在每次结束时被调用。将它添加到链中需要一些猴子修补。它的起点将是
ActiveRecord:: Relation#preload_associations
,到您必须传递作用域的地方。如果你只需要数据,先调用
includes
,然后再调用Preloader
。如果你先需要数据而不需要taxons
,然后再包含它们,调用第二个自定义预加载器来加载taxons
数据。这也会对速度有积极的影响,因为products
和vendors
不必再次加载和初始化。ct3nt3jp2#
正如Jan Vítek所指出的,在关系上调用的任何查询都将重新加载所有对象,因此会释放所有已经急切加载的关联。
由于代码的结构,我不能保证总是调用定制的
ActiveRecord::Associations::Preloader
,所以我为ActiveRecord::Relation#preload_associations
提供了一个小的monkey补丁:现在,您可以按如下方式使用它: