Chris Umbel

Solr Data Access in Ruby with SolrMapper

Skunkworx LogoRecently my employer (The Skunkworx) released our first open source project, SolrMapper. Like the readme says, it's a Ruby Object Document Mapper for the Apache Foundation's Solr search platform. It's loosely patterned after ActiveRecord and MongoMapper so it should feel somewhat familiar.

What differentiates SolrMapper from many other Solr libraries is that it's not necessarily dependent on another form of persistence a la acts_as_solr. It could certainly allow you to use Solr as a stand-alone, general purpose data store if that floats your boat.

Installing

gem install solr_mapper

Examples

I might as well get started with a simple example of a model. This example assumes a solr index located at http://localhost:8080/solr/article with an abbreviated schema of:

<fields>
  <field name="id" type="string" indexed="true" stored="true" required="true" />
  <field name="title" type="text" indexed="true" stored="true"/>
  <field name="content" type="text" indexed="true" stored="false" multiValued="true"/>
</fields>

and the model

require 'solr_mapper'

class Article
  include SolrMapper::SolrDocument

  bind_service_url  'http://localhost:8080/solr/article'
end

Not much to it. bind_service_url simply indicates the URL of the Solr instance housing our data. There's no real schema definition here. SolrDocument is just mixed-in and the model is pointed at an index.

Creating

I could then create an article as such:

id = UUID.new().generate()

article = Article.new(
  :_id => id,
  :title => 'This is a sample article',
  :content => 'Here is some sample content for our sample article.'
)

article.save()

Note the field identified by the symbol :_id. This field is somewhat special. SolrMapper prepends the underscore for this field only as it maps to "id" in solr. The remainder of the fields map to their respective fields in Solr strait-away.

Querying

I could then retrieve the article we just saved like:

article = Article.find(id)

#print out the article's title
puts article.title

producing the output

This is a sample article

Of course the whole point of Solr is rich, full-text searches. To demonstrate I'll need some additional sample data.

Article.new(
  :_id => UUID.new().generate(),
  :title => 'Yet another article',
  :content => 'Have another one.'
).save

Article.new(
  :_id => UUID.new().generate(),
  :title => 'Number three',
  :content => 'The third in a string of three.'
).save

The simplest way of accomplish this in SolrMapper is via a model's query method which can accept a typical Solr query string.

articles = Article.query('title:article')

articles.each do |article|
  puts article.title
end

producing

This is a sample article
Yet another article

It's also possible to pass in the query in a more structured fashion via a hash.

articles = Article.query({:title => 'article'})

articles.each do |article|
  puts article.title
end

producing the same results as the search example above.

Additional Solr parameters can be passed in as well such as this title sort.

articles = Article.query({:title => 'article'}, {:sort => 'title desc'})

Updating

Updating a portion of a live record can be accomplished with the familiar update_attributes method:

article.update_attributes(:title => 'An updated title')

Pagination

Generally speaking pretty much any traditional web app that lists or searches entities needs paging. SolrMapper integrates will_paginate to accomplish this.

articles_page = Article.paginate({:title => 'article'})

On to page 2 with:

articles_page = Article.paginate({:title => 'article'}, {:page => 2})

The page size can be modified with:

articles_page = Article.paginate({:title => 'article'}, {:rows => 5, :page => 2})

Relationships

Solr itself isn't relational and separate indexes are, well, separate. SolrMapper allows you to support rudimentary ActiveRecord-esque relationships. Remember though, there is no low-level joining going on. This all happens at the ruby level so you must be judicious.

class Biography
  include SolrMapper::SolrDocument
  has_many :articles

  bind_service_url 'http://localhost:8080/solr/bio'
end

class Article
  include SolrMapper::SolrDocument
  belongs_to :biography
  
  bind_service_url 'http://localhost:8080/solr/article'
end

Biography.find('SOME UUID').articles.each do |article|
  puts article.title
end

Auto-Generated IDs

As of 0.1.9 solr_mapper can auto-generate the id field with a UUID when you define your model as follows.

class Item
  include SolrDocument

  # tells solr_mapper to generate the id
  auto_generate_id

  bind_service_url 'http://localhost:8080/solr/item'
end

Conclusion

SolrMapper is intended to be simple and familiar and I hope we've accomplished that. We're looking for help either in patches or feedback, especially because we're just getting started here. If you have either please contact us at github.

Tue Oct 26 2010 02:08:14 GMT+0000 (UTC)

Follow Chris
RSS Feed
Twitter
Facebook
CodePlex
github
LinkedIn
Google