Skip to content

Instantly share code, notes, and snippets.

@steveklabnik
Created September 25, 2013 20:52
Show Gist options
  • Save steveklabnik/6705873 to your computer and use it in GitHub Desktop.
Save steveklabnik/6705873 to your computer and use it in GitHub Desktop.
def self.search(query)
query
.split(" ")
.collect do |query|
query.split(":")
end.inject(self) do |klass, (name, q)|
association = klass.reflect_on_association(name.pluralize.to_sym)
association_table = association.klass.arel_table
if [:has_and_belongs_to_many, :belongs_to].include?(association.macro)
joins(name.pluralize.to_sym).where(association_table["name"].eq(q))
else
all
end
end
end
@baroquebobcat
Copy link

How's it supposed to behave? Could you show me an example?

@baroquebobcat
Copy link

query
  .scan(/(\w+):(\w+)(?:\s+|\Z)/)
  .map {|name, q| [name.pluralize.to_sym, q] }
  .map {|name, q| [klass.reflect_on_association(name.pluralize.to_sym), name, q] }
  .select {|association, name, q| [:has_and_belongs_to_many, :belongs_to].include?(association.macro) }
  .inject(self) do |ar_query, (association, name, q)|   
    ar_query.joins(name).where(association.klass.arel_table["name"].eq(q)
  end

@steveklabnik
Copy link
Author

Sample query is "tag:ruby state:closed"

@baroquebobcat
Copy link

And that's supposed to generate?:

klass.joins(:tags).where("tags.name" => "ruby").joins(:states).where("states.name" => "closed")

@soulcutter
Copy link

Shouldn't you really be injecting a scope?

def self.search(query)
    query
      .split(" ")
      .collect do |query|
        query.split(":")
      end.inject(scoped) do |scope, (name, q)|
        association = reflect_on_association(name.pluralize.to_sym)
        association_table = association.klass.arel_table

        if [:has_and_belongs_to_many, :belongs_to].include?(association.macro)
          scope.joins(name.pluralize.to_sym).where(association_table["name"].eq(q))
        else
          scope
        end
      end
  end

@baroquebobcat
Copy link

Making it a bit smaller:

  • use split limit to ensure a:b:c => a, b:c
  • put it into a hash to avoid building where / joins multiple times
  • use where(Hash) with assoc.field syntax instead of arel nodes.
assoc_name_to_q = query.split(" ")
                       .map{ |t| t.split ':', 2 }
                       .each_with_object({}) do |(name, q), hash|
                          hash[name.pluralize.to_sym] = q
                       end
assoc_name_to_q.delete_if do |name, q|
 ![:has_and_belongs_to_many, :belongs_to].include?(klass.reflect_on_association(name).macro)
end

joins(assoc_name_to_q.keys)
.where(Hash[assoc_name_to_q.map{|assoc_name, q| ["#{assoc_name}.name",q] }])

@steveklabnik
Copy link
Author

@soulcutter, in Rails 4, all just returns a scope. The naming is better that way, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment