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

时间:2017-03-13 21:00:28

标签: ruby-on-rails rails-activerecord polymorphic-associations

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

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

使用这些关联的代码类似于:

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

目前我的预载使用:

@issues.preload(:resource)

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

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

解决此问题的好方法是什么?是否可以手动预加载关联?

7 个答案:

答案 0 :(得分:9)

你可以在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)

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

答案 1 :(得分:0)

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

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

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

答案 2 :(得分:0)

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

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

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,即可完成。

答案 3 :(得分:0)

您需要在以下模型中定义关联:

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 -%>

全部,不再是n +一查询。

答案 4 :(得分:0)

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

我有一个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

以上查询是实际查询的一小部分,但您可以了解如何使用连接将其用于急切加载,因为它是一个多态表

希望它有所帮助。

答案 5 :(得分:0)

如果将关联对象实例化为相关对象,例如称之为变量@object或其他一些变量。 Then the render should handle the determination of the correct view via the object's class.这是一个Rails惯例,即rails&#39;魔法。

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

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

答案 6 :(得分:0)

现在可以在Rails v6.0.0.rc1中运行:https://github.com/rails/rails/pull/32655

您可以进行.includes(resource: [:address, :location])