Как избежать 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

И тогда это сработает.