Finding and enumerating document attributes with ActiveCouch

Continuing from my investigation into counting tags with CouchDB and map-reduce, I've made ActiveCouch support the operations required to count all uses of attributes on a certain document type in your database. Through that it's also possible to get all unique values for an attribute in your database.

To use this new functionality just call enumarate_all_[attribute_name] or find_all_[attribute_name] on your models.

>> Article.enumerate_all_tags
=> {"security"=>2, "ldap"=>1, "xen"=>1, "stories"=>3, "rails"=>13, "xeriom"=>3, "mysql"=>3, ... }

>> Article.find_all_tags
=> ["agile", "ajax", "apache", "api", "caching", "coding", ... ]

>> Article.find_all_author_ids
=> ["craig@barkingiguana.com"]

If you'd like to see exactly what I did to add this have a look at commit 1cbbe71.

Conditions and ordering with ActiveCouch views

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 "craig@barkingiguana.com" that have a "Live" status.

Article.find(:all, :conditions => { :author_id => "craig@barkingiguana.com", :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 [ "craig@barkingiguana.com", "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 => "craig@barkingiguana.com", :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 [ "craig@barkingiguana.com", "Live" ] and [ "craig@barkingiguana.com", "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!

Counting tags with CouchDB and map-reduce

My previous post on CouchDB covered adding a simple view, but what if we've got a problem that can't be solved simply by mapping the existing documents to new documents? What if we want to get a list of each tag used in articles along with a count of how many articles use that tag? Sure, we could emit doc.tags then munge the resulting arrays in the client language, but wouldn't it be great if CouchDB would do that for us?

Well, yes, it would be great, and CouchDB can do it for us.

As a reminder, here's an example of the article documents that I'm using.

{
  "_id": "monkeys-are-awesome",
  "_rev": "1534115156",
  "type": "article",
  "title": "Monkeys are awesome",
  "posted_at": "2008-09-14T20:45:14Z",
  "tags": [
    "monkeys",
    "awesome"
  ],
  "status": "Live",
  "author_id": "craig@barkingiguana.com",
  "updated_at": "2008-09-14T21:23:59Z",
  "body": "The article body would go here..."
}

It's fairly easily add a view that, for each document, will return a count of how many times that document has been tagged by each tag.

function(doc) {
  if(doc.type == 'article') {
    for(i in doc.tags) {
      emit(doc.tags[i], 1);
    }
  }
}

For the example article document this would return ("awesome", 1) and ("monkeys", 1), but if there were several documents tagged "monkeys" then this would return ("monkeys", 1) several times.

What we want to do now is to reduce the result set to a list of unique tags and the number of times those tags were found in the results.

The reduce takes the form of a method. It's called once for each unique instance of a key that appears in the map output and is passed the key and an array of all values that the map emits with that key.

Since our keys are tags and our values are numbers we simply need to add all the numbers for each key.

function(tag, counts) {
  var sum = 0;
  for(var i=0; i < counts.length; i++) {
     sum += counts[i];
  }
  return sum;
}

This can be installed in the same way as a map function, just with the key "reduce".

{
  "tags": {
    "map": "function(doc) { if(doc.type == 'article') { for(var i in doc.tags) { emit(doc.tags[i], 1); }}}",
    "reduce": "function(tag, counts) { var sum = 0; for(var i = 0; i < counts.length; i++) { sum += counts[i]; }; return sum; }"
  }
  // other views omitted for brevity
}

Looking at this view in Futon will get you a nicely formatted list of tags and a count of the number of documents that have the tag. To use the view directly you must ask CouchDB to group the results by key.

// GET http://localhost:5984/blog/_view/articles/tags?group=true&group_level=1

{"rows":[
  {"key":"awesome","value":1},
  {"key":"agile","value":2},
  {"key":"ajax","value":2},
  {"key":"apache","value":2},
  {"key":"api","value":1},
  {"key":"caching","value":1},
  {"key":"coding","value":7},
  {"key":"conference","value":1},
  // and so on ...
]}

Filtering and ordering CouchDB view results

Being able to map documents to (key, value) pairs is really useful, but the views installed in my previous post return all pairs that the view calculates in no specific order. What if I want only the titles of articles posted in December 2007?

In the last article I mentioned in passing that it was possible to emit keys as part of the map method of a view. Keys are used to order and filter a result set. Details about how keys are sorted against each other can be found in the CouchDB view collation specification. To order and filter documents by date posted I just need to emit doc.posted_at as the key when I'm writing my map method.

// Get all article titles ordered by posted date.
function(doc) {
  if(doc.type == 'article') {
    emit([doc.posted_at], doc.title);
  }
}

It's worth noting that I've chosen always to have my keys be arrays. This is a personal preference - it was easier to make my branch of ActiveCouch support multiple keys if I assumed this.

A typical result set from running this map might look like the following.


// GET /blog/_articles/titles_by_posted_at
{
"total_rows":75,
"offset":0,
"rows":[
  {"id":"showing-multiple-message-types-with-the-flash","key":["2007-12-15T20:14:02Z"],"value":"Showing multiple message types with the flash"},
  {"id":"class-instance-and-singleton-methods","key":["2007-12-20T14:50:41Z"],"value":"Class, Instance and Singleton methods"},
  // ... and so on ...
}

Now that the articles are keyed by date note that they're ordered by the data - the lower the date value the earlier they are in the results.

The key can also be used to pick out specific articles. Maybe I want just the article that was published at 2007-12-20T14:50:41Z, I just ask for that key from the results.

// GET /blog/_articles/titles_by_posted_at?key=["2007-12-20T14:50:41Z"]

{"total_rows":75,"offset":0,"rows":[
{"id":"class-instance-and-singleton-methods","key":["2007-12-20T20:50:41Z"],"value":"Class, Instance and Singleton methods"}
]}

If I wanted to pull out a range of results I can specify a startkey and an endkey and all results that have a key that falls between the two will be returned. I can take advantage of the fact that the keys are strings too, and specify times that might otherwise not make sense such as 24:00 to make sure that I get all articles that I'm interested in.

// GET /blog/_view/articles/titles_by_created_at?startkey=[%222007-12-01T00:00:00Z%22]&endkey=[%222007-12-31T24:00:00Z%22]

{"total_rows":75,"offset":0,"rows":[
{"id":"showing-multiple-message-types-with-the-flash","key":["2007-12-15T20:14:02Z"],"value":"Showing multiple message types with the flash"},
{"id":"class-instance-and-singleton-methods","key":["2007-12-20T14:50:41Z"],"value":"Class, Instance and Singleton methods"}
]}

The key, startkey and endkey are three of a whole bunch of parameters available as part of CouchDB's view API. More information can be found at http://wiki.apache.org/couchdb/HTTP_view_API.

Adding a simple view to CouchDB

CouchDB views are sort of like scripts that run inside CouchDB which manipulate and massage the documents inside the database into a (key, value) pair, then return those pairs which contain keys that match a query you run against the view. When I started playing with CouchDB I couldn't work out how to add views, thinking that there was something I was missing.

To show how to create a view and add it to the database let's take an example problem. Say you have several documents that describe articles in your database.

{
   "_id": "monkeys-are-awesome",
   "_rev": "1534115156",
   "type": "article",
   "title": "Monkeys are awesome",
   "posted_at": "2008-09-14T20:45:14Z",
   "tags": [
       "monkeys",
       "awesome"
   ],
   "status": "Live",
   "author_id": "craig@barkingiguana.com",
   "updated_at": "2008-09-14T21:23:59Z",
   "body": "The article body would go here..."
}

You might create a view that would give you the id and title of all documents in the database. To do this you would ask CouchDB to map each existing document to a new document containing the document id and the document title. This takes the form of a method which accepts each document as an argument and returns the document or result that you'd like to get back from the view.

function(doc) {
  emit(null, { 'id': doc._id, 'title': doc.title });
}

For just now ignore that the first argument to emit is null. It's for emitting the key that will be used to sort and filter results. I'll cover it in my next post.

While writing a view it can be useful to filter documents so that you only get the desired types emitted - in this case I only want the id and title of article documents. Documents that represent, for example, comments, may not have a title attribute.

function(doc) {
  if(doc.type == 'article') {
    emit(null, { 'id': doc._id, 'title': doc.title });
  }
}

Adding this view to the database it pretty simple: just add a design document to the database. Design documents are just the same as any another document you'd add to CouchDB except they must have an identifier that starts _design/ for example _design/articles. To insert the document you can use the built-in admin client, Futon, which is available at http://localhost:5984/_utils/.

The full JSON document for a view which gives back the id and title for all documents in the database is shown below.

{
  "_id": "_design/articles",
  "_rev": "42351258",
  "language": "javascript",
  "views": {
    "titles": {
      "map": "function(doc) { emit(null, { 'id': doc._id, 'title': doc.title }); }"
    }
  }
}

Fire up Futon, access your database, add a new document and paste the view in. Once installed you can easily see the results of a view by clicking the drop-down that Futon shows when viewing a database. It's in the top right and it's labelled "select view". To get the raw JSON documents returned by your view you'll need to access your database directly. If your database was called "blog" then you could access the above view at http://localhost:5984/blog/_view/articles/titles.

There can be many views in one design documents, each with a different name, and each returning different kinds of results. Here's one that's got that a few views that use the soon-to-be discussed "key" emitted.

{
  "_id": "_design/articles",
  "_rev": "28651884",
  "language": "javascript",
  "views": {
    "all": {
      "map": "function(doc) { if(doc.type == 'article') { emit(null, doc); }  }"
    },
    "by_author_id": {
      "map": "function(doc) { if(doc.type == 'article') { emit([doc.author_id], doc); }  }"
    },
    "by_status": {
      "map": "function(doc) { if(doc.type == 'article') { emit([doc.status], doc); }  }"
    },
    "titles": {
      "map": "function(doc) { if(doc.type == 'article') { emit(null, { 'id': doc._id, 'title': doc.title }); } }"
    }
  }
}

Breaking ActiveCouch in fun and inventive ways

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.

Jan Lehnardt talks to the BBC about CouchDB

I previously wrote about installing and getting started with CouchDB. More recently I attended a talk by Jan Lehnardt introducing the technology to the BBC. Here's what happened.

Want to know more about CouchDB?

This is just one of several CouchDB articles on my blog, and there are plenty more on the way. Check out the other articles tagged CouchDB and check back often for new articles.

Getting started with CouchDB: A simple address book application

I've recently installed CouchDB but, still being pretty new to this whole document store thing, don't really know what they can do or how to make CouchDB do it.

The best way to learn, of course, is to do. I've decided that I'll implement a simple address-book implementation.

Investigation and technology choice

Since CouchDB talks JSON I figure that I'll write the address book in Javascript and HTML, and because CouchDB includes a web server I'll serve the application from the same place I store the data. I'll call the file that contains that addressbook application addressbook.html.

Taking a peek at the CouchDB configuration in /usr/local/etc/couchdb/couch.ini I see that the document root for the web server can be found at /usr/local/share/couchdb/www - that's where the addressbook.html file will go.

I'll need a database to store people's contact details in. There's a pretty nice interface to do this at /_utils/ which is accessible using a web browser by pointing it at the CouchDB server's IP address and port.

CouchDB comes with a Javascript wrapper which can be found at /_utils/script/couch.js but it only talks to a local server and I'm accessing the page across the internet so I'll steal some code from it and change it to work for my setup.

Implementation

First off, create the database. Jump into the interface at /_utils/ and create a database called "addressbook". That's where we'll store our data.

The user interface is going to be a webpage using Javascript which makes things pretty simple. I'll whip up a really simple page to start with.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <!-- The javascript will live in addressbook.js -->
  <script src="http://aaa.bbb.ccc.ddd:5984/_utils/addressbook.js"></script>
  <title>Address Book</title>
</head>
<body>
  <h1>Address Book</h1>
  <div id="addressbook">
    <p id="loading">Loading... please wait...</p>
  </div>
</body>
</html>

Since I've been spoiled by ActiveRecord I want to be able to say something like var people = Person.find("all"); in my code and have it return all Person records. I also want to be able to say Person.find("123456-1234-1234-123456"); to find an individual person.


Person = {
  // Push the implementation details of the database into a 
  // different object to keep Person clean.
  //
  database: AddressBook,
  find: function(id) {
    if(id == "all") {
      return this.database.allCards();
    } else {
      return this.database.openCard(id);
    }
  }
}

I've chosen to implement an AddressBook object that will abstract the details of database connection from the Person object. It will provide two methods, allCards and openCard(id). These methods talk to the CouchDB server and handle any and all data marshalling or other tricky bits and pieces.

AddressBook = {
  // Change this to point to your own CouchDB instance.
  uri: "http://craig-01.vm.xeriom.net:5984/addressbook/",

  _request: function(method, uri) {
    var req = new XMLHttpRequest();
    req.open(method, uri, false);
    req.send();
    return req;
  },

  // Fetch all address book cards.
  allCards: function() {
    var req = this._request("GET", this.uri + "_all_docs");
    var result = JSON.parse(req.responseText);
    if (req.status != 200)
      throw result;
    var allDocs = [];
    for(var offset in result.rows) {
      var id = result.rows[offset]["id"];
      var doc = this.openCard(id);
      allDocs[allDocs.length] = doc;
    }
    return allDocs;
  },

  // Fetch an individual address book card.
  openCard: function(id) {    
    var req = this._request("GET", this.uri + id);
    if (req.status == 404)
      return null;
    var result = JSON.parse(req.responseText);
    if (req.status != 200)
      throw result;
    return result;
  }
}

I push responsibility for parsing JSON off to another library. Luckily, Yahoo provide a rather nice JSON library that does just what I'm looking for - I don't have to implement it, but I do need to pull it into the webpage, and make it appear in the global namespace.


<!-- Add this to the head of addressbook.html -->
<script src="http://yui.yahooapis.com/2.5.2/build/yahoo/yahoo-min.js"></script>
<script src="http://yui.yahooapis.com/2.5.2/build/json/json-min.js"></script>

// Make YUI JSON available in the global namespace.
// Add this to addressbook.js
JSON = YAHOO.lang.JSON;

The last piece of Javascript I need to show the address book is something to load all people from the address book and add them to the page. This uses window.onload hook which is bad, but for this little application is a quick and easy to kick off some code.

// This is horrible, I know, but it's just a simple example.
window.onload = function() {
  var addressbook = document.getElementById("addressbook");
  var personList = document.createElement("ul");
  for(var offset in people) {
    var person = people[offset];
    var personNode = document.createElement("li");
    var name = document.createTextNode(person.name);
    personNode.appendChild(name);
    personList.appendChild(personNode);
  }
  addressbook.removeChild(document.getElementById("loading"));
  addressbook.appendChild(personList);
}

That's it; the application is ready to go. Upload the addressbook.html and addressbook.js file to the document root of the CouchDB server, fire up your browser and navigate to http://aaa.bbb.ccc.ddd:5984/_utils/addressbook.html where aaa.bbb.ccc.ddd is the IP address of your CouchDB instance.

A blank page that says "Address Book" should greet you. Not very impressive, right? What went wrong? Actually, nothing went wrong. There's just no data in the database.

The interface that I pointed out before for browsing and creating databases can also be used to add documents to the database. Jump into it again, navigate to the addressbook database and add a document. When it asks you for an id, just leave the field blank: it'll create one automatically. Add a field to the document called name and click the little green checkbox beside the textbox, then double click on the value of the new field and set it to your own name in quotes eg "Craig Webster". Click the green arrow beside the textbox then click "save document", jump back to the address book and hit refresh. The new record should now show up.

Moving forward

I've shown how to retrieve data from CouchDB using Javascript, but currently the data still has to be input using the CouchDB interface. Watch this space for an upcoming article on manipulating the database using Javascript so we can add cards to the addressbook.

Did this article help?

If this article helped you, I appreciate beer if you meet me, or recommendations at Working With Rails.

Installing CouchDB 0.8.0 on Ubuntu 8.04

CouchDB is a distrbuted document store which can be manipulated using HTTP. A more detailed introduction is available on the CouchDB site.

Some assembly required

Since CouchDB is still a fairly young project there are no packages available to install it on Ubuntu. There are rumblings which seem to indicate that Intrepid Ibis will have a package, but until then here's a quick-n-dirty way to get CouchDB running on Ubuntu 8.04.

sudo apt-get install automake autoconf libtool subversion-tools help2man 
sudo apt-get install build-essential erlang libicu38 libicu-dev
sudo apt-get install libreadline5-dev checkinstall libmozjs-dev wget
wget http://mirror.public-internet.co.uk/ftp/apache/incubator/couchdb/0.8.0-incubating/apache-couchdb-0.8.0-incubating.tar.gz
tar -xzvf apache-couchdb-0.8.0-incubating.tar.gz
cd apache-couchdb-0.8.0-incubating
./configure
make && sudo make install
sudo adduser couchdb
sudo mkdir -p /usr/local/var/lib/couchdb
sudo chown -R couchdb /usr/local/var/lib/couchdb
sudo mkdir -p /usr/local/var/log/couchdb
sudo chown -R couchdb /usr/local/var/log/couchdb
sudo mkdir -p /usr/local/var/run
sudo chown -R couchdb /usr/local/var/run
sudo update-rc.d couchdb defaults
sudo cp /usr/local/etc/init.d/couchdb /etc/init.d/
sudo /etc/init.d/couchdb start

Let others REST on your Couch

By default CouchDB listens only for connections from the local host. To change that edit /usr/local/etc/couchdb/couch.ini and restart CouchDB.

If you're running a firewall (you should be) then open the correct port.

sudo iptables -I INPUT 3 -p tcp --dport 5984 -j ACCEPT

Testing that it all works

Since CouchDB talks HTTP we can use any HTTP client to check that it's running. Our web browser, for example. Fire it up and hit the IP address of the server on port 5984. If it's running and you can access it you should get back some details about the server.

{"couchdb":"Welcome","version":"0.8.0-incubating"}

More CouchDB?

This is just one of several CouchDB articles on my blog, and there are plenty more on the way. Check out the other articles tagged CouchDB and check back often for new articles.

Love me!

If you've found this article useful I'd appreciate recommendations at Working With Rails.

About the boy

A picture of Craig in grayscale

Craig Webster is a software engineer living in London. He usually works with Ruby although sometimes he sneaks in some Erlang or JavaScript. He's into rock climbing, snowboarding, skating, photography and fencing. Yes, this does mean he has a sword.

Near here you'll find Craig's homepage, contact details, PGP key and keysigning policy, and talks.

Licence

The entire content of this blog is public domain. Use it however you fancy. You don't even need to attribute it to me, although it would be nice if you did. Just don't sue me and we'll all be happy.

I Work With Rails

Recommend Me

My Travels

I go places. Do you go places too? Let's meet up!.