How do I dynamically build a search block in sunspot?

I recently did this kind of thing using instance_eval to evaluate procs (created elsewhere) in the context of the Sunspot search block.

The advantage is that these procs can be created anywhere in your application yet you can write them with the same syntax as if you were inside a sunspot search block.

Here's a quick example to get you started for your particular case:

def build_sunspot_query(conditions)
  condition_procs = conditions.map{|c| build_condition c}

  Sunspot.search(table_clazz) do
    condition_procs.each{|c| instance_eval &c}

    paginate(:page => page, :per_page => per_page)
  end
end

def build_condition(condition)
  Proc.new do
    # write this code as if it was inside the sunspot search block

    keywords condition['words'], :fields => condition[:field].to_sym
  end
end

conditions = [{words: "tasty pizza", field: "title"},
              {words: "cheap",       field: "description"}]

build_sunspot_query conditions

By the way, if you need to, you can even instance_eval a proc inside of another proc (in my case I composed arbitrarily-nested 'and'/'or' conditions).


Sunspot provides a method called Sunspot.new_search which lets you build the search conditions incrementally and execute it on demand.

An example provided by the Sunspot's source code:

search = Sunspot.new_search do
  with(:blog_id, 1)
end
search.build do
  keywords('some keywords')
end
search.build do
  order_by(:published_at, :desc)
end
search.execute

# This is equivalent to:
Sunspot.search do
  with(:blog_id, 1)
  keywords('some keywords')
  order_by(:published_at, :desc)
end

With this flexibility, you should be able to build your query dynamically. Also, you can extract common conditions to a method, like so:

def blog_facets
  lambda { |s|
    s.facet(:published_year)
    s.facet(:author)
  }
end

search = Sunspot.new_search(Blog)
search.build(&blog_facets)
search.execute