ActiveRecord-querying polymorphic associations

Używam skojarzeń polimorficznych do śledzenia komentarzy w moim projekcie. Wszystko bardzo prosto.

Problem, który mam polega na zapytaniu w oparciu o skojarzenie polimorficzne i połączeniu z modelu komentarza z powrotem do jego właściciela.

Więc ...

Mam Model komentarza

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

I tryb ForumTopics:

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

Mam kilka innych" komentowalnych " modeli, które nie są teraz ważne. To wszystko działa.

To, co próbuję zrobić, to znaleźć wszystkie komentarze, które należą do ForumTopic z określonym warunkiem (w tym przypadku 'featured' = = true).

Kiedy próbuję użyć Findera, aby dołączyć do modeli:

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

Otrzymuję następujący błąd:

nie można z zapałem załadować asocjacji polimorficznej: komentować

Użycie AR "include syntax":

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

Zwraca:

Stowarzyszenie o nazwie "forum_topics" nie zostało znalezione; być może źle napisałeś to?

Jeśli spróbuję połączyć się z nazwą tabeli zamiast nazwy asocjacji (łańcuch zamiast symbolu):

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

Widzę:

Mysql:: Error: Unknown table 'comments': SELECT comments. FROM comments forum_topics WHERE (forum_topics.featured = 1) *

(możesz zobaczyć tutaj, że składnia podstawowego zapytania jest całkowicie wyłączona, a join całkowicie brakuje).

Nie wiem, czy to, co robię, jest w ogóle możliwe, a tam są inne sposoby osiągnięcia wymaganego rezultatu, ale wydaje się, że powinno być wykonalne. Jakieś pomysły? Coś mi umknęło?
Author: Undistraction, 2009-03-25

6 answers

Argh!

Chyba znalazłem problem.

Podczas łączenia przez:

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

Potrzebujesz całego połączenia!

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

Zobacz the ever-awesome: http://guides.rubyonrails.org/active_record_querying.html#joining-tables

 26
Author: Toby Hede,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-25 03:59:40

Stare pytanie, ale jest czystszy sposób na osiągnięcie tego poprzez ustanowienie bezpośredniego związku dla określonego typu wraz z polimorficznym:

#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

Następnie możesz przejść :forum_topics do includes pozbycie się potrzeby niechlujnego łączenia:

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

Możesz następnie dalej to wyczyścić, przenosząc zapytanie do zakresu:

#comment.rb
class Comment < ActiveRecord::Base

  ...

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

  ...

end

Zostawiając cię, abyś mógł po prostu zrobić

@comments = Comment.featured_topics
 23
Author: Sam Peacey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-10 07:31:02

Przyjęte rozwiązanie nie działa po wprowadzeniu innego modelu, który ma skojarzenie z "komentowalnym". commentable_id nie jest unikalny i dlatego zaczniesz pobierać złe komentarze.

Na przykład:

Decydujesz się dodać model newsa, który akceptuje komentarze...

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

Teraz możesz dostać dwa rekordy z powrotem, jeśli zrobiłeś komentarz na forum_topic o id 1 i artykuł newsa o id 1 używając zapytania:

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

Prawdopodobnie mógłbyś Rozwiąż problem, dostarczając commentable_type jako jeden z warunków, ale myślę, że nie jest to najlepszy sposób podejścia do tego problemu.

 15
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-11 14:32:47

Potrzebujesz warunkowego, Plus Rails 3 +

Wiele osób nawiązywało do tego w odpowiedziach i komentarzach, ale czułem, że ludzie, w tym ja, potkną się, jeśli wyląduję tutaj i nie przeczytam wystarczająco dokładnie.

Oto właściwa odpowiedź, w tym warunkowa, która jest absolutnie niezbędna.

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

Rails 5 +

Dla Rails 5+, musisz usunąć tylne kleszcze w łańcuchu zapytania:

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

Dzięki wszystkim, szczególnie @Jits, @ Peter i @prograils za komentarze.

 13
Author: Joshua Pinter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-25 16:55:46

Natknąłem się na ten post i to doprowadziło mnie do mojego rozwiązania. Używam commentable_type jako jednego z moich warunków, ale zamiast tego używam lewego zewnętrznego łącznika. W ten sposób zostaną uwzględnione tematy forum bez komentarzy.

LEFT OUTER JOIN `comments` ON `comments`.`commentable_id` = `forum_topics`.`id` AND `comments`.`commentable_type` = 'ForumTopic'
 10
Author: Chris Barnes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-23 19:07:45

Sprawdzone do pracy pod Rails 5:

Rozwiązanie 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

Lub

Rozwiązanie 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

Zwróć uwagę na surową składnię SQL: żadne backticki nie są dozwolone. Zobacz http://guides.rubyonrails.org/active_record_querying.html#joining-tables .

Osobiście wolę rozwiązanie 1, ponieważ zawiera mniej surowej składni SQL.

 4
Author: prograils,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-29 06:14:49