When I posted about my hacking on ActiveCouch I noted that it didn't yet support ordering. Well, since commit 87120176 it does. Ordering isn't as fine-grained as ActiveRecord (yet), but it's good enough for what I need to do: set conditions on the finder and have the results ordered by posted_at date and then id.

I say it's not as fine-grained as ActiveRecord because that can quite easily construct queries that are order by posted_at asc, id desc, created_at desc, author asc. ActiveCouch can only order view results by key though - either ascending or descending. I don't think that this is an insurmountable challenge, I just haven't needed that much control yet.

How does it work? I hear you ask. Or at least I hear voices ask. Well, not so much ask as whisper to me. Yes, chocolate. Let's eat the chocolate. All the chocolate. Mmm.

Uhh, anyway. When you want to find by a condition but you don't so much care about the order ActiveCouch creates a view which emits keys based on just those conditions. Maybe you want to find all articles with author "[email protected]" that have a "Live" status.

Article.find(:all, :conditions => { :author_id => "[email protected]", :status => "Live" })

The first time this query is run ActiveCouch will create a view called by_author_id_and_status in the articles design document. The view will emit a key based on the author_id and the status and then it will emit the full document.

{
  "_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 will then run against this view, asking for a key [ "[email protected]", "Live" ] which will exactly match the keys of several of the documents.

When a query is run with an order a slightly different approach is taken. Since these are articles they're probably time-sensitive so let's order them by the date they were posted_at.

Article.find(:all, :conditions => { :author_id => "[email protected]", :status => "Live" }, :order => :posted_at)

The first time this query is run it will create a view very similar to the previous query, except the key that the view emits will include the posted_at attribute and the view will be 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 view omitted for brevity
  }
}

When the query is run it'll take advantage of CouchDB's view collation specification by asking for result keys that, based on the finder conditions, are between the lowest possible key and the highest possible key. For the example above this means that it'd ask for keys between [ "[email protected]", "Live" ] and [ "[email protected]", "Live", "\u9999" ] ("\u9999" is an extremely high value unicode character as recommended in the view collation specification).

Since CouchDB view results are ordered by the keys, the key contains the attributes we want to order by, and we're calculating a range of keys that will contain any key that starts with the conditions we're looking for we now have simple sorting and conditions for finders.

Of course, since I've just done this work you don't have to worry about the internals. You can grab the code now using git: git clone http://barkingiguana.com/~craig/code/activecouch.git. There's a getting started section in my previous post on ActiveCouch. Enjoy, and please let me know if you're using this code!

written by
Craig
published
31 Jan 2009

Disagree? Found a typo? Got a question? If you'd like to have a conversation about this post, email [email protected]. I don't bite.