126 votes

Eager load polymorphe

En utilisant Rails 3.2, qu'est-ce qui ne va pas avec ce code ?

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

Elle soulève cette erreur :

Impossible de charger rapidement l'association polymorphe :reviewable

Si je retire le reviewable.shop_type = ? condition, cela fonctionne.

Comment puis-je filtrer sur la base du reviewable_type y reviewable.shop_type (qui est en fait shop.shop_type ) ?

240voto

Sean Hill Points 7500

Je pense 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 faire cette requête pour plusieurs raisons.

  1. ActiveRecord est incapable de construire la jointure sans informations supplémentaires.
  2. Il n'y a pas de table appelée reviewable

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

Vous pouvez alors faire une requête comme ceci :

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

Remarquez que le nom de la table est shops et non reviewable . Il ne devrait pas y avoir de table appelée reviewable dans la base de données.

Je pense que c'est plus facile et plus souple que de définir explicitement l'option de l'utilisateur. join entre Review y Shop puisqu'il vous permet d'effectuer un chargement rapide en plus de l'interrogation par champs connexes.

La raison pour laquelle cela est nécessaire est qu'ActiveRecord ne peut pas construire une jointure basée uniquement sur reviewable, puisque plusieurs tables représentent l'autre extrémité de la jointure, et SQL, pour autant que je sache, ne 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 dont il a besoin pour effectuer la jointure.

26voto

seanmorton Points 181

Si vous obtenez une erreur ActiveRecord::EagerLoadPolymorphicError, c'est que includes a décidé d'appeler eager_load lorsque des associations polymorphes ne sont supportées que par des preload . C'est dans la documentation ici : http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

Donc, utilisez toujours preload pour les associations polymorphes. Il y a une mise en garde à ce sujet : vous ne pouvez pas interroger l'association polymorphe dans les clauses where (ce qui est logique, puisque l'association polymorphe représente plusieurs tables).

6voto

Tyler Klose Points 33

Pas assez de réputation pour commenter afin de prolonger la réponse de Moses Lucas ci-dessus, j'ai dû faire une petite modification pour que cela fonctionne dans Rails 7 car je recevais l'erreur suivante :

ArgumentError: Unknown key: :foreign_type. Valid keys are: :class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading, :autosave, :required, :touch, :polymorphic, :counter_cache, :optional, :default

Au lieu de belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'

Je suis allé avec belongs_to :shop, class_name: 'Shop', foreign_key: 'reviewable_id'

La seule différence ici est de changer foreign_type: a class_name: !

1voto

Moses Lucas Points 29

Ceci a fait le travail pour moi

  belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'

0voto

serggl Points 71

Je crains que la réponse précédente ne soit pas tout à fait acceptable. Cela ne fonctionnera pas si vous ne mettez pas where(shops: {shop_type: 'cafe'}) dans votre requête.

Je n'ai pas trouvé de solution facile à ce problème pour moi-même...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X