New Couch Potato: simple, testable, opinionated.
After my talk about Ruby CouchDB frameworks at Scotland on Rails where I dismissed a few of of the libraries available (including my own Couhch Potato) as not fitting the CouchDB way of doing things, I have been hacking away the past few weeks working on a complete overhaul of Couch Potato.
As a first result I have just released version 0.2 of the framework. Its new goals are simplicity, embracing the CouchDB semantics and testability. In order to achieve this I had to introduce some major changes:
I disconnected models from the database – there are no more save/get/find methods in the models. Instead you can hand the models to a database object that will save/load documents for you – this allows for unit tests that are completely disconnected from the database
I have dropped associations and thrown away all the ActiveRecord like view creation/querying, replacing it with a new, more CouchDB like system. That new system is lighter, simpler and easier to extend.
The following paragraphs will show you how to work with the new Couch Potato.
Saving / loading models
As I said I have decoupled the models from the database, a model doesn’t have permanent access to the database anymore. Instead you instantiate a database object yourself and tell it to load or save a model object. This change isn’t so much about CouchDB as it is about testability. Having the database separated means you can now have true unit test of your models without talking to the database. Here is how you save/load models:
class Book
include CouchPotato::Persistence
property :title
end
CouchPotato::Config.database_name = ‘my_db’ # in Rails this is done for you
db = CouchPotato.database
book = Book.new :title => ‘The Passionate Programmer’ # good book
db.save! book # saves the book or raises an exception
db.load book._id # the original book
The database is responsible for running the model’s validations and lifecycle callbacks, saving the document to the database and afterwards setting the _id and _rev on the model. This way a lot of the persistence related logic is removed from the models making them more lightweight and most importantly easier to test (see below).
New Views
Overhauling of the views had two main goals:
- provide a simple and extensible way for saving/querying views that works the way CouchDB works
- since models don’t have access to the database anymore, find a new way to query them
Here is how you create and query a simple view:
class Book
view :by_title, :key => :title
property :title
end
db = CouchPotato.database
db.view Book.by_title # no parametters
db.view Book.by_title(:key => ‘The Passionate Programmer’, :descending => true) # just use any of the parameters CouchDB accepts
The way views work is now essentially reversed (inversion of control, rings a bell?). Instead of the view method calling the database the new view method creates a specification for a CouchDB view. The view is passed to the actual database in order to query CouchDB. Again this makes testing easier (see next section) but it also gives Couch Potato a clean interface in order to make creating views easier through abstractions. There is now a hierarchy of view specification classes that you can use to more easily create and query views. The above example used the CouchPotato::View::ModelViewSpec which is the default. This spec creates a view whose map function emits one (or an array of) attribute of the model and returns full documents.
Other view specs let you create more customized views, for example the RawViewSpec, which lets you define your own map/reduce functions and return the raw CouchDB results hash.
class Book
view :title_length, :type => :raw, :map => “function(doc) emit(doc.title.length, null)}”
property :title
end
db.view Book.title_length # returns something like {:rows => [{:key => 25}]}
For more examples see the Documentation in the CouchPotato::View::*ViewSpec classes.
Testing
As I have mentioned repeatedly decoupling the database from the models makes testing easier. With the new Couch Potato you can unit test your models without hitting the database once. First of all that makes your test lightning fast, and secondly it allows you to more easily test for example your lifecycle callbacks because you can call them directly passing them a stub or mock database.
Here’s an example: (with RSpec)
class Book
property :title
property :slug
end
describe Book, ‘create’ do
it ’should generate a slug’ do
book = Book.new :title => ‘The Passionate Programmer’
book.run_callbacks :before_create
book.slug.should == ‘the-passionate-programmer’
end
end
If you don’t like calling the run_callbacks method you can also use the actual database object but without a real connection to CouchDB. Couch Potato is based on the excellent CouchRest – a more low level CouchDB framework. The CouchPotato::Database constructor expects an instance of a CouchRest database which we can replace with a stub:
describe Book, ‘create’ do
it ’should generate a slug’ do
book = Book.new :title => ‘The Passionate Programmer’
db = CouchPotato::Database.new stub(‘couchrest database’, :save_doc => {})
db.save book
book.slug.should == ‘the-passionate-programmer’
end
end
For more details and examples please see the README, the RDocs and watch this blog. If you want to know all about Couch Potato I encourage you to read through its source code and specs – it’s not that much code actually. Note that all this is still work in progress and time will show how well all of this works. I would be happy to hear your feedback.
Tags: couchdb, couchpotato, framework, opensource, sor09




May 25th, 2009 at 20:30
I’m soooo confused. I found the docs for the old version, which allowed has_many and belongs_to. I’m coming from a relational background, so those made sense.
How do I set up hierarchical data without those? For example:
Warehouse has_many Boxes,
Box has_many Items, belongs_to Warehouse
Item belongs_to box
Would I create a Warehouse with attr :boxes and Box with attr :items and then save Warehouse as one document – or do I create as each warehouse, box and item as their own documents and somehow relate them? How would I do that?
There is now has_many in 0.2.* but there is a belongs_to. How does that work?
Thanks! I love the idea, I just can’t get my head around the structure or lack thereof.
May 25th, 2009 at 21:57
well, you have almost given the answer yourself. there is no one way of doing things, it all depends on your exact use case. that’s why there is no generic has_many feature anymore. you could either have your boxes inline, through a “foreign key” or whatever. and then you could load them via a special view that only loads certain attributes or a whole hierarchy of objects – it all depends. just find the way that fits your use case and then create one or views that fit your bill well. i’m afraid i can’t help you much more than that without knowing what you are trying to achieve.
May 29th, 2009 at 13:58
In custom_views.rb:
define_method view_name do |view_parameters = {}|
Is this a ruby1.9 thing?
I plan on making this gem compatible with 1.8.
May 29th, 2009 at 17:45
cool, thanks for that.
yes this is ruby 1.9. plus i’m using a few other 1.9 things like Hash#map which returns an Array in 1.8 but a Hash in 1.9
May 30th, 2009 at 01:46
Hi Alex,
I have a few questions about the direction of the project, and couldn’t tell if the following things were possible with the current version.
From what I can tell, defining a property as your own custom class simply stores an #{name}_id in your document that points to another document, which I am guessing you would need to save separately. Do you envision something similar to the CastedModel in couchrest that is not so much like a relational thing.
Also, I am wondering if you envision supporting more than one database per rails project. Like if you want each model to have it’s own named database.
Finally, the code looks very clean and easy to jump into, do you have a mailing list or google group setup for the project in case one might want to start contributing.
Thanks,
Peter
June 1st, 2009 at 05:38
Ok, so I got a sample app running and I now see that my custom class property is saved inside the parent doc, but no id or rev is added until you call save on it directly. Only strange thing is you now have duplicate ids, but not really a problem since one is not an actual document. This is how I had hoped it would work. Very nice.
Just wondering now what the best practice is for this capability. Set the property to null if you decide to promote it to it’s own document, or keep it around for easier access.
June 3rd, 2009 at 23:33
hi peter,
“From what I can tell, defining a property as your own custom class simply stores an #{name}_id in your document that points to another document”
nope, it converts the object to json and stores it inline. at this point i have no plans for supporting any kind of relational things. if you want something like a has_one relation you can implement a method that then calls the appropriate view to load the related documents.
you can easily use multiple databases per rails/sinatra/merb app. just create multiple instances of CouchPotato::Database and then each talks to a different database.
right now there is no google group or something like that but i have that planned. in the meantime there is the wiki and the issue tracker on github for some sort of communication. the easiest way to contribute for now is to just fork the repo on github and start adding some code. you are very welcome to do so
June 4th, 2009 at 00:36
Thanks alex,
Already submitted a few small patches and one issue. I also figured out what you said above after finally getting moved over to Ruby 1.9 and getting a sample couchpotato app up and running.
BTW, I agree with your decision not to define associations. Currently I am stuck between liking certain aspects of your project, and certain aspects of CouchRest. I’ll add a few more issues on github so you can see what I’m talking about. However, I really like the overall design of CouchPotato.
June 4th, 2009 at 14:24
Alrighty then… I’ve forked your repo and modified it to be compatible with ruby1.8 and ruby1.9.
I’ve also fixed an error that arises when you specify and entire URL in CouchPotato::Config.database_name. In short, I’ve added CouchPotato::Config.database_server which will default to http://localhost:5984/ unless specified.
http://github.com/thefool808/couch_potato
Also, I’ve written up a real quick readme on what it took to get ruby1.9, couchdb-0.9.0, and your version of couch_potato up and running on Debian Lenny. If you would like to post it up, I can email it to you.
Now to actually write an application!
June 4th, 2009 at 21:17
thanks i’ll check it out tomorrow. please send me the lenny howto, my email address can be found on the contact page.
June 5th, 2009 at 13:14
thefool808: i have merged most of your changes into master. thanks for porting this back to 1.8
June 13th, 2009 at 00:04
[...] nto completely there, but with some promising potential as an object mapper, according to him: article on couch potato)In a relational database you have structured data and unstructured queries which can be run ad hoc, [...]