Яркая нагрузка полиморфная

Использование Rails 3.2, что не так с этим кодом?

@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe')

Возникает эта ошибка:

Нельзя с нетерпением загрузить полиморфную ассоциацию: обзор

Если я удалю условие reviewable.shop_type = ?, оно будет работать.

Как фильтровать на основе reviewable_type и reviewable.shop_type (на самом деле shop.shop_type)?

Ответ 1

Я предполагаю, что ваши модели выглядят так:

class User < ActiveRecord::Base
  has_many :reviews
end

class Review < ActiveRecord::Base
  belongs_to :user
  belongs_to :reviewable, polymorphic: true
end

class Shop < ActiveRecord::Base
  has_many :reviews, as: :reviewable
end

Вы не можете выполнить этот запрос по нескольким причинам.

  • ActiveRecord не может создать соединение без дополнительной информации.
  • Нет таблицы, называемой обзорной

Чтобы решить эту проблему, вам необходимо явно определить взаимосвязь между Review и Shop.

class Review < ActiveRecord::Base
   belongs_to :user
   belongs_to :reviewable, polymorphic: true
   # For Rails < 4
   belongs_to :shop, foreign_key: 'reviewable_id', conditions: "reviews.reviewable_type = 'Shop'"
   # For Rails >= 4
   belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
   # Ensure review.shop returns nil unless review.reviewable_type == "Shop"
   def shop
     return unless reviewable_type == "Shop"
     super
   end
end

Затем вы можете запросить следующее:

Review.includes(:shop).where(shops: {shop_type: 'cafe'})

Обратите внимание, что имя таблицы shops, а не reviewable. В базе данных не должно быть таблицы, которую можно просмотреть в базе данных.

Я считаю, что это проще и гибче, чем прямое определение join между Review и Shop, поскольку оно позволяет вам получать нагрузку в дополнение к запросу по связанным полям.

Причина, по которой это необходимо, заключается в том, что ActiveRecord не может создавать соединение на основе только рассмотренных, поскольку несколько таблиц представляют собой другой конец соединения, а SQL, насколько мне известно, не позволяет вам присоединиться к таблице, названной значение, хранящееся в столбце. Определив дополнительную связь belongs_to :shop, вы предоставляете ActiveRecord информацию, необходимую для завершения соединения.

Ответ 2

В качестве добавления ответ сверху, который является превосходным, вы также можете указать :include в ассоциации, если по какой-либо причине используемый вами запрос не включает таблицу модели, и вы получаете ошибки таблицы undefined.

Так же:

belongs_to :shop, 
           foreign_key: 'reviewable_id', 
           conditions: "reviews.reviewable_type = 'Shop'",
           include: :reviews

Без параметра :include, если вы просто обращаетесь к ассоциации review.shop в приведенном выше примере, вы получите ошибку UndefinedTable (протестированную в Rails 3, а не 4), потому что ассоциация будет выполнять SELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' ).

Опция :include заставит JOIN вместо этого.:)

Ответ 3

@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe').references(:reviewable)

Когда вы используете SQL-фрагменты с WHERE, ссылки необходимы для объединения вашей ассоциации.

Ответ 4

Если вы получаете ActiveRecord:: EagerLoadPolymorphicError, это потому, что includes решил вызвать eager_load, когда полиморфные ассоциации поддерживаются только preload. Это в документации: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

Поэтому всегда используйте preload для полиморфных ассоциаций. Для этого существует одна оговорка: вы не можете запрашивать полиморфное объединение в тех случаях, когда клаузулы (что имеет смысл, поскольку полиморфная ассоциация представляет собой несколько таблиц.)