CouchDB on Rails (part 8 of ?)

  1. An introduction to CouchDB
  2. Installing CouchDB
  3. Experimenting with CouchDB's web interface
  4. Integrating with Rails using ActiveCouch
  5. Integrating with Rails using RelaxDB
  6. Getting to scaffolding using RelaxDB
  7. Installing CouchDB from Subversion source code
  8. Trying out couchrest and topfunky's basic_model

Some time has gone by since i last used CouchDB and one method seems to have bubbled up to the top as a good one to use. It is couchrest, helped along by topfunky's basic_model wrapper. Today i am going to have a little play with them.

I have been meaning to get back into CouchDB but to be honest, i was tired of the old CD Collection database, and i was waiting for some inspiration. Today that inspiration arrived! I need to write my Christmas cards by the end of the week. I was impressed this afternoon by my auntie's label printing system, and i decided i should do the same. I did some label printing last week at work, and i quite fancy making a nice little Ruby library for it.

I also think an address book is a pretty neat application for CouchDB as a document oriented database. It'll work quite well for people who have multiple email addresses, or multiple telephone numbers. The document for each person can expand as big as it needs to be without wasting database space for the people who only have one phone number.

I need to print these address labels by the end of the week. You know that if a programmer has 100 hours to do something, they will generally spend 99 hours figuring out how to do it in 1 hour! Let's go then! :D

Starting up the Rails project

rails address_book
cd address_book

Couchrest specifically tells me to install it as a gem, so i will do just that. A little hint, couchrest requires rest-client and a few other dependencies, so make sure they are installed first.

sudo gem install rest-client json extlib
sudo gem install jchris-couchrest -s http://gems.github.com

You can run a 'gem list' to ensure that it installed successfully.

gem list | grep 'couchrest'
jchris-couchrest (0.9.12)

A quick check that Ruby can find and use the couchrest gem:

irb
irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require 'couchrest'
=> true

Getting the basic_model

Geoffrey Grosenbach (topfunky) has written a helpful basic_model wrapper that should allow us to perform a few useful operations on our models. I'm not sure whether we're supposed to install it as a gem, submodule it as a plugin, or just copy the files by hand. It looks as if it ought to be installable as a gem, but i can't work out how. I think it should work just as well as a plugin, because i can see the lib there which contains the basic_model.

cd ~/rails/address_book
git submodule add git://github.com/topfunky/basic_model.git vendor/plugins/basic_model

Creating a model

Here is the simplest possible model for a Contact in my address book. Notice that it inherits from BasicModel.

class Contact < BasicModel
 
end

One particular feature of BasicModel is that every command needs to know a database to work on. This is good if you are using a different database per user accessing your application, which is a perfectly valid thing to do in CouchDB. Let me give an example.

./script/console 
Loading development environment (Rails 2.1.2)
>> c = Contact.new
ArgumentError: wrong number of arguments (0 for 1)
	from (irb):1:in `initialize'
	from (irb):1:in `new'
	from (irb):1

Normally that would work, right? This is how we know that we are inheriting from BasicModel and not just using a standard Ruby class. Now let's do that again but give it a database name …

./script/console 
Loading development environment (Rails 2.1.2)
>> c = Contact.new('address_book')
=> #<Contact:0x7f6470e3d538 @attributes={}, @database_name="address_book">
>> c.save
=> #<Contact:0x7f6470e3d538 @attributes={"updated_at"=>Tue Dec 09 21:43:15 +0000 2008, "_rev"=>"4254838631", "_id"=>"2421574ce782cf1b41e801fd0c19cf4f", "type"=>"Contact", "created_at"=>Tue Dec 09 21:43:15 +0000 2008}, @database_name="address_book">

Woohoo, look at that! Part of the basic_model gives us a created_at and updated_at, just like we're used to with Rails! It also adds in a 'type' field for this document to show that it is a contact. What's more, we get to see those CouchDB specific fields like _id and _rev. This is looking good!

It gets better! Look at my Futon utility!

Basic_model created a database for me!

I did not create that address_book database. BasicModel just created it for me! And here is my document, ready to go.

Here is my document in CouchDB

Now, let me think. Who do i want to send a Christmas card to?!

./script/console 
Loading development environment (Rails 2.1.2)
>> c = Contact.new('address_book', :first_name => 'Geoffrey', :last_name => 'Grosenbach')
=> #<Contact:0x7f1300eeb280 @attributes={:first_name=>"Geoffrey", :last_name=>"Grosenbach"}, @database_name="address_book">
>> c.save
=> #<Contact:0x7f1300eeb280 @attributes={"updated_at"=>Tue Dec 09 22:09:58 +0000 2008, "_rev"=>"3194024490", "_id"=>"26e09404188d8bce0b5081e653d137b4", "type"=>"Contact", :first_name=>"Geoffrey", "created_at"=>Tue Dec 09 22:09:58 +0000 2008, :last_name=>"Grosenbach"}, @database_name="address_book">

Oooh, nice, it actually works!

Couchview utility

Couchrest comes with a nice utility called Couchview which lets us create views locally and push them to the database. It's quite neat to be able to store them in our db directory like we do with migrations on relational databases.

couchview generate db/views contacts
Writing db/views/lib.js
Writing db/views/contacts/sample-map.js
Writing db/views/contacts/sample-reduce.js
Writing db/views/contacts/lib.js

It creates a sample map and a sample reduce for me. I'm actually going to write my own map for finding contacts using their last name as the key. The file is saved as db/views/contacts/by_last_name-map.js

function(doc) {
  if (doc.type == "Contact") {
    emit(doc.last_name, doc);
  }
}

Please note that double quotes are important. It breaks with single quotes.

If i were interested in knowing how many of my contacts have the same last name, i could now write a reduce to count up the results returned by the map. But for now, i only want the map.

Push this to the database like so:

couchview push db/views/contacts address_book
Pushing views from directory db/views/contacts to database http://localhost:5984/address_book
creating _design/contacts

Sure enough, here is my view, showing my one contact:

A CouchDB design view

Let's make a Rails website, shall we?!

Okay, so a contacts controller could now use this view to find and show my contacts.

class ContactsController < ApplicationController
  def index
    @contacts = Contact.view(database_name, 'contacts/by_last_name-map')
  end
end

A little tip: i defined database_name in the application controller just to return 'address_book' for now.

Here is app/views/contacts/index.html.erb

<h1>Contacts</h1>
 
<% @contacts.rows.each do |contact| -%>
  <h2><%= contact.full_name %></h2>
<% end -%>

Notice you have to get the rows out of the @contacts. If you raise @contacts.inspect you'll see why.

Where did full_name come from, you ask? I just defined it in the Contact model:

  def full_name
    "#{first_name} #{last_name}"
  end

But what good is a Christmas card list without addresses? Let's edit our contact and put in an address. It is nice and RESTful so long as you add a resource as usual in your config/routes.rb

map.resources :contacts

Then put this in the view:

<p><%= link_to 'edit', edit_contact_path(contact) %></p>

Here is the controller action:

  def edit
    @contact = Contact.find(database_name, params[:id])
  end

The edit view:

<h1>Edit <%= @contact.full_name %></h1>
 
<% form_for @contact do |f| -%>
  <%= f.hidden_field '_rev' %>
 
  <p><%= f.label :first_name %>
  <%= f.text_field :first_name %></p>
 
  <p><%= f.label :last_name %>
  <%= f.text_field :last_name %></p>
 
  <p><%= f.label :address %><br />
  <%= f.text_area :address, :rows => 4 %></p>
 
  <p><%= f.submit :save %></p>
<% end -%>

Notice i added in the _rev as a hidden field. I believe CouchDB needs to know that, but i'm not entirely sure.

Able to load and edit a contact

The update action simply needs to find the record, update it, and return to the index page.

  def update
    @contact = Contact.find(database_name, params[:id])
    @contact.save(params[:contact])
    redirect_to(contacts_path)
  end

I can now output the address on my index page:

<h1>Contacts</h1>
 
<% @contacts.rows.each do |contact| -%>
  <h2><%= contact.full_name %></h2>
  <p><%= simple_format(contact.address) %></p>
  <p><%= link_to 'edit', edit_contact_path(contact) %></p>
<% end -%>

Just quickly, the new and create actions:

  def new
    @contact = Contact.new(database_name)
  end
 
  def create
    contact = Contact.new(database_name)
    contact.save(params[:contact])
    redirect_to(contacts_path)
  end

And here we have two contacts!

Index of my contacts

There, that wasn't particularly hard. I definitely think the CouchRest and BasicModel are the easiest method i have tried so far. I also really like the method of creating views in files and pushing them to the database. Very neat!

Please note, the addresses used in these examples are easily found on the web! I'm not giving away any secrets!

Update: I have the pushed the code so far to GitHub, as couchdb_address_book. Feel free to take it and use it for your own purposes.

Update 14th December 2008: I did it! I printed my labels and wrote all my Christmas cards! Now i can go on holiday! Although they're not part of the "CouchDB on Rails" series, see these two related posts if you're curious how i did it!

Posted: December 10th, 2008
Categories: couchdb, ruby on rails
Comments: View Comments.
  • "Now, let me think. Who do i want to send a Christmas card to?!"
    How charming :P

    Thank you for this brilliant series of articles.
  • Yap Thank you very much for response,do update the changes.
  • Your site is very useful and nicely done!
    Thank you very much for your work.
  • Yup - sounds about right - 99 hours to do it in 1 hour :-)
  • Thought I would revisit this to compare how Im doing. Well better thasn I thought. THXs
  • I wondering how to get around doing this. Thanks!
  • indeed this is a very good tutorial. thx
  • Great tutorial, thanks, it helps a lot
  • nice tutorials , work with my projects.
  • I worked on above Trying out couchrest and topfunky's basic_model.I followed the steps.I could write a Model and got the view, but i could not push the view in to database(couchdb).Please send me the solution.
  • dave8291
    thanks for a great post! very helpful

    How to quit smoking
  • dave8291
    Thanks for sharing.. a really helpful article!
  • Will you be doing a PDF version of it?
  • Excellent tutorial. Been trying to figure something of this caliber for quite some time. Thanks!
  • I got this error..NameError: uninitialized constant CouchRest::FileManager
  • I was also getting the same error as Upender was getting but its absolutely fine now. Thanks for the great tutorial.
  • That's a good tutorial. Thanks for it. Looking forward to see more from you
  • I'm glad you have this tutorial, as this can be very complicated stuff.
  • tremorlouis
    It's good that it worked out and really
    IVF-Fertility for it. Hope to get more prior updates soon.
  • This idea seems interesting. I used it & its working wonderfully. Will glad to know the updates. Thanks.
  • I followed the steps.I could write a Model and got the view, but i could not push the view in to database(couchdb).Please send me the solution.
  • Any idea how to make a mapreduce viewserver working with couchdb directly via erlang message passing or via http, not thru a stdin/stdout?
    You probably know Reia, scripting language on erlang VM, it fits nice for a viewserver, expressions can be twice shorter than javascript's, performance can be better, but stdin/stdout interface stomps all the berries.
    There are rumors that there were plans for direct erlang interface for viewservers, but i was unable to find anything related on CouchDB's JIRA
  • Some very detailed work there. Keep up the good work on CouchDB.
  • Thanks for the article is was very interesting
  • thanks a million. Really worth reading. I had few problems setting up my database, but the tips were really helpful. You should also make a pdf format so that people can download. I recommend everyone to read this tutorial.
blog comments powered by Disqus