It's been just over 5 months since I started playing with CouchDB. Until a few days ago I didn't have much time to explore it, but since Christmas I've been playing around with it almost constantly, seeing what it can do and experimenting with it in my favourite language, Ruby.

Since I hadn't used Ruby to work with CouchDB before I decided to pick up ActiveCouch. It's an excellent library, but after playing for a few days I found that it worked with CouchDB in ways that I don't think I would. That could be down to my inexperience with using CouchDB, or it could just be that how you use CouchDB depends on how you think. Either way, I pushed a copy of ActiveCouch onto my server and began hacking at it to make it play the way that I wanted it to.

One application, one database

As it stood, ActiveCouch used one database per class. People went into the people database, comments went into the comments database, articles went into the articles database. My approach to using CouchDB is to store all application data in one database and differentiate between the document types by having a doc.type attribute.

ActiveCouch now also installs a set of views that can be used to access just the documents of one type. You should see these in the Futon client after you've run your application once.

Unknown functionality dropped

I broke ActiveCouch::Base#find_from_url while I was working. I didn't know what it was for, and I didn't use it in the application I was building, so I dropped it in 9982b348c. If you use this functionality please let me know what it's for!

Syntactic sugar

One of the goals of ActiveCouch is to act like ActiveRecord, and ActiveRecord provides #all and #first. I like them. ActiveCouch now provides them too.

New attribute types

Sometimes there's data that's too simple to merit a class and an association. I've added a new attribute type, :array. Simple tags, for example, are a perfect fit for this. The default value of these attributes is an empty array.

class Article < ActiveCouch::Base
  has :title, :which_is => :text
  has :tags, :which_is => :array
end

article = Article.new :title => "Sandwiches", :tags => [ "pickle" ]
article.tags << "cheese"
article.tags # => [ "pickle", "cheese" ]

I've also added a :datetime attribute type that defaults to Time.now.

Calculated default values

It's now possible to set a default value which is a calculation to be lazily executed ie it'll be worked out when the instance comes into existence rather than when the class is declared. You can do this be setting the default value to a proc (or anything else that responds_to?(:call)).

class Egg < ActiveCouch::Base
  has :hatches_at, :type => :datetime, :with_default_value => proc { 3.weeks.from_now }
end

The instance is yielded into the proc in case you'd like to do any calculations based on that.

Conversion to native Ruby types

When you declare a type for a document attribute, ActiveCouch now tries to convert the value from the document into a native Ruby type. For example, if you declared that your class had an attribute called created_at that was a datetime you'd now get an instance of Time when you access that attribute. Previously you'd have got a String.

class Person < ActiveCouch::Base
  has :birthday, :which_is => :datetime
end

Person.find(:first).birthday.class # => Time

Changes to associations and adding belongs_to

I've changed the has_many and has_one associations so that they don't appear in the document that declares them. These associations are declaring that other classes have keys which point at the current class and they a query is needed to fetch them.

To accomodate that change there's a new association, belongs_to, which says that the declaring class contains a key which points to an owning class.

class Pet < ActiveCouch::Base
  # This document will have a person_id attribute
  belongs_to :person
end

class Person < ActiveCouch::Base
  # Instances of this class will query the database for doc.type = "pet"
  # and doc.person_id = self.id
  has_many :pets
end

At the moment you have to set the association on the class that belongs_to. Setting it on the class that has_many won't work.

# BAD
craig.pets << cat

# GOOD
cat.person = craig

Views with multiple keys

It's now possible to have a view with more than one attribute as a key. Just use ActiveCouch::View#with_key more than once and each key you specify will be added to the view.

Design document with multiple views

The version of ActiveCouch that I checked out allowed only one view per design document. I think that was a bug since there was some code in there to merge in the existing view. I've fixed that and now design documents can have more than one view.

Finders have conditions, not params

It felt a little unnatural typing :params => { ... } when writing custom finders. ActiveRecord asks for :conditions, so now ActiveCouch does too.

Person.find(:all, :condtions => { :last_name => "Smith" })

Automatic view generation for custom finders

I don't really want to worry about writing and installing the views for a finder with conditions before I run it. Now, the first time you run a finder with conditions, ActiveCouch will generate and install the appropriate view for you.

Probably lots more too!

I've still got to clean up quite a few of my changes, make sure that the test coverage is meaningful enough, and write lots of documentation. I am using my altered version of ActiveCouch for an application so that should improve over time.

Want?

You can clone my changes using Git. The repository is available at http://barkingiguana.com/~craig/code/activecouch.git.

git clone http://barkingiguana.com/~craig/code/activecouch.git

Getting started

If you don't already have CouchDB setup, do that now. If you use Ubuntu, I wrote a brief article on setting it up there. If you use OS X then install MacPorts and run sudo port install couchdb.

The first thing you'll need to do is configure ActiveCouch to connect to your CouchDB instance. Set site to the URL that your CouchDB instance is listening at, and the database name to something that makes sence for your application.

ActiveCouch::Base.class_eval do
  set_database_name 'blog'
  site 'http://localhost:5984/'
end

Now you'll need some classes to work with.

class Author < ActiveCouch::Base
  has :name, :which_is => :text
  has :email_address, :which_is => :text
  has_many :articles
end

class Article < ActiveCouch::Base
  has :title, :which_is => :text
  has :status, :which_is => :text, :with_default_value => "draft"
  has :body, :which_is => :text
  belongs_to :author
end

has declares an attribute, has_many, has_one and belongs_to work very similar to the way that you're familiar with from ActiveRecord (except they don't have the extreme customisability - the name of the association must reflect the name of the class on the other side of the association).

That's pretty much it. Use your classes however make sense for your application.

author = Author.create :name => "Craig R Webster",
  :email_address => "craig@barkingiguana.com"

a = Article.new
a.title = "Getting started with ActiveCouch"
a.body =<<-EOF
  Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
  tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
  quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
  consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
  cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
  proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
EOF
a.author = author
a.save

Article.find(:all)
Author.first
Article.find(:first, :conditions => { :status => "draft" })

Known Issues

Not so much a bug as a feature which hasn't yet been implemented. ActiveCouch::Base#find doesn't support ordering. It should be possible to add this but I haven't started working on it yet. If you need ordering then a patch would be lovely ;)

Having problems? Got feedback?

There are likely to be a bunch of bugs in there still. Bug reports, patches and feedback, as always, would be awesome. Either leave a comment or get in touch directly.

written by
Craig
published
2009-01-08
Disagree? Found a typo? Got a question?
If you'd like to have a conversation about this post, email craig@barkingiguana.com. I don't bite.
You can verify that I've written this post by following the verification instructions:
curl -LO http://barkingiguana.com/2009/01/08/breaking-activecouch-in-fun-and-inventive-ways.html.orig
curl -LO http://barkingiguana.com/2009/01/08/breaking-activecouch-in-fun-and-inventive-ways.html.orig.asc
gpg --verify breaking-activecouch-in-fun-and-inventive-ways.html.orig{.asc,}