ThinkingSphinx gives Ruby-on-Rails developers the power of a full-text search engine, with only one downside; it’s a full-text search engine. Recently I had to develop an Advanced Search facility for a site which required me to locate content within certain indexed fields; TS can do this, but it does require a little more configuration than normal.

The Basics

If you’ve never used ThinkingSphinx before, make yourself comfortable, and then visit http://freelancing-god.github.com/ts/en/ for the basics of using ThinkingSphinx. The video at RailsCasts is also excellent; http://railscasts.com/episodes/120-thinking-sphinx

The Problem

The usual usage of ThinkingSphinx is to simply give it the search parameters.

@images = Image.search ferarri

This query will return an array containing each image object which has the word ferarri in it. Often, this is sufficient, but consider a model index structure such as

  define_index do
    indexes :name, :as => :asset_name
    indexes description, :as => :desc
    indexes alt_tag, :as => :alttag
    indexes credit
    indexes image_type.name, :as => :image_type
    indexes keywords.name, :as => :keywords
    set_property :delta => :datetime, :threshold => 1.hour
  end

There may be occasions where we want to search for the word Ferarri in the name field, but we don’t want to see records where the word ferrari just occured in the description. To do this, we need to place Thinking Sphinx into Extended Mode and restrict the location of the search

@images = Image.search @asset_name => ferrari, :match_mode => :extended

If we want to specify multiple search terms across several fields, we add them into the search string seperated by commas;

@images = Image.search @asset_name => ferrari, @alttag => Peter, :match_mode => :extended

To specify a string, enclose it in quotes;

@images = Image.search @asset_name => ferrari, @alttag => 'Peter Connolly', :match_mode => :extended

That’s fine for hard coded searches, but how do we build this into an advanced search form? Here’s some code snippets that may help Ruby on Rails developers (no – this is not a step-by-step record of how to build advanced search forms, just a series of hints)

Model

For Thinking Sphinx to work, it needs to know which content is to be indexed. We do this by adding a define_index section to the appropriate model (image.rb, in this case)

  define_index do
    indexes :name, :as => :asset_name
    indexes description, :as => :desc
    indexes alt_tag, :as => :alttag
    indexes credit
    indexes image_type.name, :as => :image_type
    indexes keywords.name, :as => :keywords
    set_property :delta => :datetime, :threshold => 1.hour
  end

View

The view will need pagination; for this, we use the will_paginate plugin (walkthrough at http://railscasts.com/episodes/51-will-paginate).

<%# Display the pagination %>
<%= will_paginate @images,
        :inner_window => 1,
        :outer_window => 0,
        :params => {
            'img_type' => @img_type,
            'hidden_id' => @hidden_id,
            'search' => @search_terms,
            'search_title' => @search_title,
            'search_desc' => @search_desc,
            'search_alttag' => @search_alttag,
            'search_credit' => @search_credit,
            'search_imagetype' => @search_imagetype,
            'search_keywords' => @search_keywords
        }
%>

<%# Display the correct set of results %>
<div id="image_library_images">
  <%= render :partial => 'search_image',
             :collection => @images,
             :as => :image
   %>
</div>

 

The Advanced form itself uses a standard Rails form

  <div id="advancedsearch" style="display:none;">
    <p><strong>Advanced Image Search options</strong></p>
    <table>
      <tr><td>
        <%= label_tag "Title" %>
      </td><td>
        <%= text_field_tag :search_title %>
      </td></tr><tr><td>
        <%= label_tag "Caption" %>
      </td><td>
        <%= text_field_tag :search_desc %>
      </td></tr><tr><td>
        <%= label_tag "Alt Tag" %>
      </td><td>
        <%= text_field_tag :search_alttag %>
      </td></tr><tr><td>
        <%= label_tag "Credit" %>
      </td><td>
        <%= text_field_tag :search_credit %>
      </td></tr><tr><td>
        <%= label_tag "Image Type" %>
      </td><td>
        <%= text_field_tag :search_keywords %>
      </td></tr>
    </table>
  </div>

Controller

The controller is where all the heavy lifting occurs for this search function.

  def advanced_search
    @hidden_id = params[:hidden_id] ? params[:hidden_id] : params[:content_type] + '_asset_attributes_primary_image_id'
    @switch_img = params[:img_id] ? params[:img_id] : :primary_image;

    @search_terms = params[:search]  
    @search_title = params[:search_title]
    @search_desc = params[:search_desc]
    @search_alttag = params[:search_alttag]
    @search_credit = params[:search_credit]
    @search_keywords = params[:search_keywords]

    @search = []
    @advsearch = ""

    if (@search_title.present?)
      @advsearch = ' @asset_name => ' + @search_title
    end

    if (@search_desc.present?)
      searchdesc = " @desc => " + @search_desc
      @advsearch += @advsearch.present? ? ", " + searchdesc : searchdesc
    end

    if (@search_alttag.present?)
      searchalttag = " @alttag => " + @search_alttag
      @advsearch += @advsearch.present? ? ", " + searchalttag : searchalttag
    end

    if (@search_credit.present?)
      searchcredit = " @credit => " + @search_credit
      @advsearch += @advsearch.present? ? ", " + searchcredit : searchcredit
    end

    if (@search_keywords.present?)
      searchkeywords = " @keywords => " + @search_keywords
      @advsearch += @advsearch.present? ? ", " + searchkeywords : searchkeywords
    end

    @images = Image.search @search_terms + @advsearch,
              :match_mode => :extended,
              :page => params[:page],
              :ignore_errors => false,
              :per_page => 10

    render :partial => 'image_search_results', :layout => false
  end

Indexing Sphinx

This is the mistake that we often make.. After editing data in your database, you will need to reindex Sphinx; otherwise your new data will not be shown the search results.Use the following command to rebuild sphinx from the linux command prompt;

   rake ts:rebuild

Again, please note that the code above is a series of hints, *not* a final, fully fledged solution. Use it as you will, but it’s supplied with no warranty or guarantees. Have fun!