Conditions and Ordering with ActiveCouch Views

January 31, 2009

When I posted about my hacking on ActiveCouch, I mentioned it didn't yet support ordering. Well, since commit 87120176, it does. It's not as fine-grained as ActiveRecord yet, but it handles what I need: setting conditions on the finder and getting results ordered by posted_at date and then id.

When I say "not as fine-grained," I mean ActiveRecord can effortlessly build queries like ORDER BY posted_at ASC, id DESC, created_at DESC, author ASC. ActiveCouch can only order view results by key — either ascending or descending. I don't think that's an insurmountable limitation; I just haven't needed more control yet.

So how does it work?

When you want to find by conditions but don't particularly care about the order, ActiveCouch creates a view that emits keys based on just those conditions. Say you want all articles by "craig@barkingiguana.com" with a "Live" status:

Article.find(:all, :conditions => { :author_id => "craig@barkingiguana.com", :status => "Live" })

The first time this runs, ActiveCouch creates a view called by_author_id_and_status in the articles design document. The view emits a key built from those two attributes, along with the full document as the value:

{
  "_id": "_design/articles",
  "_rev": "1532981864",
  "language": "javascript",
  "views": {
    "by_author_id_and_status": {
      "map": "function(doc) { if(doc.type == 'article') { emit([doc.author_id, doc.status], doc); }  }"
    }
    // other views cut for brevity
  }
}

The query then hits this view asking for the key ["craig@barkingiguana.com", "Live"], which matches exactly the documents we're after.

When you add an order, things get a bit more interesting. Since these are articles and probably time-sensitive, let's order by posted_at:

Article.find(:all, :conditions => { :author_id => "craig@barkingiguana.com", :status => "Live" }, :order => :posted_at)

This time, ActiveCouch creates a view whose key also includes the posted_at attribute, named by_author_id_and_status_and_posted_at:

{
  "_id": "_design/articles",
  "_rev": "3752119467",
  "language": "javascript",
  "views": {
    "by_author_id_and_status_and_posted_at": {
      "map": "function(doc) { if(doc.type == 'article') { emit([doc.author_id, doc.status, doc.posted_at], doc); }  }"
    }
    // other views omitted for brevity
  }
}

When the query runs, it takes advantage of CouchDB's view collation specification by requesting keys in a calculated range. For the example above, it asks for keys between ["craig@barkingiguana.com", "Live"] and ["craig@barkingiguana.com", "Live", "\u9999"] (that's a very high-value Unicode character, as recommended in the collation spec).

Since CouchDB view results are ordered by key, and the key now contains the attribute we want to sort by, and our key range captures exactly the conditions we're filtering on — we get sorted, filtered results in one clean query.

The good news is that since I've already done this work, you don't need to think about the internals. Grab the code with git: git clone http://barkingiguana.com/~craig/code/activecouch.git. There's a getting-started guide in my previous post on ActiveCouch. Give it a spin, and please let me know if you end up using it!

Questions or thoughts? Get in touch.