Search code examples
ruby-on-railssortingsunspotsunspot-railsfacet

Custom Sorting of Solr Facet Values - Rails & Sunspot Solr


I am using Sunspot Solr to drilldown with faceted searching. However I am having an issue when trying to display the facet values sorted in the order they need to be. My products could have sizes that are either (S, M, L, XL, XXL) and some products have sizes ranging such as shoe sizes (8, 9, 10, 11, 12, 13). When setting the facet.sort method to 'index' it puts the string values into alphabetical order, which results in [L, M, S, XL, XXL] and [10, 11, 12, 8, 9] respectively. What would be a good method for achieving a custom sort method to achieve these goals?

My Contoller:

@search = Product.search do
    fulltext params[:search]
    facet(:size, :sort => :index)
    with(:size, params[:size]) if params[:size].present?
end

My View:

<% for row in @search.facet(:brand).rows %>
    <li>
        <% if params[:brand].blank? %>
            <%= link_to link_to "#{row.value} (#{row.count})", params.merge({:brand => row.value}) %>
        <% else %>
            <strong><%= row.value %></strong> (<%= link_to "remove", params.merge({:brand => nil}) %>)
        <% end %>
    </li>
<% end %>

The Resulted Sort That Is Displayed

   SIZE 
L (10)
M (10)
S (10)
XL (10)
XXL (10)

Solution

  • As you mentioned, Solr only accepts two sort values for facets, count (number of results) and index (lexicographic). Assuming you don't want to change the indexed data, you have two alternatives, sort the facets array manually, or create a custom query facet. I would opt for the former, as you have a very limited facet result set.

    Manual sort

    # supposing these are your possible sizes
    SCALE = %w(S M L XL XXL) 
    # once you perform your search sort based on the scale order
    @search.facet(:size).rows.sort_by! { |r| SCALE.index(r.value) }
    

    Query facet

    @search = Product.search do
      fulltext params[:search]
      facet(:size) do
        row('S') { with(:size, 'S') }
        row('M') { with(:size, 'M') }
        row('L') { with(:size, 'L') }
        row('XL') { with(:size, 'XL') }
        row('XXL') { with(:size, 'XXL') }
      end
      with(:size, params[:size]) if params[:size].present?
    end