Model Specific Formatted Search Results Using Thinking Sphinx

Having recently implemented Thinking Sphinx on one of my web sites, I thought it would be cool to be able to search every indexed model. With Thinking Sphinx, it’s easy to have a bunch of different classes returned in the results. The tougher part is displaying them in a way that is organized (although admittedly not very DRY).

Let’s start out with the controller method for search results (app/controllers/search_controller.rb):

  def result
    if !params[:model].blank?
      @model = params[:model]
    else
      @model = "ThinkingSphinx"
    end
    @query = params[:search][:query]
   
    @results = Search.model_search(@model, @query)
  end

Also, let’s make sure we define the actual search model. I define it in a separate search library (lib/search.rb). This is the relevant snippet of code:

  def self.model_search(model, keywords, var = {})
    @search_options = { :page => var[:page] || 1,
                          :per_page => 15 }

    @search_options.merge!( :order => "@relevance DESC",
                            :sort_mode => :expr,
                            :sort_by => "@weight * @weight")

    model.constantize.search(keywords, @search_options)
  end

Assuming that we are working with the all models search, where the above model is ThinkingSphinx, let’s iterate over the search results with this code in the view (app/views/search/results.html.erb):

< % if @results.total_entries > 0 %>
<div id="localLocationList">
    <ul class="list">
    < % @results.each do |result| %>
        <li>
            < %= display_search_result(result) %>
        </li>
    < % end %>
    </ul>
    <div class="clear"></div>
</div>
< % end %>

And the view helper (app/helpers/search_helper.rb):

def display_search_result(result)
  eval "render :partial => '#{result.class.to_s.downcase.pluralize}/result',
    :locals => { :result => result }"

end

The interesting thing about this bit of code is the eval. The eval on each iteration decides which search result partial to display based on the class and then passes the result to the partial for display. So if the result has a class of Business, the partial app/views/businesses/_result.html.erb will be rendered. This is a quick example of a search result partial:

<span id="localBizName">< %= link_to result.name, business_path( result ) %></span><br />
<p class="smallOffset">< %= truncate( "#{result.description}", :length => 128 ) %></p>

This is useful because all models don’t have the same characteristics. By creating a search result partial for each model type, this can be reused for consistent looking search results around your webapp. If the search is model specific, the same result partial will be used in every iteration over the results. If the search is model agnostic, then you can display your search results in a consistent manner.

Posted in Rails. Tags: , , . 1 Comment »
  • http://www.genlighten.com dean_genlighten

    Hi Ben… thanks for this post… it looks very helpful and I’m eager to try implementing it in my app. Can you offer any details on your search box/form itself, and how you hook it up with your search_controller? In particular:nn*how is :model passed back through the params hash? Could I use that approach if I have a dropdown next to my search box that, for example, lets the user choose to search either the “projects” or “updates” models?nn*how are the keywords in the search query passed? Do you just have a search form with form_tag placed in a partial? Which view holds that form?nnMost of the search implementations I’ve found seem to have search reside within the index action of a particular controller/model, so I found yours particularly valuable since I’m interested in search across several models at once.nnThanks,nnDean RichardsonnGenlighten.comnn