Подтвердить что ты не робот

ActiveRecord - запрос полиморфных ассоциаций

Я использую полиморфные ассоциации для отслеживания комментариев в моем проекте. Все очень прямо вперед.

Проблема, которую я имею, заключается в запросе на основе полиморфной ассоциации и присоединении от модели Comment обратно к ее владельцу.

Итак...

У меня есть модель комментария

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

И режим ForumTopics:

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

У меня есть несколько других "замечательных" моделей, которые сейчас не важны. Все это работает.

То, что я пытаюсь сделать, это найти все комментарии, относящиеся к ForumTopic с заданным условием (в данном случае "featured" == true).

Когда я пытаюсь использовать поисковик для присоединения к моделям:

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

Я получаю следующую ошибку:

Невозможно с нетерпением загрузить полиморфную ассоциацию: комментарий

Использование синтаксиса AR "include":

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

возвращает:

Ассоциация с именем "forum_topics" не найдена; возможно, вы его опечалили?

Если я попытаюсь присоединиться к имени таблицы вместо имени ассоциации (строка вместо символа):

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

Я вижу:

Mysql:: Ошибка: Неизвестные таблицы 'comments': комментарии SELECT. FROM comments forum_topics WHERE (forum_topics.featured = 1) *

(Здесь вы можете видеть, что синтаксис базового запроса полностью отключен и соединение вообще отсутствует).

Не уверен, что то, что я делаю, даже возможно, и есть другие способы достижения требуемого результата, но похоже, что это должно быть выполнимо.

Любые идеи? Что-нибудь мне не хватает?

4b9b3361

Ответ 1

Argh!

Я думаю, что нашел проблему.

При подключении через:

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

Вам нужно целое соединение!

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

Посмотрите на всеохватывающие: http://guides.rubyonrails.org/active_record_querying.html#joining-tables

Ответ 2

Старый вопрос, но есть более чистый способ достижения этого, установив прямую связь для определенного типа вместе с полиморфным:

#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

Затем вы можете передать :forum_topics в includes, избавившись от необходимости беспорядочного соединения:

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

Затем вы можете очистить его, перемещая запрос в область видимости:

#comment.rb
class Comment < ActiveRecord::Base

  ...

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

  ...

end

Предоставляя вам возможность просто делать

@comments = Comment.featured_topics

Ответ 3

Принятое решение не работает, как только вы вводите другую модель, которая имеет ассоциацию, используя "комментарий". commentable_id не уникален, и поэтому вы начнете получать неправильные комментарии.

Например:

Вы решили добавить модель новостей, которая принимает комментарии...

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

Теперь вы можете вернуть две записи, если вы сделали комментарий на forum_topic с идентификатором 1 и новостной статьей с идентификатором 1, используя ваш запрос:

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

Возможно, вы, возможно, решите проблему, предоставив commentable_type в качестве одного из ваших условий, но я не думаю, что лучший способ подойти к этой проблеме.

Ответ 4

Я столкнулся с этим сообщением, и это привело меня к моему решению. Использование commentable_type как одного из моих условий, но вместо этого используйте LEFT OUTER JOIN. Таким образом будут включены темы форума без комментариев.

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

Ответ 5

Вам нужны условные, плюсовые рельсы 3 +

Многие люди ссылались на него в ответах и ​​комментариях, но я чувствовал, что люди, включая меня, будут спотыкаться, если я приземлюсь здесь и не буду читать достаточно тщательно.

Итак, здесь необходим правильный ответ, включая условие абсолютно.

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

Рельсы 5 +

Для Rails 5+ вам нужно удалить обратные тики в строке запроса:

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

Спасибо всем, особенно @Jits, @Peter и @prograils за их комментарии.

Ответ 6

Проверено, работает ли Rails 5:

Решение 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

или

Решение 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

Обратите внимание на исходный синтаксис SQL: никаких обратных ссылок не допускается. См. http://guides.rubyonrails.org/active_record_querying.html#joining-tables.

Я лично предпочитаю решение 1, поскольку оно содержит меньше синтаксиса SQL.