I am using polymorphic associations to track Comments in my project. All very straight forward stuff.
The problem I have is in querying based on the polymorphic asso
I came across this post and it lead me to my solution. Using the commentable_type as one of my conditions but using a LEFT OUTER JOIN instead. That way forum topics without comments will be included.
LEFT OUTER JOIN `comments` ON `comments`.`commentable_id` = `forum_topics`.`id` AND `comments`.`commentable_type` = 'ForumTopic'
A lot of people alluded to it in the answers and comments but I felt that people, including myself, would get tripped up if they landed here and didn't read thoroughly enough.
So, here's the proper answer, including the conditional that is absolutely necessary.
@comments = Comment.joins( "INNER JOIN forum_topics ON comments.commentable_id = forum_topics.id" )
.where( comments: { commentable_type: 'ForumTopic' } )
.where( forum_topics: { featured: true } )
Thanks to all, especially @Jits, @Peter, and @prograils for their comments.
The accepted solution does not work once you introduce another model that has an association using "commentable". commentable_id is not unique and therefore you'll start retrieving the wrong comments.
For example:
You decide to add a news model that accepts comments...
class News < ActiveRecord::Base
has_many :comments, :as => :commentable
end
Now you may get two records back if you made a comment on a forum_topic with an id of 1 and a news article with an id of 1 using your query:
:joins => "INNER JOIN forum_topics ON forum_topics.id = comments.commentable_id"
You could probably solve the problem by supplying a commentable_type as one of your conditions, but I don't think that's the best way to approach this issue.
An old question, but there is a cleaner way of achieving this by setting up a direct association for the specific type along with the polymorphic:
#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
You are then able to pass :forum_topics
to includes
getting rid of the need for a messy join:
@comments = Comment
.includes( :forum_topics )
.where( :forum_topics => { featured: true } )
You could then further clean this up by moving the query into a scope:
#comment.rb
class Comment < ActiveRecord::Base
...
scope :featured_topics, -> {
includes( :forum_topics )
.where( :forum_topics => { featured: true } )
}
...
end
Leaving you to be able to simply do
@comments = Comment.featured_topics
Checked to work under 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
or
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
Pay attention to the raw SQL syntax: no backticks are allowed. See http://guides.rubyonrails.org/active_record_querying.html#joining-tables .
I personally prefer Solution 1 as it contains fewer raw SQL syntax.
Argh!
I think I found the problem.
When joining via:
@comments = Comment.find(:all,
:joins => "forum_topics",
:conditions => ["forum_topics.featured = ? ", true]
)
You need the whole join!
:joins => "INNER JOIN forum_topics ON forum_topics.id = comments.commentable_id",
See the ever-awesome: http://guides.rubyonrails.org/active_record_querying.html#joining-tables