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) *
(Здесь вы можете видеть, что синтаксис базового запроса полностью отключен и соединение вообще отсутствует).
Не уверен, что то, что я делаю, даже возможно, и есть другие способы достижения требуемого результата, но похоже, что это должно быть выполнимо.
Любые идеи?
Что-нибудь мне не хватает?
Ответы
Ответ 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.