web-dev-qa-db-fra.com

ActiveRecord - interrogation des associations polymorphes

J'utilise des associations polymorphes pour suivre les commentaires dans mon projet. Tous les trucs très simples.

Le problème que j'ai est dans l'interrogation basée sur l'association polymorphe et la jonction du modèle de commentaire à son propriétaire.

Alors ...

J'ai un modèle de commentaire

class Comment < ActiveRecord::Base
  belongs_to :commentable, :polymorphic => true
end

Et un mode ForumTopics:

class ForumTopic < ActiveRecord::Base
  has_many :comments, :as => :commentable
end

J'ai plusieurs autres modèles "commentables" qui ne sont pas importants pour le moment. Tout cela fonctionne.

Ce que j'essaie de faire, c'est de trouver tous les commentaires qui appartiennent à un ForumTopic avec une condition spécifiée (dans ce cas, "en vedette" == vrai).

Lorsque j'essaie d'utiliser un Finder pour rejoindre les modèles:

@comments = Comment.find(:all 
            :joins => :commentable
            :conditions => ["forum_topics.featured = ? ", true] 
            )

Je reçois l'erreur suivante:

Impossible de charger avec impatience l'association polymorphe: commentable

Utilisation de l'AR "include syntax":

@comments = Comment.find(:all 
            :include => :forum_topics
            :conditions => ["forum_topics.featured = ? ", true] 
            )

retour:

L'association nommée 'forum_topics' est introuvable; peut-être l'avez-vous mal orthographié?

Si j'essaie de me joindre à un nom de table au lieu du nom de l'association (chaîne au lieu de symbole):

@comments = Comment.find(:all,
            :joins => "forum_topics",
            :conditions => ["forum_topics.featured = ? ", true] 
            )

Je vois:

Mysql :: Erreur: commentaires de la table inconnue: commentaires SELECT. FROM commentaires forum_topics WHERE (forum_topics.featured = 1) *

(Vous pouvez voir ici que la syntaxe de la requête sous-jacente est totalement désactivée et que la jointure est complètement manquante).

Je ne sais pas si ce que je fais est même possible, et il existe d'autres façons d'obtenir le résultat souhaité, mais il semble que devrait être faisable.

Des idées? Quelque chose me manque?

43
Toby Hede

Argh!

Je pense avoir trouvé le problème.

Lors de l'adhésion via:

@comments = Comment.find(:all,
        :joins => "forum_topics",
        :conditions => ["forum_topics.featured = ? ", true] 
        )

Vous avez besoin de la jointure entière!

:joins => "INNER JOIN forum_topics ON forum_topics.id = comments.commentable_id",

Voir le toujours génial: http://guides.rubyonrails.org/active_record_querying.html#joining-tables

32
Toby Hede

Une vieille question, mais il existe un moyen plus propre d'y parvenir en créant une association directe pour le type spécifique avec le polymorphe:

#comment.rb
class Comment < ActiveRecord::Base

  belongs_to :commentable, polymorphic: true
  belongs_to :forum_topics, -> { where( comments: { commentable_type: 'ForumTopic' } ).includes( :comments ) }, foreign_key: 'commentable_id'

  ...

end

Vous pouvez alors passer :forum_topics à includes supprimant la nécessité d'une jointure en désordre:

@comments = Comment
  .includes( :forum_topics )
  .where( :forum_topics => { featured: true } )

Vous pouvez ensuite nettoyer cela en déplaçant la requête dans une étendue:

#comment.rb
class Comment < ActiveRecord::Base

  ...

  scope :featured_topics, -> { 
    includes( :forum_topics )
    .where( :forum_topics => { featured: true } ) 
  }

  ...

end

Vous laissant être capable de faire simplement

@comments = Comment.featured_topics
28
Sam Peacey

Vous avez besoin d'un conditionnel, plus Rails 3+

Beaucoup de gens y ont fait allusion dans les réponses et les commentaires, mais je sentais que les gens, y compris moi-même, se feraient trébucher si j'atterrissais ici et ne lisais pas suffisamment.

Alors, voici la bonne réponse, y compris le conditionnel qui est absolument nécessaire.

@comments = Comment.joins( "INNER JOIN forum_topics ON comments.commentable_id = forum_topics.id" )
                   .where( comments:     { commentable_type: 'ForumTopic' } )
                   .where( forum_topics: { featured:         true         } )

Merci à tous, en particulier @Jits, @Peter et @prograils pour leurs commentaires.

19
Joshua Pinter

La solution acceptée ne fonctionne pas une fois que vous avez introduit un autre modèle qui a une association utilisant "commentable". commentable_id n'est pas unique et vous commencerez donc à récupérer les mauvais commentaires.

Par exemple:

Vous décidez d'ajouter un modèle de news qui accepte les commentaires ...

class News < ActiveRecord::Base
   has_many :comments, :as => :commentable
end

Vous pouvez maintenant récupérer deux enregistrements si vous avez fait un commentaire sur un forum_topic avec un identifiant de 1 et un article de nouvelles avec un identifiant de 1 en utilisant votre requête:

:joins => "INNER JOIN forum_topics ON forum_topics.id = comments.commentable_id"

Vous pourriez probablement résoudre le problème en fournissant un type commentable comme l'une de vos conditions, mais je ne pense pas que ce soit la meilleure façon d'aborder ce problème.

17
Peter

Vérifié pour fonctionner sous Rails 5 :

Solution 1:

@comments = Comment
              .where(commentable_type: "ForumTopic")
              .joins("INNER JOIN forum_topics ON comments.commentable_id = forum_topics.id")
              .where(forum_topics: {featured: true})
              .all

ou

Solution 2:

@comments = Comment
              .joins("INNER JOIN forum_topics ON comments.commentable_id = forum_topics.id AND comments.commentable_type = 'ForumTopic'")
              .where(forum_topics: {featured: true}).all

Faites attention à la syntaxe SQL brute: aucun backticks n'est autorisé. Voir http://guides.rubyonrails.org/active_record_querying.html#joining-tables .

Personnellement, je préfère la solution 1 car elle contient moins de syntaxe SQL brute.

11
prograils

Je suis tombé sur ce post et cela m'a conduit à ma solution. Utiliser le commentable_type comme une de mes conditions mais utiliser un LEFT OUTER JOIN à la place. De cette façon, les sujets du forum sans commentaires seront inclus.

LEFT OUTER JOIN `comments` ON `comments`.`commentable_id` = `forum_topics`.`id` AND `comments`.`commentable_type` = 'ForumTopic'
10
Chris Barnes

Rails n'inclut pas de jointure polymorphe par défaut, mais ce joyau vous aiderait à rejoindre facilement votre relation polymorphe. https://github.com/jameshuynh/polymorphic_join

0
Damian Sia