Monday, July 13, 2009

CouchDesignDocs Gem Ready for Real-World Use

‹prev | My Chain | next›

Before the couch_design_docs gem is ready for prime-time (i.e. ready to be used in my Sinatra / CouchDB app), I want a simpler API than is currently exposed. Currently I need to instantiate both a Store object (to describe the target CouchDB store) and a Directory object (to describe the design documents stored on the file system). The Store object then needs to upload a hash representation of the Directory object. I would much prefer a single method to upload a design documents directory to a CouchDB store. Something like:
CouchDesignDocs.upload_dir(URI, DIR)
This convenience method should instantiate Store and Directory objects just I have to by-hand now. The instantiated Store object should receive the :load message with an argument of the hash from Directory. As an RSpec example:
describe CouchDesignDocs do
it "should be able to load directory/JS files into CouchDB as design docs" do
store = mock("Store")
Store.stub!(:new).and_return(store)

dir = mock("Directory")
dir.stub!(:to_hash).and_return({ "foo" => "bar" })
Directory.stub!(:new).and_return(dir)

store.
should_receive(:load).
with({ "foo" => "bar" })

CouchDesignDocs.upload_dir("uri", "fixtures")
end
end
The example fails with
cstrom@jaynestown:~/repos/couch_design_docs$ spec spec/couch_design_docs_spec.rb
F...........

1)
NoMethodError in 'CouchDesignDocs should be able to load directory/JS files into CouchDB as design docs'
undefined method `upload_dir' for CouchDesignDocs:Module
./spec/couch_design_docs_spec.rb:16:

Finished in 0.011794 seconds

12 examples, 1 failure
I change the message by defining the method. I change the next message ("wrong number of arguments (2 for 0)") by defining the method with two arguments:
  def self.upload_dir(db_uri, dir)
end
With that I get this failure:
cstrom@jaynestown:~/repos/couch_design_docs$ spec spec/couch_design_docs_spec.rb
F...........

1)
Spec::Mocks::MockExpectationError in 'CouchDesignDocs should be able to load directory/JS files into CouchDB as design docs'
Mock 'Store' expected :load with ({"foo"=>"bar"}) once, but received it 0 times
./spec/couch_design_docs_spec.rb:12:

Finished in 0.012137 seconds

12 examples, 1 failure
I make that example pass by defining the methods as I planned from the beginning:
  def self.upload_dir(db_uri, dir)
store = Store.new(db_uri)
dir = Directory.new(dir)
store.load(dir.to_hash)
end
After updating the README and the History.txt file, I create an updated gemspec with the built-in Bones rake command:
rake gem:spec
Then I upload to github.
(commit)

Finally, I replace the code from which the couch_design_docs gem was extracted with a call to the gem:
Before do
# For mocking & stubbing in Cucumber
$rspec_mocks ||= Spec::Mocks::Space.new

# Create the DB
RestClient.put @@db, { }

# Upload the design documents with a super-easy gem :)
CouchDesignDocs.upload_dir(@@db, 'couch/_design')

end
Nice! At one point, that Before block was well over a hundred lines long, mostly because the design documents were embedded directly in that block. Now it is a single line.

To make sure that everything is still working, I break the design docs out into the component .js files:
cstrom@jaynestown:~/repos/eee-code$ find couch/
couch/
couch/_design
couch/_design/lucene
couch/_design/lucene/transform.js
couch/_design/recipes
couch/_design/recipes/views
couch/_design/recipes/views/by_date
couch/_design/recipes/views/by_date/map.js
couch/_design/meals
couch/_design/meals/views
couch/_design/meals/views/by_month
couch/_design/meals/views/by_month/map.js
couch/_design/meals/views/by_month/reduce.js
couch/_design/meals/views/by_date
couch/_design/meals/views/by_date/map.js
couch/_design/meals/views/count_by_month
couch/_design/meals/views/count_by_month/map.js
couch/_design/meals/views/count_by_month/reduce.js
couch/_design/meals/views/count_by_year
couch/_design/meals/views/count_by_year/map.js
couch/_design/meals/views/count_by_year/reduce.js
couch/_design/meals/views/by_year
couch/_design/meals/views/by_year/map.js
couch/_design/meals/views/by_year/reduce.js
Running the various Cucumber scenarios that exercise the full-stack, including the CouchDB views/design documents as well as the couchdb-lucene design document, I find no failures:
27 scenarios
9 skipped steps
1 undefined step
226 passed steps
Say, that's a really convenient gem, it'd be nice to have easy access to it from the command line! I add it to my Rakefile thusly:
task :load_design_docs do
CouchDesignDocs.upload_dir("http://localhost:5984/eee", "couch/_design")
end
That winds up work on the couch_design_docs gem—an excursion made quite enjoyable thanks to Bones. I still have some features that I would like to add (ability to unit test the .js files, Javascript function re-use via Erb includes), but it serves my purposes really well as-is.

Up tomorrow: figuring out where I was before I decided extracting a gem might be a good idea.

No comments:

Post a Comment