Rails3 ActiveRecord - выборка всех записей, имеющих A и B (через отношение has_many) - PullRequest
1 голос
/ 25 апреля 2011

У меня есть следующие модельные отношения:

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, но не более эффективный. Тем не менее, я могу, вероятно, легче построить запрос ActiveRecord "find_by_sql", используя этот SQL.

Будем весьма благодарны за любые рекомендации о том, как лучше всего выполнить такой запрос с ActiveRecord (желательно без использования SQL).

1 Ответ

0 голосов
/ 25 апреля 2011

Я решил свою проблему.Я создал именованную область видимости следующим образом:

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».

...