Saturday, August 8, 2009

CouchDocs: From the Command Line

‹prev | My Chain | next›

For a while now, couch_design_docs (extracted from my CouchDB / Sinatra application) has done more than play with CouchDB design documents. So the first thing I do today is to finally create a new couch_docs gem. It is copied directly from couch_design_docs. Now that I think about it, I probably should have forked it, but mostly the same result.

I reset the version number to 0.9. I will consider it 1.0 when I have a script to dump and load (backup and restore) a CouchDB database. So let's get started...

My couch-docs script (mostly generated by Bones):
#!/usr/bin/env ruby

require File.expand_path(
File.join(File.dirname(__FILE__), %w[.. lib couch_docs]))

# Put your code here ARGV

I do not expect to do much in the CommandLine, but, all the same, I create an instance—encapsulation helps once the command line gains any complexity. The run class method should instantiate and run a command line, or, in RSpec format:
describe CommandLine do
it "should be able to run a single instance of a command line" do
with('foo', 'bar').
and_return(mock("Command Line").as_null_object)'foo', 'bar')

it "should run the command line instance" do
command_line = mock("Command Line").as_null_object

CommandLine.stub!(:new).and_return(command_line)'foo', 'bar')
Those examples drive this implementation:
module CouchDocs
class CommandLine

def initialize(args)
With the preliminaries out of the way, it is time to drive the command line to actually do something. I want two different command line options:
# For dumping the contents of a CouchDB database to the filesystem
couch-docs dump "http://localhost:5984/db" path/to/dump_dir/

# For loading documents from the filesystem into CouchDB
couch-docs load path/to/dump_dir/ "http://localhost:5984/db"
To run the dump version:
  context "an instance that dumps a CouchDB database" do
before(:each) do
@it ='dump', 'uri', 'dir')

it "should dump CouchDB documents from uri to dir when run" do
with("uri", "dir")
When I execute that example, I get:
NoMethodError in 'CouchDocs::CommandLine a "dump" instance should dump CouchDB documents from uri to dir when run'
undefined method `run' for #

Finished in 0.019961 seconds
I change the message by defining an empty run method, to find:
Spec::Mocks::MockExpectationError in 'CouchDocs::CommandLine an instance that dumps a CouchDB database should dump CouchDB documents from uri to dir when run'
CouchDocs expected :dump with ("uri", "dir") once, but received it 0 times
To make that pass, I squirrel away the command and options in instance variables and use them in the run method to call the dump class method on CouchDocs:
    attr_accessor :command, :options

def initialize(*args)
@command = args.shift
@options = args

def run
case command
when "dump"
raise"Unknown command #{command}")
After following a similar path with the load option, I am done with the couch-docs script!

After installing the gem locally, the script is in my $PATH, I give it a try with the seed data that I dumped yesterday. Unfortunately:
cstrom@jaynestown:~/repos/eee-code$ couch-docs load couch/seed http://localhost:5984/seed
/usr/lib/ruby/gems/1.8/gems/rest-client-1.0.3/lib/restclient/request.rb:193:in `process_result': Resource not found (RestClient::ResourceNotFound)
from /usr/lib/ruby/gems/1.8/gems/rest-client-1.0.3/lib/restclient/request.rb:125:in `transmit'
from /usr/lib/ruby/1.8/net/http.rb:543:in `start'
from /usr/lib/ruby/gems/1.8/gems/rest-client-1.0.3/lib/restclient/request.rb:123:in `transmit'
from /usr/lib/ruby/gems/1.8/gems/rest-client-1.0.3/lib/restclient/request.rb:49:in `execute_inner'
from /usr/lib/ruby/gems/1.8/gems/rest-client-1.0.3/lib/restclient/request.rb:39:in `execute'
from /usr/lib/ruby/gems/1.8/gems/rest-client-1.0.3/lib/restclient/request.rb:17:in `execute'
from /usr/lib/ruby/gems/1.8/gems/rest-client-1.0.3/lib/restclient.rb:65:in `get'
from /home/cstrom/.gem/ruby/1.8/gems/couch_docs-0.9.0/lib/couch_docs/store.rb:55:in `get'
from /home/cstrom/.gem/ruby/1.8/gems/couch_docs-0.9.0/lib/couch_docs/store.rb:50:in `delete'
from /home/cstrom/.gem/ruby/1.8/gems/couch_docs-0.9.0/lib/couch_docs/store.rb:38:in `delete_and_put'
from /home/cstrom/.gem/ruby/1.8/gems/couch_docs-0.9.0/lib/couch_docs/store.rb:34:in `put!'
from /home/cstrom/.gem/ruby/1.8/gems/couch_docs-0.9.0/lib/couch_docs.rb:45:in `put_document_dir'
To track this down, I check the CouchDB logs to find:
[info] [<0.14094.13>] - - 'PUT' /seed/2002-01-12-squash_ravioli 409
Ah, nuts! The dump from yesterday includes the revision number of the document. That revision number is causing a conflict in the DB. I will need to revisit the dump to strip out that revision number.

I will call it a day at that point. In addition to stripping the revision from the document when it gets dumped to the file system, I also need to include the image attachments.


  1. HI,

    you have seen couchapp*, have you? :)



  2. For sure.

    I mostly wanted something that can work with Ruby, since my app is written in Sinatra / Ruby. Why add another dependency in my app if I can avoid it?

    Although there is some overlap in functionality, I am mostly looking to manage documents, not manage an application.