Rails 5 预加载然后 find_by
Rails 5 Eager load and then find_by
我有如下三个模型:
class Parent < ApplicationRecord
has_many :children
has_many :assets
end
class Child < ApplicationRecord
belongs_to :parent
end
class Asset < ApplicationRecord
belongs_to :parent
end
现在我需要通过parent找出属于child的资产。 "Asset" 有 asset_type 列。所以我需要做这样的事情
Parent.first.children.each do |child|
child.parent.assets.find_by(asset_type: "first").asset_value
end
如何避免 N+1 次查询?
rails: 5.1.6
ruby: 2.3.4
第一个问题是添加 find_by
将 总是 执行另一个查询,无论您预加载了什么(至少从 Rails 4 开始,我怀疑它已经改变了)。这是因为实施 find_by
以生成更多 SQL。如果你想预加载,你可以使用 find
代替,只要每个父级的资产数量不多,这就很好,但如果有很多资产 and/or 他们是会占用大量内存的大对象(请参阅下面的注释了解替代解决方案)。
您可以像这样预加载资产:
parent.children.preload(:parent => :assets) each do |child|
# Will not execute another query
child.parent.assets.find{ |asset| asset.asset_type == "first" }
或者,您可以声明一个 has_many :through
关联:
class Child < ActiveRecord::Base
belongs_to :parent
has_many :assets, through: :parent
...
end
那你就可以
parent.children.preload(:assets).each do |child|
# Will not execute another query
child.assets.find { |asset| asset.asset_type == "first" }
如果你想在db层而不是在ruby中执行查找,你可以定义一个作用域关联:
class Parent < ActiveRecord::Base
has_one :first_asset, ->{ where asset_type: "first" }
...
end
这样你就可以 preload(:parent => :first_asset)
了。
我有如下三个模型:
class Parent < ApplicationRecord
has_many :children
has_many :assets
end
class Child < ApplicationRecord
belongs_to :parent
end
class Asset < ApplicationRecord
belongs_to :parent
end
现在我需要通过parent找出属于child的资产。 "Asset" 有 asset_type 列。所以我需要做这样的事情
Parent.first.children.each do |child|
child.parent.assets.find_by(asset_type: "first").asset_value
end
如何避免 N+1 次查询?
rails: 5.1.6
ruby: 2.3.4
第一个问题是添加 find_by
将 总是 执行另一个查询,无论您预加载了什么(至少从 Rails 4 开始,我怀疑它已经改变了)。这是因为实施 find_by
以生成更多 SQL。如果你想预加载,你可以使用 find
代替,只要每个父级的资产数量不多,这就很好,但如果有很多资产 and/or 他们是会占用大量内存的大对象(请参阅下面的注释了解替代解决方案)。
您可以像这样预加载资产:
parent.children.preload(:parent => :assets) each do |child|
# Will not execute another query
child.parent.assets.find{ |asset| asset.asset_type == "first" }
或者,您可以声明一个 has_many :through
关联:
class Child < ActiveRecord::Base
belongs_to :parent
has_many :assets, through: :parent
...
end
那你就可以
parent.children.preload(:assets).each do |child|
# Will not execute another query
child.assets.find { |asset| asset.asset_type == "first" }
如果你想在db层而不是在ruby中执行查找,你可以定义一个作用域关联:
class Parent < ActiveRecord::Base
has_one :first_asset, ->{ where asset_type: "first" }
...
end
这样你就可以 preload(:parent => :first_asset)
了。