• Home

  • Custom Ecommerce
  • Application Development
  • Database Consulting
  • Cloud Hosting
  • Systems Integration
  • Legacy Business Systems
  • Security & Compliance
  • GIS

  • Expertise

  • About Us
  • Our Team
  • Clients
  • Blog
  • Careers

  • VisionPort

  • Contact
  • Our Blog

    Ongoing observations by End Point Dev people

    Sunspot, Solr, Rails: Working with Results

    Steph Skardal

    By Steph Skardal
    December 12, 2011

    Having worked with Sunspot and Solr in several large Rails projects now, I’ve gained some knowledge about working with result sets optimally. Here’s a brief explanation on working with results or hits from a search object.

    MVC Setup

    When working with Sunspot, searchable fields are defined in the model:

    class Thing < ActiveRecord::Base
      searchable do
        text :field1, :stored => true
        text :field2
        string :field3, :stored => true
        integer :field4, :multiple => true
      end
    end
    

    The code block above will include field1, field2, field3, and field4 in the search index of things . A keyword or text search on things will search field1 and field2 for matches. field3 and field4 may be used for scoping, or limiting the search result set based to specific values of field3 or field4.

    In your controller, a new search object is created with the appropriate scoping and keyword values, shown below. Pagination is also added inside the search block.

    class ThingsController < ApplicationController
      def index
        @search = Sunspot.search(Thing) do
          #fulltext search
          fulltext params[:keyword]
    
          #scoping
          if params.has_key?(:field3)
            with :field3, params[:field3]
          end 
          if params.has_key?(:field4)
            with :field3, params[:field4]
          end
    
          paginate :page => params[:page], :per_page => 25
        end
        @search.execute!
      end
    end
    

    In the view, one can iterate through the result set, where results is an array of Thing instances.

    <% @search.results.each do |result| -%>
    <h2><%= result.field3 %></h2>
    <%= result.field1 %>
    <% end -%>
    

    Working with Hits

    The above code works. It works nicely until you display many results on one page where instantiation of things is not expensive. But the above code will call the query below for every search, and subsequently instantiate Ruby objects for each of the things found. This can become sluggish when the result set is large or the items themselves are expensive to instantiate.

    # development.log
    Thing Load (0.9ms)  SELECT "things".* FROM "things" WHERE "things"."id" IN (6, 12, 7, 13, 8, ...)
    

    An optimized way to work with search results sets is working directly with hits. @search.hits is an array of Sunspot::Search::Hits, which represent the raw information returned by Solr for a single returned item. Hit objects provide access to stored field values, identified by the :stored option in the model’s searchable definition. The model definition looks the same. The controller may now look like this:

    class ThingsController < ApplicationController
      def index
        search = Sunspot.search(Thing) do
          #fulltext search
          fulltext params[:keyword]
    
          #scoping
          if params.has_key?(:field3)
            with :field3, params[:field3]
          end 
          if params.has_key?(:field4)
            with :field3, params[:field4]
          end
        end
        search.execute!
    
        @hits = search.hits.paginate :page => params[:page], :per_page => 25
      end
    end
    

    And working with the data in the view may look like this:

    <% @hits.each do |result| -%>
    <h2><%= hit.stored(:field3) %></h2>
    <%= hit.stored(:field1) %>
    <% end -%>
    

    In some cases, you may want to introduce an additional piece of logic prior pagination, which is the case with the most recent Rails application I’ve been working on:

        ...
        search.execute!
    
        filtered_results = []
    
        search.hits.each do |hit|
          if hit.stored(:field3) == "some arbitrary value"
            filtered_results << hit
          elsif hit.stored(:field1) == "some other arbitrary value"
            filtered_results << hit
          end
        end
       
        @hits = filtered_results.paginate :page => params[:page], :per_page => 25
    

    Sunspot and Solr are rich with functionality and features that can add value to a Rails application, but it’s important to identify areas of the application where database calls can be minimized and lazy loading can be optimized for better performance. The standard log file and database log file are good places to start looking.

    rails search solr sunspot


    Comments