Швидке навантаження поліморфне


104

Використовуючи 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)?

Відповіді:


207

Я думаю, що ваші моделі виглядають так:

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. У базі даних не повинно бути таблиці, яка називається переглядається.

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

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


6
Насправді я все-таки скористався цим, не заявляючи нічого більше:@reviews = @user.reviews.joins("INNER JOIN shops ON (reviewable_type = 'Shop' AND shops.id = reviewable_id AND shops.shop_type = '" + type + "')").includes(:user, :reviewable => :photos)
Віктор

1
Це тому, що :reviewableє Shop. Фотографії належать до магазину.
Віктор

6
працював у rails4, але дасть попередження про депресію, він повинен використовувати стиль, як has_many: spam_comments, -> {де спам: true}, class_name: 'коментар'. Тож у rails4, буде last_to: shop, -> {where ("reviews.reviewable_type = 'Shop'")}, Foreign_key: 'reviewable_id'. Але будьте обережні, Review.includes (: магазин) призведе до помилок, він повинен додавати в оренду той, де застереження.
raykin

49
Є також іноземний тип, який працював на мене для подібної проблеми:belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
A5308Y

14
Під час завантаження, reviewsвключаючи невдале завантаження асоційованого shopкоду, Review.includes(:shop)визначення last_to belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id' кидає вислів про помилку missing FROM-clause entry for table "reviews". Я виправив це шляхом оновлення визначення last_to таким чином: belongs_to :shop, -> { joins(:reviews) .where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
Jignesh Gohel

12

Якщо ви отримуєте ActiveRecord :: EagerLoadPolymorphicError, це тому, що includesвирішено зателефонувати, eager_loadколи поліморфні асоціації підтримуються лише preload. Це в документації тут: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

Тому завжди використовуйте preloadдля поліморфних асоціацій. Для цього є одне застереження: ви не можете запитувати поліморфну ​​асоціацію, де клаузи (що має сенс, оскільки поліморфна асоціація являє собою кілька таблиць.)


Я бачу, що це єдиний метод, який не зафіксований у посібниках: guides.rubyonrails.org/…
MSC

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

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


0

Як додаток відповідь вгорі, яка є відмінною, ви також можете вказати :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 замість цього. :)


5
Невідомий ключ:: умови. Дійсні ключі:: ім'я класу,: клас,: іноземний ключ,: валідація,: автоматичне збереження,: залежний,: основний_кейк,: зворотний_файв:: необхідний,: іноземний_тип,: поліморфний,: дотик
,:
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.