Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ActiveRecord has_and_belongs_to_many: find models with all given elements

I'm implementing a search system that uses name, tags, and location. There is a has_and_belongs_to_many relationship between Server and Tag. Here's what my search method currently looks like:

def self.search(params)
  @servers = Server.all

  if params[:name]
    @servers = @servers.where "name ILIKE ?", "%#{params[:name]}%"
  end

  if params[:tags]
    @tags = Tag.find params[:tags].split(",")
    # How do I eliminate servers that do not have these tags?
  end

  # TODO: Eliminate those that do not have the location specified in params.
end

The tags parameter is just a comma-separated list of IDs. My question is stated in a comment in the if params[:tags] conditional block. How can I eliminate servers that do not have the tags specified?

Bonus question: any way to speed this up? All fields are optional, and I am using Postgres exclusively.

EDIT

I found a way to do this, but I have reason to believe it will be extremely slow to run. Is there any way that's faster than what I've done? Perhaps a way to make the database do the work?

tags = Tag.find tokens
servers = servers.reject do |server|
    missing_a_tag = false

    tags.each do |tag|
        if server.tags.find_by_id(tag.id).nil?
            missing_a_tag = true
        end
    end

    missing_a_tag
end
like image 500
Blake Beaupain Avatar asked Jun 20 '14 20:06

Blake Beaupain


1 Answers

Retrieve the servers with all the given tags with

if params[:tags]
  tags_ids = params[:tags].split(',')
  @tags = Tag.find(tags_ids) 
  @servers = @servers.joins(:tags).where(tags: {id: tags_ids}).group('servers.id').having("count(*) = #{tags_ids.count}")
end

The group(...).having(...) part selects the servers with all requested tags. If you're looking for servers which have at least one of the tags, remove it.

With this solution, the search is done in a single SQL request, so it will be better than your solution.

like image 134
Baldrick Avatar answered Oct 24 '22 22:10

Baldrick