渴望加载,具体取决于Ruby on Rails中的关联类型

我有一个多态关联( belongs_to :resource, polymorphic: true ),其中resource可以是各种不同的模型。 为简化问题,假设它可以是OrderCustomer

如果是Order我想加载订单,并预加载Address 。 如果是客户,我想预先加载Customer并预加载Location

使用这些关联的代码执行如下操作:

          

目前我的预装用途:

 @issues.preload(:resource) 

但是我仍然看到加载条件关联的n +加一个问题:

 SELECT "addresses".* WHERE "addresses"."order_id" = ... SELECT "locations".* WHERE "locations"."customer_id" = ... ... 

有什么好办法解决这个问题? 是否可以手动预加载关联?

您可以在ActiveRecord::Associations::Preloader类的帮助下完成此操作。 这是代码:

 @issues = Issue.all # Or whatever query ActiveRecord::Associations::Preloader.new.preload(@issues.select { |i| i.resource_type == "Order" }, { resource: :address }) ActiveRecord::Associations::Preloader.new.preload(@issues.select { |i| i.resource_type == "Customer" }, { resource: :location }) 

过滤集合时可以使用不同的方法。 例如,在我的项目中,我使用的是group_by

 groups = sale_items.group_by(&:item_type) groups.each do |type, items| conditions = case type when "Product" then :item when "Service" then { item: { service: [:group] } } end ActiveRecord::Associations::Preloader.new.preload(items, conditions) 

您可以轻松地将此代码包装在某个帮助程序类中,并在应用程序的不同部分中使用它。

您可以将多态关联分解为单个关联。 我已经遵循了这一点,并对它如何简化我的应用程序非常满意。

 class Issue belongs_to :order belongs_to :customer # You should validate that one and only one of order and customer is present. def resource order || customer end end Issue.preload(order: :address, customer: :location) 

我实际上写了一个包装这个模式的gem,以便语法变成

 class Issue has_owner :order, :customer, as: :resource end 

并适当地设置关联和validation。 不幸的是,实施不公开或公开。 但是,做自己并不困难。

当我遇到这个问题时,我已经为自己想出了一个可行的解决方案。 我遵循的是迭代每种类型的实现并将其连接成一个数组。

首先,我们将记下为特定类型加载的属性。

 ATTRIBS = { 'Order' => [:address], 'Customer' => [:location] }.freeze AVAILABLE_TYPES = %w(Order Customer).freeze 

上面列出了为可用实现类型急切加载的关联。

现在在我们的代码中,我们将简单地遍历AVAILABLE_TYPES ,然后加载所需的关联。

 issues = [] AVAILABLE_TYPES.each do |type| issues += @issues.where(resource_type: type).includes(resource: ATTRIBS[type]) end 

通过这个,我们有一种托管方式来根据类型预加载关联。 如果您有其他类型,只需将其添加到AVAILABLE_TYPES ,将属性添加到ATTRIBS ,您就完成了。

您需要在模型中定义关联,如下所示:

 class Issue < ActiveRecord::Base belongs_to :resource, polymorphic: true belongs_to :order, -> { includes(:issues).where(issues: { resource_type: 'Order' }) }, foreign_key: :resource_id belongs_to :customer, -> { includes(:issues).where(issues: { resource_type: 'Customer' }) }, foreign_key: :resource_id end class Order < ActiveRecord::Base belongs_to :address has_many :issues, as: :resource end class Customer < ActiveRecord::Base belongs_to :location has_many :issues, as: :resource end 

现在你可能需要预加载:

 Issue.includes(order: :address, customer: :location).all 

在视图中,您应该使用显式关系名称:

 <%- @issues.each do |issue| -%> <%- case issue.resource -%> <%- when Customer -%> <%= issue.customer.name %> <%= issue.customer.location.name %> <%- when Order -%> <%= issue.order.number %> <%= issue.order.address.details %> <%- end -%> 

这就是全部,不再是一加一查询。

我想分享一下我用于条件急切加载的查询,但不确定这是否对您有帮助,我不确定但值得一试。

我有一个address模型,它是userproperty 多态 。 所以我只需手动检查addressable_type ,然后调用相应的查询 ,如下所示: –

获得userproperty ,我得到的address急切加载所需的模型

 ##@record can be user or property instance if @record.class.to_s == "Property" Address.includes(:addressable=>[:dealers,:property_groups,:details]).where(:addressable_type=>"Property").joins(:property).where(properties:{:status=>"active"}) else if @record.class.to_s == "User" Address.includes(:addressable=>[:pictures,:friends,:ratings,:interests]).where(:addressable_type=>"User").joins(:user).where(users:{is_guest:=>true}) end 

上面的查询是一小段实际查询,但您可以通过连接了解如何使用它进行急切加载, 因为它是一个多态表

希望能帮助到你。

如果将关联对象实例化为相关对象,例如将其称为变量@object或其他类似对象。 然后渲染应该通过对象的类来处理正确视图的确定。 这是一个Rails惯例,即rails的魔力。

我个人讨厌它,因为如果没有像byebug或pry这样的东西来调试bug的当前范围是如此困难,但我可以certificate它确实有效,因为我们在雇主处使用它来解决类似的问题。

我认为通过这种方法和rails缓存可以更好地解决速度问题,而不是通过预加载更快。