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
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
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
来源:https://stackoverflow.com/questions/18955190/how-to-find-records-whose-has-many-through-objects-include-all-objects-of-some