Relax with Merb and CouchDB

Introduction

Welcome. From rack to the upcoming merb-notifier, Merb’s flexibility and feature set offer us much in building web apps. But most of us still end up storing our semi-structured data in a relational database. Relational databases are a poor fit for many web applications. So why do we do it? Most probably, a lack of alternatives. No more. CouchDB is a document oriented database that doesn’t impose the constraints of schemas and fixed size fields on your data. It’s also distributed, fault-tolerant and accessible via a RESTful JSON API. Queries are written in JavaScript and are performed as map reduce invocations.

This tutorial offers one view of developing a CouchDB backed Merb application. It uses RelaxDB and its corresponding Merb plugin. RelaxDB takes its inspiration from ActiveRecord and offers a simple idiom for specifying object relationships.

This tutorial assumes some familiarity with Merb and is based on the slapp tutorial.

Getting Started

Merb
CouchDB
RelaxDB
gem sources -a http://gems.github.com # if not already added
sudo gem install paulcarey-relaxdb
git clone git://github.com/paulcarey/merb_relaxdb.git
cd merb_relaxdb
sudo rake install
cd ..

Testing your Setup

We’re going to create an app where you can post messages and rate them. In Web 2.0 spirit, the app will be called ratr.

$ merb-gen app ratr
$ cd ratr

First we need to let ratr know that we’ll be talking to CouchDB. Open up config/init.rb and in the dependencies section, just before Merb::BootLoader.after_app_loads, add dependency "merb_relaxdb" A little further down, in the ORM section, add use_orm :relaxdb The depdency does the real work, but setting the orm to relaxdb allows the plugin to hook into Merb generators.

Back to the shell and type


$ merb-gen model rating

You’ll see that app/models/rating.rb and spec/models/rating_spec.rb have been created for you. But you’ll also see a message stating that couchdb.yml couldn’t be found. Rename the sample file to couchdb.yml, and edit it so it looks like the following. Note: make sure the whitespace before host, port and db is exactly two spaces. If you use tabs, all bets are off.

:development:
  :host: localhost
  :port: 5984
  :db: ratr_dev

:test:
  :host: localhost
  :port: 5984
  :db: ratr_test

Next we’re going to confirm that our Merb app can talk to CouchDB. Open up spec/models/rating_spec.rb and edit it so it looks like the following.

require File.join( File.dirname(__FILE__), '..', "spec_helper" )

describe Rating do

  before(:each) do
    RelaxDB.delete_db("ratr_test")
    RelaxDB.use_db("ratr_test")
  end

  it "should function as a CouchDB document" do
    Rating.new.save
  end

end

From the shell run


$ rake spec

If the test passed, congratulations, you’ve set up everything successfully. You can visually confirm the existence of your document in CouchDB by opening the Futon web client at http://127.0.0.1:5984/_utils/index.html and choosing the ratr_test database.

If the test failed, confirm the following

Declaring Model Relationships

Now that we know we have a working environment, let’s create a richer object model. Inheriting from RelaxDB::Document allows classes to define properties and has_one, has_many and belongs_to relationships. Properties can be supplied a default, a validator and a validation message.

Let’s create our post model. From the shell

$ merb-gen model post

This creates app/models/post.rb. We’ll declare its relationship to a rating, add a contents property and a created_at property.

class Post < RelaxDB::Document
  property :created_at
  property :contents

  has_one :rating
end
Modify app/models/rating.rb, giving it a thumbs_up property and linking it to a post.
class Rating < RelaxDB::Document
  property :thumbs_up, :validator => lambda { |i| i >=0 && i < 3 }, :validation_msg => "No no" 

  belongs_to :post
end

Just like ActiveRecord, when Rating declares that it belongs_to a post, it’s stating that each rating document will contain a property listing the id of the post to which it belongs.

Let’s quickly explore our models to ensure things work as expected. For a real application, testing is obviously the way to go, but for a whistle-stop tour, visual confirmation is fine. We’re also going to cheat a little – matching 32 character identifiers by sight is no fun, so we’ll swap out the UUID gem at runtime for a short random id. Fire up the console
$ merb -i
~ Loaded DEVELOPMENT Environment...
...
~ RelaxDB connected to CouchDB http://localhost:5984/ratr_dev
>> RelaxDB::UuidGenerator.id_length = 3
=> 3
>> p = Post.new(:contents => "gromit").save
...
=> #<Post:12108880, _id: "841", _rev: "776888311", created_at: Tue Aug 26 10:42:20 +0100 2008, contents: "gromit">
>> p.rating = Rating.new(:thumbs_up => 2).save
...
=> #<Rating:9863000, _id: "380", _rev: "2267620448", thumbs_up: 2, post_id: 841>

Looks fine, good. Our model is finished, creating it couldn’t have been much easier. This is just one area where the schema free nature of CouchDB shines.

If you’re following along you’ll have noticed Merb making a few requests to CouchDB, including one that submitted some JavaScript. CouchDB queries are expressed via map reduce functions rather than the set based operators of SQL and relational databases. When p.rating was invoked above, RelaxDB noticed that a view to retrieve all ratings for a particular post didn’t yet exist, so it created a corresponding map function, written in JavaScript, wrapped it in a document and PUT it to CouchDB.

This brings us to an interesting observation – not only is your application data stored as documents but so too are your queries. This single viewpoint keeps the CouchDB API very clean and simple.

Inbetweening

So far we’ve been concerning ourselves exclusively with the backend. Let’s now look at the controllers.

Before we do, add the following to config/init.rb, right before the merb_relaxdb dependency.
dependency "merb_helpers" 
dependency "merb-action-args"  

$ merb-gen controller posts
Add the following to app/controllers/posts.rb
class Posts < Application

  def index
    @posts = Post.all.sorted_by(:created_at) { |q| q.descending(true) }
    render
  end

  def create
    Post.new(params).save
    redirect "/posts/" 
  end

end

Quite a lot is going on with Post.all.sorted_by(:created_at) { |q| q.descending(true) }. Each map function in a CouchDB view can emit a key and value. When a view is queried, the result set is sorted by key. Post.all.sorted_by(:created_at) creates a view where the emitted key is the created_at value of a Post. The sorted_by method also takes a block, yielding a query object. The query object maps directly to the query params accepted by CouchDB. Keys may also be complex e.g. an array. Querying views with complex keys opens up many non obvious possibilites. See the view collation article listed below for more details.

The create method is straightforward. To create a post, simply pass it the params hash and save it. Keys and values contained in the params hash that aren’t known to the Post class are silently ignored.

Next up is our ratings controller

$ merb-gen controller ratings
Copy the following into app/controllers/ratings.rb
class Ratings < Application

  def create(post_id, thumbs_up)
    post = RelaxDB.load(post_id)
    rating = Rating.new(:thumbs_up => thumbs_up.to_i)
    if rating.save
      post.rating = rating
      redirect "/posts/" 
    else
      error = rating.errors[:thumbs_up]
      redirect("/posts/", :message => {:error => "#{error}"} )
    end
  end

end

This highlights a few nice features of Merb. The action_args plugin is used to map form parameters to method parameters. The else clause uses a redirect that makes it easy to present a one shot message to the user. Note that the rating is saved before being assigned to a post. This ensures that the right action can be taken on validation failure or success.

The User Experience

The following section deals with views and is based heavily on the slapp tutorial. It’s light on descriptive detail, so if you feel a little lost, you may want to consult slapp. If you’d like to jazz up the front end a little, you may also want to overwrite public/stylesheets/master.css with slapp’s css.

app/views/posts/index.html.erb
<h1>ratr</h1>

<p>Recent Posts:</p>

<%= message[:error] %>

<div id="posts" class="container">
    <%= partial "post", :with => @posts %>
</div>

<p>Post a message</p>
<%= form :action => url(:controller => "posts", :action => "create") do %>
  <%= text_field(:name => "contents", :size => 40) %>
  <%= submit "Post" %>
<% end =%>
app/views/posts/_post.html.erb (you’ll need to create this file yourself)
<div id="post-<%= post.id %>" class="post">
  <p class="body"><%= h(post.contents) %></p>
  <p class="created"><%= time_ago_in_words(post.created_at) %> ago
    <% if post.rating %>
       - <%= post.rating.thumbs_up %> thumbs up!
    <% else %>
      <%= form :action => url(:controller => "ratings", :action => "create") do %>
        <%= text_field(:name => "thumbs_up", :value => 0) %>
        <%= hidden_field(:name => "post_id", :value => post._id) %>
        <%= submit "Rate" %>
      <% end =%>    
    <% end %>
  </p>
</div>
You’re done. Start Merb,

$ merb
point your browser at http://localhost:4000/posts and start posting and rating.

To Infinity and Beyond

CouchDB delivers a lot and promises even more. If you’re interested in getting involved, the wiki and mailing lists are good places to start.

It might not always be obvious how to express a query with map reduce functions. cmlenz’s description of view collation offers a window into the mindset.

More details on RelaxDB and merb_relaxdb can be found on github.

RelaxDB abstracts away a certain amount. If you prefer to stay close to the metal, you may be interested in jchris’s couchrest.

Finally, questions or comments to paul.p.carey@gmail.com. Please put relaxdb in the title.

About the author

Paul Carey is the founder of strawberrydiva.com, a massively multiplayer casual web game due to launch in a few months.