Note: You are viewing a non-styled version of this website. Click here to skip to the content.

Vermonster - Open Standards through Open Source

Vermonster Logo

Ultrasphinx - Facets Fix

Posted on June 17th, 2008 under Code, Ruby on Rails.

We’ve been working with a client on an ultrasphinx integration effort. Ultrasphinx is a nice interface to the powerful Sphinx fulltext search engine. One of the rather cool things about sphinx, is the faceting feature - being able to facet, or group search totals by specified field(s). However, when we went to implement it, ran into an issue relating to the facet caching, resulting in errors like:

Field series is a text field, but was not configured for text faceting

We fixed this with a undocumented feature in our index definition and a small patch to ultrasphinx.

First think, our models had a bit of inheritance. We had a base class for our products with a child for our active products. Active ones had two more children, different classes of products. And I guess for completeness the product base is a child of ActiveRecord.

Products::Base
  -> Products::Active
    -> Products::Type1
    -> Products::Type2

We wanted to place our search index on Products::Active, to make all our active products searchable.

Using :class_name

We found that perhaps because of our inheritance, we needed to use an under-documented index specification, :class_name, with our includes. For instance, one of our relationships it to a category, so we had to do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Class Product::Active < Product::Base
is_indexed :fields => [
    { :field => 'name', :sortable => true },
    { :field => 'description' },
    { :field => 'series', :facet => true }
  ],
  :include => [
    { :class_name => 'Category',
       :field => 'name',
       :as => 'category',
       :facet => true,
       :association_sql => 'LEFT OUTER JOIN categories c ON
             (c.id = products.category_id)' 
    }
  ]

As you can see, instead of using :name in our include on line 8, we are using :class_name. This is could be a bug in the constantization of class names, we didn’t really dig too deep on this one. You will also notice that we explicitly stated the :association_sql statement.

Facet Caching

This was working fine, but we kept getting errors telling us that our series wasn’t configured for text faceting. But clearly we have it configured in our index. Looking at internals.rb it looks like it is always expecting an :as setting for the facet field. But the assumption we had is the the :as configuration is optional, in it’s absence it should use the :field configuration. So a small path did the trick, shown here:

--- internals-orig.rb	2008-06-17 22:47:48.000000000 -0400
+++ internals.rb	2008-05-01 13:40:29.000000000 -0400

@@ -174,9 +210,9 @@

           # Concatenates might not work well
           type, configuration = nil, nil
           MODEL_CONFIGURATION[klass.name].except('conditions', 'delta').each do |_type, values|
             type = _type
-            configuration = values.detect { |this_field| this_field['as'] == facet }
+            configuration = values.detect { |this_field| (this_field['as'] == facet) || (this_field['field'] == facet) }
             break if configuration
           end

@@ -196,7 +232,7 @@
             when 'include'
               # XXX Only handles the basic case. No test coverage.

-              table_alias = configuration['table_alias']
+              table_alias = configuration['table_alias'] || configuration['as']
               association_model = if configuration['class_name']
                 configuration['class_name'].constantize
               else

Leave a Reply