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!

$ su
# gem install json
# exit
$ 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

Related posts (automatically calculated)

  1. CouchDB on Rails (part 5 of ?)
  2. CouchDB on Rails (part 6 of ?)
  3. CouchDB on Rails (part 1 of ?)
  4. CouchDB on Rails (part 2 of ?)
  5. CouchDB on Rails (part 7 of ?)

This entry was posted on Tuesday, September 9th, 2008 at 22:51 and is filed under couchdb. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

14 Responses to “CouchDB on Rails (part 4 of ?)”

  1. Austin Putman UNITED STATES Mac OS X Mozilla Firefox 3.0.1 Says:

    thanks for writing all this up — this kind of work could lead to some cool dbstore patterns

  2. Aimee UNITED KINGDOM Debian GNU/Linux Mozilla Firefox 2.0.0.16 Says:

    It is a pleasure.

  3. Jan GERMANY Mac OS X Safari 526.11.2 Says:

    Aimee, this is again, excellent, thanks for taking the time to write this, and keep it up!

    The find/view issue, in my opinion shows that AR is probably not the best model to use with CouchDB. I'm not saying it can't be made to work together, but I think we'd need to bend both parts to something they weren't designed to do. Why is that?

    The difference between CouchDB and an RDBMS (that AR is 'just' and object representation of) can be summed up as "RDBMS: Static data & dynamic queries vs CouchDB: Dynamic data & fixed queries". So providing dynamic queries is not exactly CouchDB's strength. This is not a bad thing though, you still can get all the info you need for your application out of CouchDB. Just in a different way. That makes AR probably not the first choice for an OO abstraction layer on top of CouchDB.

    To fix the API issues of ActiveCouch, see http://wiki.apache.org/couchdb/BreakingChanges for what has changed in CouchDB.

    Again, brilliant content, please continue!

    Cheers
    Jan

  4. SD CANADA Mac OS X Safari 525.20.1 Says:

    Great series of articles. You've hit a perfect balance between code examples and explanation which has really got me interested in investigating CouchDB. Thanks very much

  5. Aimee UNITED KINGDOM Mac OS X Mozilla Firefox 2.0.0.16 Says:

    @Jan … Thank you for your encouragement, and it is really great to hear your opinions. I'm not particularly impressed with ActiveCouch and i don't want to be the one to fix its issues. I have a feeling the RelaxDB Merb plugin will prove to be much more flexible and more suited to CouchDB.

    @SD … Thanks, it's lovely to hear your feedback. I'm glad to be getting other people interested in the Couch! :)

  6. Jan GERMANY Mac OS X Safari 526.11.2 Says:

    Jup, my comment definitely missed a "In case you want to fix AactiveCouch"… Sorry :-) (New ep tonight? :)

  7. Aimee UNITED KINGDOM Debian GNU/Linux Mozilla Firefox 2.0.0.16 Says:

    Yes, i'm getting on with some more CouchDB tonight. 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 …!

  8. Geoffrey Grosenbach UNITED STATES Mac OS X Mozilla Firefox 3.0.1 Says:

    Personally, I think some of these ActiveRecord-ish libraries are overkill. I wrote a simple 60-line wrapper for CouchRest that does most of what you need, and takes advantage of CouchDB's strengths (flexible schema, etc.)

    http://gist.github.com/10636

  9. Aimee UNITED KINGDOM Debian GNU/Linux Mozilla Firefox 2.0.0.16 Says:

    Excellent, thank you Geoffrey! I will take a look.

    I'm inclined to agree with you, but to get started i wanted something
    that feels familiar for me.

  10. Arun Thampi SINGAPORE Mac OS X Mozilla Firefox 3.0.1 Says:

    Hi Aimee - Just stumbled upon this blog post. I'm making changes to ActiveCouch this weekend to make it compatible with 0.8.x as well as more Rails-friendly (so a 'couchify' command which will automagically add config files for ActiveCouch to use in your Rails project, etc.)

    Been really busy at work, so haven't gotten a chance to catch up with CouchDB awesomeness.

    Stay tuned, though :)

  11. Aimee UNITED KINGDOM Debian GNU/Linux Mozilla Firefox 3.0.1 Says:

    Nice to hear from you, Arun. Good luck with the new updates - should be exciting! :)

  12. Arun Thampi SINGAPORE Mac OS X Mozilla Firefox 3.0.1 Says:

    Hi Aimee - Just an update, the master branch of ActiveCouch now supports 0.8.0 and upwards. I'm still working on the couchify command (as you may notice from the code) so please give it another go!

    Thanks for your feedback.

    Cheers,
    Arun

  13. Aimee UNITED KINGDOM Debian GNU/Linux Mozilla Firefox 3.0.1 Says:

    Nice one, thanks for the update! :)

  14. Arun Thampi SINGAPORE Mac OS X Safari 525.20.1 Says:

    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/

Leave a Reply