it-roy-ru.com

Стремительная нагрузка полиморфная

Используя 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)?

92
Victor

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

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

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

  1. ActiveRecord не может построить объединение без дополнительной информации. 
  2. Нет таблицы с именем рецензируемой

Чтобы решить эту проблему, вам нужно явно определить отношения между 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. В базе данных не должно быть таблицы с именем revable.

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

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

181
Sean Hill

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

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

4
seanmorton

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

Вот так:

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 вместо этого. :)

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

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

0
un_gars_la_cour