Question

I got a typical tag and whatever-object relation: say

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

nothing special here: each project is tagged by one or multiple tags.
The app has a feature of search: you can select the certain tags and my app should show you all projects which tagged with ALL mentioned tags. So I got an array of the necessary tag_ids and then got stuck with such easy problem

Was it helpful?

Solution

To do this in one query you'd want to take advantage of the common double not exists SQL query, which essentially does find X for all Y.

In your instance, you might do:

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

Alternatively, you can use count, group and having, although I suspect the first version is quicker but feel free to benchmark:

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

OTHER TIPS

This would be one way of doing it, although by no means the most efficient:

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

There may be a couple of typos in there, but you get the idea

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top