I have three models as follows:
class Parent < ApplicationRecord
has_many :children
has_many :assets
end
class Child < ApplicationRecord
belongs_to :parent
end
class Asset < ApplicationRecord
belongs_to :parent
end
Now I need to find out the assets that belong to a child through parent. And "Asset" has asset_type column. So I need to do something like this
Parent.first.children.each do |child|
child.parent.assets.find_by(asset_type: "first").asset_value
end
How can I do this avoiding N+1 queries?
rails: 5.1.6
ruby: 2.3.4
First issue is that adding a find_by will always execute another query no matter what you have preloaded (at least as of Rails 4, I doubt it has changed though). This is because find_by is implemented to generate more SQL. If you want to preload, you can use find instead, which is fine as long as there aren't a ridiculous number of assets per parent, but bad if there are lots and lots of assets and/or they are large objects that would take up a ton of memory (see note below for alt solution).
You can preload assets like this:
parent.children.preload(:parent => :assets) each do |child|
# Will not execute another query
child.parent.assets.find{ |asset| asset.asset_type == "first" }
Alternatively, you can declare a has_many :through association:
class Child < ActiveRecord::Base
belongs_to :parent
has_many :assets, through: :parent
...
end
Then you can simply
parent.children.preload(:assets).each do |child|
# Will not execute another query
child.assets.find { |asset| asset.asset_type == "first" }
If you want to execute the find in the db layer and not in ruby, you can define a scoped association:
class Parent < ActiveRecord::Base
has_one :first_asset, ->{ where asset_type: "first" }
...
end
This way you can preload(:parent => :first_asset) instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With