Rails 5 Eager 加载然后 find_by

Are*_*fin 2 activerecord ruby-on-rails eager-loading ruby-on-rails-5

我有以下三个模型:

class Parent < ApplicationRecord
  has_many :children
  has_many :assets
end

class Child < ApplicationRecord
  belongs_to :parent
end

class Asset < ApplicationRecord
  belongs_to :parent
end
Run Code Online (Sandbox Code Playgroud)

现在我需要通过父母找出属于孩子的资产。并且“资产”具有资产类型列。所以我需要做这样的事情

Parent.first.children.each do |child|
  child.parent.assets.find_by(asset_type: "first").asset_value
end
Run Code Online (Sandbox Code Playgroud)

如何避免 N+1 查询?

导轨:5.1.6

红宝石:2.3.4

And*_*rtz 7

第一个问题是,无论您预加载了什么,添加 afind_by都将始终执行另一个查询(至少从 Rails 4 开始,我怀疑它是否已更改)。这是因为find_by被实现来生成更多的 SQL。如果你想预加载,你可以find改用,只要每个父级的资产数量不多,这很好,但如果有很多很多资产和/或它们是会占用的大对象,那就不好了大量内存(有关替代解决方案,请参阅下面的注释)。

您可以像这样预加载资产:

parent.children.preload(:parent => :assets) each do |child|
# Will not execute another query
child.parent.assets.find{ |asset| asset.asset_type == "first" }
Run Code Online (Sandbox Code Playgroud)

或者,您可以声明一个has_many :through关联:

class Child < ActiveRecord::Base
  belongs_to :parent
  has_many :assets, through: :parent
  ...
end
Run Code Online (Sandbox Code Playgroud)

然后你可以简单地

parent.children.preload(:assets).each do |child|
# Will not execute another query
child.assets.find { |asset| asset.asset_type == "first" }
Run Code Online (Sandbox Code Playgroud)

如果你想在 db 层而不是在 ruby​​ 中执行查找,你可以定义一个作用域关联:

class Parent < ActiveRecord::Base
  has_one :first_asset, ->{ where asset_type: "first" }
  ...
end
Run Code Online (Sandbox Code Playgroud)

这样你就可以了preload(:parent => :first_asset)