Charge polymorphe

En utilisant Rails 3.2, quel est le problème avec ce code?

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

Il soulève cette erreur:

Impossible de charger avec impatience l’association polymorphe: révisable

Si je supprime le reviewable.shop_type = ? condition, ça marche.

Comment puis-je filtrer sur le type reviewable_type et reviewable.shop_type (qui est en fait shop.shop_type )?

Je suppose que vos modèles ressemblent à ceci:

 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 

Vous ne pouvez pas effectuer cette requête pour plusieurs raisons.

  1. ActiveRecord ne parvient pas à créer la jointure sans informations supplémentaires.
  2. Il n'y a pas de table appelée révisable

Pour résoudre ce problème, vous devez définir explicitement la relation entre Review et 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 

Ensuite, vous pouvez interroger comme ceci:

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

Notez que le nom de la table est shops et non reviewable . Il ne devrait pas y avoir de table appelée révisable dans la firebase database.

Je pense que cela est plus facile et plus souple que la définition explicite de la join entre Review et Shop car elle vous permet de charger les requêtes en plus des champs associés.

La raison en est que ActiveRecord ne peut pas générer de jointure en fonction du seul révisable, car plusieurs tables représentent l'autre extrémité de la jointure, et SQL, à ma connaissance, ne vous permet pas de joindre une table nommée par la valeur stockée. dans une colonne. En définissant la relation supplémentaire belongs_to :shop , vous donnez à ActiveRecord les informations nécessaires pour compléter la jointure.

Si vous obtenez un ActiveRecord :: EagerLoadPolymorphicError, c’est parce que includes décidé d’appeler eager_load lorsque les associations polymorphes ne sont sockets en charge que par le preload . C’est dans la documentation ici: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

Utilisez donc toujours le preload pour les associations polymorphes. Il y a une mise en garde pour ceci: vous ne pouvez pas interroger l’assocition polymorphe dans les clauses where (ce qui est logique puisque l’association polymorphe représente plusieurs tables).

En tant qu’addendum, la réponse en haut, qui est excellente, vous pouvez également spécifier :include sur l’association si pour une raison quelconque la requête que vous utilisez n’inclut pas la table du modèle et que vous obtenez des erreurs de table non définies.

Ainsi:

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

Sans l’option :include , si vous accédez simplement à l’association review.shop dans l’exemple ci-dessus, vous obtiendrez une erreur UndefinedTable (testée dans Rails 3, et non 4) car l’association fera SELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' ) .

L’option :include forcera un JOIN à la place. 🙂

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

Lorsque vous utilisez des fragments SQL avec WHERE, des références sont nécessaires pour rejoindre votre association.