问题
I have the following model relationships:
class Article < ActiveRecord::Base
has_many :tags, :through => :article_tags
end
class ArticleTag < ActiveRecord::Base
belongs_to :tag
belongs_to :article
end
class Tag < ActiveRecord::Base
has_many :articles, :through => :article_tags
end
Now I want to load all the Articles that are tagged with both tag "A" and tag "B".
I'm pretty new to Rails and it has been a few years since I did any serious web-dev/SQL work but for the life of me I can't figure out how one would construct an ActiveRecord query to do this.
One way to do it in native SQL would be as follows:
SELECT a.* FROM articles a
INNER JOIN article_tags at ON at.article_id = a.id
INNER JOIN tags t ON at.tag_id = t.id
WHERE t.name = 'A'
intersect
SELECT a.* from articles a
INNER JOIN article_tags at ON at.article_id = a.id
INNER JOIN tags t ON at.tag_id = t.id
WHERE t.name = 'B'
Now that might not be the most efficient SQL but it works. At this late hour I can't think of a better solution in SQL.
Update: Further investigation has lead me to this SQL:
SELECT a.* FROM article_tags at, articles a, tags t
WHERE at.tag_id = t.id
AND (t.name = 'A' OR t.name = 'B')
AND a.id = at.vehicle_id
GROUP BY a.id HAVING COUNT(a.id) = 2
This seems like simpler (less verbose) SQL but in no more efficient. However, I can probably more easily construct a "find_by_sql" ActiveRecord query using this SQL.
Any guidance on how to best do this sort of query with ActiveRecord (preferably without resorting to SQL) would be greatly appreciated.
回答1:
I ended up solving my own problem. I constructed a named scope as follows:
scope :tagged, lambda { |tag, *tags|
tags = tags.unshift(*tag)
joins(:tags).
where("lower(tags.name) = '" + tags.uniq.collect{ |t| t.to_s.downcase }.join("' OR lower(tags.name) = '") + "'").
group("articles.id").
having("count(articles.id) = #{tags.count}")
}
So, now I can do this in my controllers:
@tagged_articles = Article.tagged('A', 'B', 'C')
And @tagged_articles
will include all the articles tagged with all of the tags 'A', 'B', and 'C'.
来源:https://stackoverflow.com/questions/5779354/rails3-activerecord-fetching-all-records-that-have-a-and-b-through-a-has-many