Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 5 Eager load and then find_by

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

like image 547
Arefin Avatar asked Dec 10 '25 17:12

Arefin


1 Answers

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.

like image 165
Andrew Schwartz Avatar answered Dec 12 '25 14:12

Andrew Schwartz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!