Rails3 - отношения many_to_many и цепочка областей - PullRequest
2 голосов
/ 22 мая 2011

Допустим, у меня есть связь many_to_many между статьями и тегами

class ArticleTag < ActiveRecord::Base
  belongs_to :article
  belongs_to :tag
end

class Tag < ActiveRecord::Base
  has_many :article_tags
  has_many :articles, :through => :article_tags
end

class Article < ActiveRecord::Base
  has_many :article_tags
  has_many :tags, :through => :article_tags

  named_scope :tagged, lambda { |id| joins(:tags).where("tags.id = ?", id) }
end

Статья имеет помеченную область видимости, которая, как следует из названия, позволяет мне извлекать статьи, помеченные определенным тегом

Меня беспокоит следующее:

$ a = Article.create
 => #<Article id: 3, created_at: "2011-05-22 13:54:02", updated_at: "2011-05-22 13:54:02"> 
$ t1 = Tag.create
 => #<Tag id: 4, created_at: "2011-05-22 13:54:07", updated_at: "2011-05-22 13:54:07"> 
$ t2 = Tag.create
 => #<Tag id: 5, created_at: "2011-05-22 13:54:11", updated_at: "2011-05-22 13:54:11"> 
$ a.tags << t1
 => [#<Tag id: 4, created_at: "2011-05-22 13:54:07", updated_at: "2011-05-22 13:54:07">] 
$ a.tags << t2
 => [#<Tag id: 4, created_at: "2011-05-22 13:54:07", updated_at: "2011-05-22 13:54:07">, #<Tag id: 5, created_at: "2011-05-22 13:54:11", updated_at: "2011-05-22 13:54:11">] 
$ Article.tagged(t1.id)
 => [#<Article id: 3, created_at: "2011-05-22 13:54:02", updated_at: "2011-05-22 13:54:02">] 
$ Article.tagged(t2.id)
 => [#<Article id: 3, created_at: "2011-05-22 13:54:02", updated_at: "2011-05-22 13:54:02">] 
$ Article.tagged(t1.id).tagged(t2.id)
 => [] 

Если статья помечена двумя тегами, объединение соответствующих областей не позволяет ее извлекать. Это предполагаемое поведение? Если это так, как я должен изменить свой код, чтобы эта последняя строка не возвращала пустой массив?

PS: вот сгенерированный SQL.

SELECT \"articles\".* FROM \"articles\" INNER JOIN \"article_tags\" ON \"articles\".\"id\" = \"article_tags\".\"article_id\" INNER JOIN \"tags\" ON \"tags\".\"id\" = \"article_tags\".\"tag_id\" WHERE (tags.id = 4) AND (tags.id = 5)

Ответы [ 3 ]

1 голос
/ 22 мая 2011

Хотя я не знаю, почему встроенная цепочка не работает, вот решение:

В вашей Article модели

def self.tagged_by_one_in(*ids)
  return [] if ids.nil?
  self.joins(:tags).where("tags.id in (?)", ids).group(:article_id)
end

def self.tagged_by_all_in(*ids)
  return [] if ids.nil?
  #sorry raw sql there, found no Rails way.
  self.find_by_sql("select * from articles where id in (select article_id from article_tags where tag_id in (" + ids * ", " + ") group by article_id having count(*) = " + ids.size.to_s + ")" )
end

В консоли вы можете вызвать эти методы:

Article.tagged_by_one_in(1,2)
Article.tagged_by_all_in(1,2)
0 голосов
/ 23 мая 2011

Хорошо, я нашел решение.Хитрость заключается в том, чтобы вручную создавать объединения и добавлять уникальный идентификатор в некоторых местах, чтобы избежать конфликтов имен.

named_scope :tagged, lambda { |id| uid = rand(36**8).to_s(36); joins("INNER JOIN article_tags AS at_#{uid} ON (articles.id = at_#{uid}.article_id) INNER JOIN tags AS tags_#{uid} ON (tags_#{uid}.id = at_#{uid}.tag_id)").where("tags_#{uid}.id = ?",id) }
0 голосов
/ 22 мая 2011

Попробуйте это

В статье .rb модель

named_scope :tagged, lambda { |ids| joins(:tags).where("tags.id in (?)", ids) }

В консоли

$ Article.tagged([t1.id,t2.id])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...