ActiveRecord - запрашивающий полиморфные ассоциации
-
22-08-2019 - |
Вопрос
Я использую полиморфные ассоциации для отслеживания комментариев в моем проекте.Все очень прямолинейные вещи.
Проблема, с которой я сталкиваюсь, заключается в запросе на основе полиморфной ассоциации и присоединении из модели комментариев обратно к ее владельцу.
Итак ...
У меня есть модель комментария
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
И режим тематических форумов:
class ForumTopic < ActiveRecord::Base
has_many :comments, :as => :commentable
end
У меня есть несколько других "комментируемых" моделей, которые сейчас не важны.Все это работает.
То, что я пытаюсь сделать, это найти все комментарии, которые принадлежат теме форума с указанным условием (в данном случае 'featured' == true).
Когда я пытаюсь использовать поисковик, чтобы присоединиться к моделям:
@comments = Comment.find(:all
:joins => :commentable
:conditions => ["forum_topics.featured = ? ", true]
)
Я получаю следующую ошибку:
Не удается с готовностью загрузить полиморфную ассоциацию:комментируемая
Использование AR "включить синтаксис":
@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::Ошибка:Неизвестная таблица "комментарии":ВЫБЕРИТЕ комментарии. ИЗ комментариев forum_topics, ГДЕ (forum_topics.featured = 1 )*
(Здесь вы можете видеть, что синтаксис базового запроса полностью отключен, а соединение вообще отсутствует).
Не уверен, возможно ли вообще то, что я делаю, и есть другие способы достичь требуемого результата, но похоже, что это следует быть выполнимым.
Есть какие-нибудь идеи?Чего-нибудь мне не хватает?
Решение
Аргх!
Я думаю, что нашел проблему.
При присоединении через:
@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
Другие советы
Старый вопрос, но есть более чистый способ достичь этого, установив прямую ассоциацию для определенного типа вместе с полиморфным:
#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
Вам нужно Условное обозначение, плюс Rails 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 } )
Спасибо всем, особенно @Jits, @Peter и @prograils за их комментарии.
Принятое решение не работает, как только вы вводите другую модель, которая имеет ассоциацию с использованием "commentable".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 в качестве одного из ваших условий, но я не думаю, что это лучший способ подойти к этой проблеме.
Я наткнулся на этот пост, и он привел меня к моему решению.Используя commentable_type в качестве одного из моих условий, но вместо этого используя ЛЕВОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ.Таким образом, будут включены темы форума без комментариев.
LEFT OUTER JOIN `comments` ON `comments`.`commentable_id` = `forum_topics`.`id` AND `comments`.`commentable_type` = 'ForumTopic'
Проверено на работу в соответствии Рельсы 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.
Rails по умолчанию не включает полиморфное соединение, но этот драгоценный камень поможет вам с легкостью объединить ваши полиморфные отношения. https://github.com/jameshuynh/polymorphic_join