Как избежать n + 1 запросов при использовании полиморфных ассоциаций?
У меня есть 4 модели, скажем:
class Photo < ActiveRecord::Base
belongs_to :photoable, polymorphic: true
end
class User < ActiveRecord::Base
has_one :photo, as: :photoable
end
class Company < ActiveRecord::Base
has_one :photo, as: :photoable
has_many :products
end
class Products < ActiveRecord::Base
belongs_to :company
end
Итак, запрос Photo.all.includes(:photoable)
работает.
Но если я использую Photo.all.includes(photoable: :products)
, работает только в том случае, если все загруженные фотографии принадлежат Компании. Если отношение содержит фотографии пользователей и компаний, эта ошибка возникает:
ActiveRecord::ConfigurationError (Association named 'products' was not found; perhaps you misspelled it?):
Это происходит потому, что пользователь не имеет отношения к продуктам.
Есть ли способ загружать пользователей и компаний с продуктами для отношения фотографий?
EDIT:
Этот вопрос не дублируется Политическая нерезкость загрузки. Как я уже отмечал ниже, в этом вопросе я хочу сделать большую нагрузку для полиморфных ассоциаций, которые имеют разные ассоциации (у одного есть продукты, а другие нет). В этом вопросе OP использует неправильные имена для имен таблиц.
Ответы
Ответ 1
У меня такие же проблемы, и я нашел решение.
Вы можете вызвать оживленную нагрузку после поиска:
photos = Photo.all.includes(:photoable)
photos_with_products_association = photos.select{|p| p.photoable.is_a?(Company)}
ActiveRecord::Associations::Preloader.new.preload(
photos_with_products_association,
:products
)
... или более общий
photos_with_products_association = photos.select do |p|
p.class.reflections.keys.include?(:products)
end
Ответ 2
Странно, что вы присоединяетесь к компаниям к своим фотографиям, а не наоборот. Почему ты бы так поступил? И вам действительно нужны эти продукты компании в представлении или контроллере?
SELECT * from photos
LEFT JOIN users AS u ON u.id = photos.photoable_id AND
photos.photoable_type = 'User'
LEFT JOIN companies AS c ON c.id = photos.phottoable_id AND
photos.photoable_type = 'Company'
LEFT JOIN products AS p ON p.company_id = c.id
Ответ 3
Вы можете добавить определенные ассоциации к модели Photo
:
class Photo < ActiveRecord::Base
belongs_to :photoable, polymorphic: true
belongs_to :user, -> { where(photoable_type: 'User' ) }, foreign_key: :photoable_id
belongs_to :company, -> { where(photoable_type: 'Company' ) }, foreign_key: :photoable_id
end
После этого вы можете предварительно установить вложенные определенные ассоциации:
photos = Photo.all.includes(:photoable, company: :products)
С доступом к таким записям:
photos.each do |photo|
puts photo.photoable.inspect
puts photo.company.products.inspect if photo.photoable_type == "Company"
end
Этот подход загружает ассоциацию company
дважды, но не выполняет n + 1 запросов.
Ответ 4
class Products < ActiveRecord::Base
belongs_to :company
end
Должно быть
class Product < ActiveRecord::Base
belongs_to :company
end
И тогда это сработает.