CouchDB on Rails (part 4 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

A grand moment has come. It is time to attempt to get Rails talking to my CouchDB database.

I have come across four plugins that i intend to try out. ActiveCouch, RelaxDB, couchrest and couchobject. They are all hosted on GitHub because GitHub is made of love.

Today i am going to try ActiveCouch. My main reason for picking it first is that it seems to be inspired by ActiveRecord. It is quite evidently intended for Rails. It seems quite close to what i'm used to. RelaxDB may also be, but it is written for Merb so i may run into troubles that are simply differences between Rails and Merb.

I have some doubts about ActiveCouch, particularly that it hasn't been updated since July. It also seems to have some odd syntax … but we'll see how it goes.

A new Rails app

Nothing unusual to begin with …

rails cd_collection
cd cd_collection
git init

Including RSpec comes as second nature to me now … i have been well trained!

git submodule add git://github.com/dchelimsky/rspec.git vendor/plugins/rspec
git submodule add git://github.com/dchelimsky/rspec-rails.git vendor/plugins/rspec-rails
./script/generate rspec

So i'm guessing the next thing to do is include ActiveCouch as a submodule too.

git submodule add git://github.com/arunthampi/activecouch.git vendor/plugins/activecouch

Looking at the README i see that i need a json gem, which i don't think i have. There are some other requirements too, so if you're following along at home, make sure you have them all!

sudo gem install json
gem list

I ran 'gem list' to confirm that i now have the json gem, version 1.1.3.

ActiveCouch spec tests

So i presumably want to run the specs that are included with ActiveCouch. I tried this:

cd vendor/plugins/activecouch
rake

… which was probably the wrong thing to do! It has created a pkg directory and zipped the whole lot up into a gem, a tgz and a zip file. Not exactly what i had in mind! What i should have done was check what the Rakefile actually does!

rake -T
rake clobber_package  # Remove package products
rake gem              # Build the gem file activecouch-0.1.9.gem
rake package          # Build all the packages
rake repackage        # Force a rebuild of the package files

So i think i'll go ahead and clobber the package! But how do you run the specs?

ruby spec/connection/initialize_spec.rb
...
 
Finished in 0.065529 seconds
 
3 examples, 0 failures

Well, that's a good start! But surely i don't have to go through every file one at a time? This is not filling me with joy. Unfortunately i am revealing a gap in my knowledge. I have never run specs on a plugin before, and i don't know how to write a Rake file to do it.

Well this is a little bit hacktastic, but let's give it a go …

# vendor/plugins/activecouch/spec/runner.rb
files = File.join(File.dirname(__FILE__), "*/*.rb")
Dir[files].each do |file|
  puts `ruby #{file}`
end

Great! Loads of stuff fails! Stuff like "Failed with 500 Internal Server Error". I have ensured that the CouchDB is running on port 5984, as the specs expect. What about "Error creating database – got HTTP response 200". If it can't even create a database, that would explain a lot of the other errors. Here's an odd one:

'ActiveCouch::Base #after_save method with an Object (which implements after_save) as argument should call before_save in the object passed as a param to after_delete' FAILED
expected: true,
     got: false (using ==)

Oh dear, it's not looking good for ActiveCouch. I am discouraged by a comment i've found in the STATUS file: "Version 0.2.0 and upwards will support CouchDB 0.8.0 and upwards." – i am on CouchDB version 0.8.1. I can't find a version 0.2.0 of ActiveCouch anywhere. I'm guessing CouchDB has moved on a lot and ActiveCouch has failed to keep up.

Still, on the bright side, quite a lot of the specs did actually pass. I'm determined to get at least something working so that Part 4 is not a total disaster!

I know what, i'll create a model, but instead of inheriting from ActiveRecord::Base i will inherit from ActiveCouch::Base.

# app/models/cd.rb
class Cd < ActiveCouch::Base
end

Now let's see what it can do.

script/console FTW

./script/console
Loading development environment (Rails 2.1.0)
>> cd = Cd.new
=> #<Cd:0x2b852ffcbfb8 @associations={}, @attributes={:_id=>nil, :_rev=>nil}, @callbacks={}, @connection=nil>

Interesting! I have a CD document! It has the id and rev attributes that i saw last night in the CouchDB web interface. I wonder whether i can add some fields to it.

>> cd.title = 'Peaceful, The World Lays Me Down'
NoMethodError: undefined method `title=' for #<Cd:0x2b852ffcbfb8>

Useless. That's the kind of error i'd expect from ActiveRecord when it can't find a field in the database. The whole point of CouchDB is it's document-oriented and you can put more-or-less whatever fields you like into each document. But wait a minute, here's an idea:

class Cd < ActiveCouch::Base
  has :title
end

Now let's try.

>> cd = Cd.new(:title => 'Peaceful, The World Lays Me Down')
=> #<Cd:0x2b9f6de3e120 @callbacks={}, @associations={}, @attributes={:_id=>nil, :_rev=>nil, :title=>"Peaceful, The World Lays Me Down"}, @connection=nil>

Better. So we have to define our attributes in the model. I can live with that.

>> cd.save
NoMethodError: You have a nil object when you didn't expect it!
The error occurred while evaluating nil.post

Yeah, i wasn't really expecting that to work! I haven't told Rails anything at all about my database! Normally we'd do it in config/database.yml of course, but i don't know how to set up a CouchDB adapter. Fortunately i do know how to trawl through specs (in the absence of any documentation) to look for clues!

class Cd < ActiveCouch::Base
  site 'http://localhost:5984/'
  has :title
end

Please forgive the hard-coding for the moment! Now, let's try again.

>> cd = Cd.new(:title => 'Peaceful, The World Lays Me Down')
=> #<Cd:0x2b8a2c7174b0 @callbacks={}, @associations={}, @attributes={:_id=>nil, :_rev=>nil, :title=>"Peaceful, The World Lays Me Down"}, @connection=#<ActiveCouch::Connection:0x2b8a2c71e6c0 @site=#<URI::HTTP:0x2b8a2c71e3f0 URL:http://localhost:5984/>>>
>> cd.save
ActiveCouch::ResourceNotFound: Failed with 404 Object Not Found

Ah, it helps to watch the log of CouchDB as we do this.

[info] [<0.602.0>] HTTP Error (code 404): not_found
[info] [<0.602.0>] 127.0.0.1 - - "POST /cds" 404

ActiveCouch seems to have made an assumption that the database is called cds. I'm not sure i'm very happy about this. Does this mean there is a separate database per model? Surely that's non-optimal. Am i misunderstanding something here? Well, let's try creating the database and see if it keeps ActiveCouch happy.

>> ActiveCouch::Migrator.create_database('http://localhost:5984/', 'cds')
=> true

The CouchDB log indicates that the database was created:

[info] [<0.603.0>] 127.0.0.1 - - "PUT /cds" 201

Now maybe we'll have some success.

>> cd.save
=> true
>> cd
=> #<Cd:0x2b339ae93ce0 @callbacks={}, @associations={}, @attributes={:_id=>"29dc9e093c369e13c05c87baf5be00f8", :_rev=>"3659454517", :title=>"Peaceful, The World Lays Me Down"}, @connection=#<ActiveCouch::Connection:0x2b8a2c71e6c0 @site=#<URI::HTTP:0x2b8a2c71e3f0 URL:http://localhost:5984/>, @default_header={"Content-Type"=>"application/json"}>>

Good. It has generated a UUID and a revision for me, so i guess this has been saved.

Sure enough, i can now see the document through the CouchDB web interface. Apparently i am winning!

A document saved to CouchDB from Rails

Finding documents

Unfortunately i can't seem to find a CD again after i created it.

>> Cd.find(:all)
NoMethodError: You have a nil object when you didn't expect it!
The error occurred while evaluating nil.keys
>> Cd.find(:first)
NoMethodError: You have a nil object when you didn't expect it!
The error occurred while evaluating nil.keys
>> Cd.find(:last)
=> nil
>> Cd.find_by_title("Peaceful, The World Lays Me Down")
NoMethodError: undefined method `find_by_title' for Cd:Class

LOL. ActiveRecord this is not!

I think this would work, if i had a view:

>> Cd.find(:all, :params => {:title => "Peaceful, The World Lays Me Down"})
ActiveCouch::ResourceNotFound: Failed with 404 Object Not Found

See what it's trying to do:

[info] [<0.621.0>] HTTP Error (code 404): {not_found,missing}
[info] [<0.621.0>] 127.0.0.1 - - "GET /cds/_view/by_title/by_title" 404

Fine, i'll create the view. I don't know how to do it with ActiveCouch so i'll just do it through the web interface.

Created a view to find CDs by title

Well, that's good. Yesterday i didn't think you could create views with underscores in the ID. So let's try again.

>> Cd.find(:all, :params => {:title => "Peaceful, The World Lays Me Down"})
=> [#<Cd:0x2b339ae67fa0 @callbacks={}, @associations={}, @attributes={:_id=>"29dc9e093c369e13c05c87baf5be00f8", :_rev=>"3659454517", :title=>"Peaceful, The World Lays Me Down"}, @connection=#<ActiveCouch::Connection:0x2b339ae9aef0 @site=#<URI::HTTP:0x2b339ae9ac20 URL:http://localhost:5984/>, @default_header={"Content-Type"=>"application/json"}>>]

Hey, can we find by ID?

>> Cd.find('29dc9e093c369e13c05c87baf5be00f8')
=> [#<Cd:0x2b339ae67fa0 @callbacks={}, @associations={}, @attributes={:_id=>"29dc9e093c369e13c05c87baf5be00f8", :_rev=>"3659454517", :title=>"Peaceful, The World Lays Me Down"}, @connection=#<ActiveCouch::Connection:0x2b339ae9aef0 @site=#<URI::HTTP:0x2b339ae9ac20 URL:http://localhost:5984/>, @default_header={"Content-Type"=>"application/json"}>>]

Excellent! This is a bit more promising.

So let's scaffold up a web interface

# app/controllers/cds_controller.rb
class CdsController < ApplicationController
  def show
    @cd = Cd.find(params[:id])
  end
end

And of course, the view to go with it …

<!-- app/views/cds/show.html.erb -->
<h1>A CD in CouchDB</h1>
<p>
  <strong>Title:</strong> <%= @cd.title %>
</p>

I'll need to declare a resource in config/routes.rb …

map.resources :cds

Come on then, fingers crossed …

Rails using CouchDB!

My gosh, it worked!! :D Yeah yeah, i know it doesn't look like much, but it has connected to the database, found the document, loaded it into ActiveCouch and output it for me.

My verdict

So i have proved that you can do it. Hurrah! But i don't like the way i had to do it. I don't like having the database named after the model (what if, God forbid, you have more than one model?!) I don't like having to create view called by_title/by_title if i want to search by title. I don't know why i can't do Cd.find(:all).

Of course, i may have got it entirely wrong and maybe somebody who knows about ActiveCouch will be able to tell me exactly how i should have done it. But for now, i've gone as far as i want to go. I've shown how you can do it, and if you're interested you could probably take it further. But stay tuned, because i think there are better things yet to come in this series …! :)

Thank you if you stuck with me through this long and rambling exploration of ActiveCouch!

Part 5: Integrating with Rails using RelaxDB

Posted: September 9th, 2008
Categories: couchdb
Comments: View Comments.
  • motorcycleleatherjackets
    oooopss....mind blowing...but very informative...thanks to share...
  • Really great blog and do the great work on this blog thanks for share information with us.
  • bumperstickers
    Great work on this blog thanks for share this information this information is really helpful for me.
  • i wish i could also create just a simple program in the future..i hate coding:-(

    Rose
    computer keeps freezing
  • you can but codes are still basics in programming.lol
  • Great job in your post here! I really have enjoyed reading this very much. will bookmark, thanks for sharing!

    www.jermainelovepoems.com

    www.facebooklogin.us

    Facebook Login
    Love Poems

  • Another great post. Always a pleasure to read them.
  • I feel i'm in a race with TopFunky to see if i can get my next part out before the PeepCode! :) Although i'm really very interested to see what Geoffrey has to say about CouchDB and Rails.
  • I feel i'm in a race with TopFunky to see if i can get my next part out before the PeepCode! :) Although i'm really very interested to see what Geoffrey has to say about CouchDB and Rails!
  • Thanks for all your exploration into this for us all and posting your experiences, I'm sure for people wanting the same things you want you are saving them a bunch of headaches. I'm going to continue on reading the rest of your entries!

    Craig - Full Tilt referral code
  • That's some of the good ol' fashioned cobbled code I like to see! Fantastic job running with this project and keeping it going, I hope the rewards you reap are worth it. Cheers!
  • dave8291
    Great info here.. thanks for taking the time to write it all out in detail.
  • I always had problems to get this right, however, your posts helped me to get an better understanding.
  • It is not uncommon to have to deal with several constant stores in a single application. A very common approach is to use a relational database that stores paths pointing to files that are stored in a file system.
    So you might think as CouchDB as a special "file system" for a special part of your data model.
    Also, in larger applications, multiple stores and complex physical architectures are quite common, so don't be shy of using more than one persistent store for your models.
    faxless payday loan
  • Great post.
    Thanks for the effort to write all this up.
    Seems easy enough to follow thanks to the great instructions.
    Great plugins
  • This is great! Been trying to figure out the CouchDB on rails issue for quite some time. Thanks
  • Aimee,

    Another great post. Always a pleasure to read them.

    Bod
  • HBC
    I concur, it has taken me a while to find these informative blogs but they are well worth the lokking
  • simonly
    I totally agree. Fantastic post yet again.

  • I understand what you meant. You've managed to state the facts clearly and you explained it well. Thanks for your post!
  • thanks for writing all this up
  • Ariel, you are very welcome. I'm sure it's obvious that they are very much exploratory on my part, but i'm glad to know that they are of help to other people.
  • Aimee,

    Many many many thanks for these brillant posts.

    Ariel
  • Hi Aimee - I've updated the ActiveCouch wiki where you can read about the latest changes to ActiveCouch and how it integrates much better with Ruby On Rails:

    http://mclovindoesruby.wordpress.com/2008/10/26/how-to-use-couchdb-with-ruby-on-rails/
  • Nice one, thanks for the update! :)
blog comments powered by Disqus