Integrating Solr Search with Spree

For the past 2 months I’ve been working on an e-commerce website where we’ve been using Spree. The built-in searching with Spree is pretty decent and simply builds SQL queries to execute against the database. While this is a great starting point we quickly got to a point where we needed to do more advanced searching – for example, we wanted the user to be able to filter by size, which is not something you can really accomplish with basic SQL queries.

We can obviously build a SQL query that will filter by a specified size, but it’s not really possible to show the user the different sizes that are available within his current search. So if the user is looking at all ‘geek t-shirts’ in your store, you want to show him only sizes that apply to ‘geek t-shirts’ – such as S/M/L, not all the sizes in your entire store (which might include shoe sizes – such as 9/10/11). This is usually referred to as faceted search and cannot really be accomplished without multiple SQL queries or pre-computed data.

So basically SQL is amazing at the ‘search’ part of this problem, but really bad at the ‘faceting’ part of it. We initially started off by pre-calculating some of the faceting information, but this is really stretching the technology and only works (1) in the short-term and (2) if you have complete control over when your data is changing. The best solution here is to start using a search engine.

Integrating Solr

There were a few different search engines available to us – ElasticSearch, Sphinx, Solr, etc. We opted for Solr because we had strong experience with using it in the past and due to the nice integration with Rails through the Sunspot gem.

There is a gem called spree-solr-search that add Solr integration to your Spree application. We evaluated this gem and decided not to use it for the following reasons:

  • It hasn’t been updated in at least 11 months
  • There is no version matching the current stable version of Spree
  • The gem didn’t match many of our requirements in a straightforward way (custom sorting, for example)
  • Integrating Solr ourselves is pretty straighforward

In the end we simply integrated Solr ourselves and it was much easier than we thought it would be. Spree already has the concept of a searcher (instead of search just being a function on the Product class) so you basically need to implement a new search class that matches the existing interface and then configure your application to use it.

The outline for your Solr searcher should look something like the following:

module Spree::Search
  class Solr
    attr_accessor :current_user
    attr_accessor :current_currency

    def initialize(params)
      page = (params[:page].to_i <= 0) ? 1 : params[:page].to_i
      @search_result = Spree::Product.solr_search do
        # execute your search here
        paginate page: page, per_page: Spree::Config[:products_per_page]
      end
    end
    
    def retrieve_products
      @search_result.results
    end
  end
end

That’s pretty much it. The Sunspot documentation has some nice examples of how to setup your objects for indexing (which I haven’t shown here) and then how to perform the actual search. Spree will also make the searcher available to your views so any additional methods you add to your searcher (such as any faceting results) will be available in your views.

The last step is to configure your application to use the new searcher (config/initializers/spree.rb):

Spree.config do |config|
  config.searcher_class = Spree::Search::Solr
end

One thing that really made this kind of integration easy is that the default pagination gem - Kaminari - uses the exact same interface as Solr, which means once you plugin your new searcher all your pagination will still work as before (as long as you actually do the pagination inside your search block).

Overall I was very happy with how flexible Spree was with regards to search. Using Solr as our search engine added a very powerful technology for allowing users to find the products they are looking for. Happy coding.