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')
=> #
>> c.save
=> #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')
=> #"Geoffrey", :last_name=>"Grosenbach"}, @database_name="address_book">
>> c.save
=> #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

Contacts

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:

Here is the controller action:

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

The edit view:

Edit


4 %>

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:

Contacts

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!

37 comments on “CouchDB on Rails (part 8 of ?)

  1. Great tutorial – but I’m still not too clear on how to integrate attachments with any of the couch/active*/rest-frameworks: Let’s imagine, that you want to store a picture of grandma along with her contact-info – how exactly would I do that? Sadly, each and every tutorial I have seen stops at exactly the point where it actually starts to be useful to use CouchDB instead of a plain mysql…

  2. @Geoffrey – thanks! I hope you didn’t mind me putting your address in there. Hopefully you will receive your Christmas card in time, haha!

    @Stf – Yes, you can upload attachments. I don’t think it’s too hard. I recommend the PeepCode CouchDB screencast which includes adding attachments (along with a lot more excellent information). You could probably also glean the information you’re looking for from the source code: peepcode-couchdb-code

  3. I followed the steps given in couchrest model,but i couldn’t push the views into the couchdb.

    Please help me.
    Give us an example if any.

  4. Hi Upender,

    Look very carefully where you are saving the file, and what you call it. These things seem to matter. Also, ensure you use double quotes in the javascript because single quotes break the push.

    Make sure your CouchDB server is running and do the command

    couchview push db/views/contacts address_book
  5. 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.

  6. Thank you for response.I restarted the same process again.when i am saving the
    c = Contact.new(‘address_book’)
    => #
    >> c.save

    I got the following error

    NameError: uninitialized constant CouchRest::FileManager
    from /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.2/lib/active_support/dependencies.rb:276:in `load_missing_constant’
    from /usr/lib/ruby/gems/1.8/gems/activesupport-2.1.2/lib/active_support/dependencies.rb:468:in `const_missing’
    from /root/Upender/address_book/vendor/plugins/basic_model/lib/basic_model.rb:48:in `db’
    from /root/Upender/address_book/vendor/plugins/basic_model/lib/basic_model.rb:114:in `save’
    from (irb):3

    I reinstalled the sam gems again.It would be very nice if you help me.Thank you

  7. hi Geoffrey,Aimee

    I want to develop a Document Management System(DMS) using rails.
    The DMS must be able to store files with the metadata and should have searchable tags to search the data.
    Is this possible through Couchdb at this point of time.
    I want to know the best way to implement this
    would you suggest me the possible solution.

  8. Ran into this as well, could I check out an early revison of the gem to get it working? Would you happen to know the sha?

  9. Thanks for this article (and thanks to Geoffrey for basic_model, it's just the right amount of abstraction). I almost gave up trying CouchDB on Rails until reading this, and now I'm super psyched. I think the main thing I'm missing from ActiveRecord is validation, to ensure I don't put trash into my beautifully delicate schemaless database. Time for a fork.Cheers

  10. 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

  11. 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

  12. hey schizobuddy this is the site that help in sure thing if you want really want some help try google because by Google you will get best result in no time.Thanks and Regards

  13. I almost gave up trying CouchDB on Rails until reading this, and now I'm super psyched. I think the main thing I'm missing from ActiveRecord is validation, to ensure I don't put trash into my beautifully delicate schemaless database. Time for a fork.

  14. I almost gave up trying CouchDB on Rails until reading this, and now I'm super psyched. I think the main thing I'm missing from ActiveRecord is validation, to ensure I don't put trash into my beautifully delicate schemaless database.

  15. “Now, let me think. Who do i want to send a Christmas card to?!”How charming :PThank you for this brilliant series of articles.

Leave a reply to Cars Lights Cancel reply