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.
- ActiveRecord ne peut pas créer la jointure sans informations supplémentaires.
- Il n'y a pas de tableau appelé 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 doit pas y avoir de table appelée révisable dans la base de données.
Je pense que cela est plus facile et plus flexible que de définir explicitement l' join
intervalle Review
et Shop
car cela vous permet de charger rapidement en plus d'interroger des champs connexes.
La raison pour laquelle cela est nécessaire est qu'ActiveRecord ne peut pas créer une jointure basée uniquement sur révisable, car plusieurs tables représentent l'autre extrémité de la jointure et SQL, pour autant que je sache, ne vous permet pas de rejoindre 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 terminer la jointure.
@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)
:reviewable
c'estShop
. Les photos appartiennent à la boutique.belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
reviews
y compris le chargement hâtif dushop
code d'utilisation associé,Review.includes(:shop)
la définition appartient_tobelongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
renvoie une erreur en disantmissing FROM-clause entry for table "reviews"
. Je l'ai corrigé en mettant à jour la définition de Splend_to de la manière suivante:belongs_to :shop, -> { joins(:reviews) .where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
Si vous obtenez une ActiveRecord :: EagerLoadPolymorphicError, c'est parce que j'ai
includes
décidé d'appelereager_load
lorsque les associations polymorphes ne sont prises en charge que parpreload
. C'est dans la documentation ici: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.htmlUtilisez donc toujours
preload
pour les associations polymorphes. Il y a une mise en garde à cela: vous ne pouvez pas interroger l'assocition polymorphe dans les clauses where (ce qui est logique, car l'association polymorphe représente plusieurs tables.)la source
Lorsque vous utilisez des fragments SQL avec WHERE, des références sont nécessaires pour rejoindre votre association.
la source
En complément la réponse en haut, ce qui est excellent, 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 vous obtenez des erreurs de table non définies.Ainsi:
Sans l'
:include
option, si vous accédez simplement à l'associationreview.shop
dans l'exemple ci-dessus, vous obtiendrez une erreur UndefinedTable (testée dans Rails 3, pas 4) car l'association fera l'affaireSELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' )
.L'
:include
option forcera un JOIN à la place. :)la source