Tony Pitale

Sunspot with Datamapper

Expanding search beyond simple queries is not easy. I tend to be lazy. For a long time this meant chaining queries and just letting PostgreSQL sort it all out, adding indexes along the way. This all falls down of course when we need to find one word – or part of one – within a body of text. Even worse is joining across tables all with one query. It can get very ugly, very quickly.

Thankfully, I had read about Sunspot, a well-designed wrapper around the powerful Solr search daemon. To get started, I watched the excellent introductory "Search with Sunspot" Railscast by Ryan Bates (you should really buy a pro account, it's worth every penny).

Unfortunately, out-of-the-box, Sunspot comes with ActiveRecord support and not much mention of other ORM's in the documentation. Here's what's working for me.

Install and Setup

Installation is cake. Simply add gem "sunspot_rails" to your Gemfile as in any other case, and bundle install away.

Thanks to a gist by Dan Kubb, there is an example of how to create an adapter for DataMapper to work with Sunspot. I forked it and added a few more pieces that seem to be required for it to work.

After the adapter is in place, we can generate the Sunspot configuration with script/rails generate sunspot_rails:install. We'll want to be sure to add solr/data to our .gitignore file.

Model Configuration

To get our models set to be indexed, we can simply follow Sunspot's README and configure our models using a searchable block. Here's what I'm using to search a Problem class, though it uses a lot of "text" fields. Many more, better examples can be found in the Sunspot docs.

Searching

For some reason, I was unable to call Problem.search {} and instead I have to call Sunspot.search(Problem) {}. Happily, I like this form better. It makes it clear that I can pass in multiple model class names to search across and that I'm only really searching the Problem in this example.

Sunspot/Solr Tweaks

I've made only a few minor modifications to solr's schema.xml configuration as generated by Sunspot. Under the solr.TextField fieldType I've added just a bit to use PorterStemFilterFactory to stem words in the index. Stemming is the process of removing common endings from words, to normalize when searching. For example, "riding" and "rides" would both be stemmed to "ride". Be careful, each language will have its own stemmer.

Quirks

Without specifying the models you wish to use explicitly, the rake tasks for Sunspot will attempt to load ActiveRecord, which we obviously do not have. The simple solution is to use rake sunspot:index models=Problem+Task+OtherModel to tell the task exactly which models we would like to be indexed.

Testing

I would be remiss if I didn't write about how I'm testing. Stubbing is advisable in most scenarios where you don't want to actually test the functionality within Sunspot.

However, I would also ensure the results you want are coming back from your search, a small, precise set of integration tests should be built.

In Conclusion

I'm happy to report that, in combination, Sunspot and DataMapper get along quite beautifully and I have had no real problems with this arrangement. The declarative nature of both DataMapper and Sunspot make for models that are extremely easy to understand and maintain. Now, my search pains are eased.

Read More Recent Posts