Rails3 ActiveRecord - Fetching all records that have A and B (through a has_many relationship)

醉酒当歌 提交于 2019-12-14 02:38:55

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!