1

我有一个典型的标签和任何对象关系:说

class Tag < ActiveRecord::Base
   attr_accessible :name
   has_many :tagazations
   has_many :projects, :through => :tagazations
end

class Tagazation < ActiveRecord::Base
  belongs_to :project
  belongs_to :tag
  validates :tag_id, :uniqueness => { :scope => :project_id }
end

class Project < ActiveRecord::Base
   has_many :tagazations
   has_many :tags, :through => :tagazations
end

这里没什么特别的:每个项目都由一个或多个标签标记。
该应用程序具有搜索功能:您可以选择某些标签,我的应用程序应该向您显示所有标记有所有提到的标签的项目。所以我得到了一组必要的 tag_ids 然后被这么简单的问题困住了

4

2 回答 2

7

要在一个查询中执行此操作,您需要利用常见的double not exists SQL 查询,该查询本质上确实为所有 Y 找到 X

在您的情况下,您可能会这样做:

class Project < ActiveRecord::Base
  def with_tags(tag_ids)
    where("NOT EXISTS (SELECT * FROM tags
      WHERE NOT EXISTS (SELECT * FROM tagazations
        WHERE tagazations.tag_id = tags.id
        AND tagazations.project_id = projects.id)
      AND tags.id IN (?))", tag_ids)
  end
end

或者,您可以使用计数、分组和拥有,虽然我怀疑第一个版本更快,但可以随意进行基准测试:

def with_tags(tag_ids)
  joins(:tags).select('projects.*, count(tags.id) as tag_count')
    .where(tags: { id: tag_ids }).group('projects.id')
    .having('tag_count = ?', tag_ids.size)
end
于 2013-09-23T09:36:52.780 回答
1

这将是一种方法,尽管绝不是最有效的:

class Project < ActiveRecord::Base
   has_many :tagazations
   has_many :tags, :through => :tagazations

   def find_with_all_tags(tag_names)
     # First find the tags and join with their respective projects
     matching_tags = Tag.includes(:projects).where(:name => tag_names)
     # Find the intersection of these lists, using the ruby array intersection operator &
     matching_tags.reduce([]) {|result, tag| result & tag.projects}
   end
end

里面可能有几个错别字,但你明白了

于 2013-09-23T09:06:03.470 回答