Rails3 ActiveRecord - 获取具有A和B的所有记录(通过has_many关系)

时间:2011-04-25 14:28:50

标签: sql activerecord has-many ruby-on-rails-3

我有以下模型关系:

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

现在我要加载所有标记为“A”和标记“B”的文章。

我是Rails的新手,自从我做了任何严肃的web-dev / SQL工作已经有几年了但是对于我的生活,我无法弄清楚如何构建一个ActiveRecord查询来执行此操作

在本机SQL中执行此操作的一种方法如下:

    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'

现在,这可能不是最有效的SQL,但它有效。在这么晚的时候,我想不出更好的SQL解决方案。

更新:进一步的调查让我想到了这个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

这似乎更简单(更简洁)SQL,但效率不高。但是,我可以使用此SQL更轻松地构造“find_by_sql”ActiveRecord查询。

如何最好地使用ActiveRecord进行此类查询(最好不要使用SQL)的任何指导都将非常感激。

1 个答案:

答案 0 :(得分:0)

我最终解决了自己的问题。我构建了一个命名范围如下:

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}")
}

所以,现在我可以在我的控制器中执行此操作:

@tagged_articles = Article.tagged('A', 'B', 'C')

@tagged_articles将包含所有标记为“A”,“B”和“C”的文章。