rwxr--r--
/dev/blog

Bez Hermoso, Software Engineer @ Square

After reading a brief on CouchDB, I decided to use it instead of MongoDB for a pet project and began diving right in. Cradle, a CouchDB client for Node.js, was one npm install away. Installing CouchDB and creating the database was a breeze. I was able to quickly store data and refined the structure when needed. Gotta love NoSQL!

Querying data is where it got a bit more interesting: fetching data requires the creation of views. This is unlike other popular data stores that has their own query DSLs (domain-specific language), like SQL for SQL-flavored RDBMS, JSON-based query DSLs for MongoDB and other NoSQL stores.

CouchDB views are simply applications of the MapReduce paradigm. In a nutshell, you provide map functions and/or reduce functions which will be used to narrow down the data-set and/or to reduce a data-set into a single aggregate value. Sounds easy. So I went ahead and pecked these on my keyboard:

exports.initialize = (db, onError) ->

    db.create (err) ->
    	onError err if onError
    	return

    db.save '_design/games',
      views:
        # Emit all document of type 'game'
        all:
          map: (doc) ->
            emit(null, doc) if doc.type == 'game'
            return
        # Same as 'all', but only if game is waiting for more players.
        open:
          map: (doc) ->
            emit(null, doc) if doc.type == 'game' && doc.status == 'open'
            return
        # Same as 'all', but emit documents indexed by the players.
        # Figure out how to deal with duplicates later using `reduce`.
        by_player:
          map: (doc) ->
            emit(doc.player1, doc) if doc.type == 'game' && doc.player1
            emit(doc.player2, doc) if doc.type == 'game' && doc.player2
            return
    return
If you are not familiar with CoffeeScript and the above is gibberish to you (unless you know Ruby), the above snippet will eventually be compiled into JavaScript.

However, this doesn’t really work, and it took me a while to realize my mistake: the map and reduce functions shouldn’t be function definitions, but function definition strings. *facepalm*

Not a huge problem: I can just substitute the (doc) -> ... blocks with actual JavaScript code and wrap them in quotation marks (and carefully escape inner strings). Kinda sucks since I am operating with CoffeeScript to minimize actual coding and to avoid syntax gotchas.

Then I remembered that JavaScript functions are objects, and has a toString method. So simply changing the map functions like these solves the problem:

((doc) ->
    emit(null, doc) if doc.type == 'game'
    return
).toString()

This results in JavaScript that looks like:

function (doc) {
    if(doc.type === 'game') {
       emit(null, doc);
    }
    return;
}.toString()

Calling the toString method on the function returns the whole function block as a string which is exactly what we need to create CouchDB views. Yay!

This approach still allows me to write zero actual JavaScript at all – I still get to have the function strings while still operating in CoffeeScript, which I find really helpful in minimizing the volume of code I write and also in avoiding the occasional syntax gotchas which are sometimes frustrating to debug in JavaScript.

comments powered by Disqus