Rails 5 жду загрузки и затем find_by - PullRequest
0 голосов
/ 11 октября 2018

У меня есть три модели следующим образом:

class Parent < ApplicationRecord
  has_many :children
  has_many :assets
end

class Child < ApplicationRecord
  belongs_to :parent
end

class Asset < ApplicationRecord
  belongs_to :parent
end

Теперь мне нужно выяснить активы, которые принадлежат ребенку через родителя.И «Актив» имеет столбец 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

1 Ответ

0 голосов
/ 11 октября 2018

Первая проблема заключается в том, что добавление find_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" }

В качестве альтернативы, вы можете объявить ассоциацию 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).

...