Skip to content
debasishg edited this page Sep 13, 2010 · 17 revisions

scouchdb : Persisting Scala Objects in CouchDB

scouchdb offers a Scala interface to using CouchDB. Scala offers objects and classes as the natural way to abstract entities, while CouchDB stores artifacts as JSON documents. scouchdb makes it easy to use the object interface of Scala for persistence and management of Scala objects as JSON documents.

Motivation

The primary motivation for making scouchdb is to offer a form of CouchDB driver to manipulate objects in a completely non-intrusive manner. The Scala objects are not CouchDB aware and remain completely transparent of any CouchDB dependency. Incorporating CouchDB specific attributes like id and rev take away a lot of reusability goodness from domain objects and make them constrained only for the specific platform.

CouchDB Views

One of the biggest hits of CouchDB is the view engine that uses the power of MapReduce to fetch data to the users. The current version of the framework does not offer much in terms of view creation apart from basic abstractions that allow plugging in “map” and “reduce” functions in Javascript to the design document. There are some plans to make this more Scala ish with little languages that will enable map and reduce function generation from Scala objects.

But what it offers today is a small DSL that enables building up view queries along with the sea of options that CouchDB server offers ..

// fetches records from the view named least_cost_lunch
http(test view(Views.builder("lunch/least_cost_lunch").build))

// fetches records from the view named least_cost_lunch 
// using key and limit options
couch(test view(
  Views.builder("lunch/least_cost_lunch")
       .options(optionBuilder key(List("apple", 0.79)) limit(10) build)
       .build))

// fetches records from the view named least_cost_lunch 
// using specific keys and other options for deciding output filters
http(test view(
  Views.builder("lunch/least_cost_lunch")
       .options(optionBuilder descending(true) limit(10) build)
       .keys(List(List("apple", 0.79), List("banana", 0.79)))
       .build))

// temporary views
val mf = 
  """function(doc) {
       var store, price;
       if (doc.item && doc.prices) {
         for (store in doc.prices) {
           price = doc.prices[store];
           emit(doc.item, price);
         }
       }
  }"""

val rf = 
  """function(key, values, rereduce) {
       return(sum(values))
  }"""

// with grouping
val aq = 
  Views.adhocBuilder(View(mf, rf))
       .options(optionBuilder group(true) build)
       .build
val s = http(test adhocView(aq))
s.size should equal(3)

// without grouping
val aq_1 = 
  Views.adhocBuilder(View(mf, rf))
       .build
val s_1 = http(test adhocView(aq_1))
s_1.size should equal(1)

Attachment Handling

val att = "The quick brown fox jumps over the lazy dog."

val s = Shop("Sears", "refrigerator", 12500)
val d = Doc(test, "sears")
var ir:(String, String) = null
var ii:(String, String) = null

// create a document from an object    
couch(d add s)
ir = http(d ># %(Id._id, Id._rev))
ir._1 should equal("sears")

// query by id should fetch a row
ii = http(test by_id ir._1)
ii._1 should equal("sears")

// sticking an attachment should be successful
http(d attach("foo", "text/plain", att.getBytes, Some(ii._2)))

// retrieving the attachment should equal to att
val air = http(d ># %(Id._id, Id._rev))
air._1 should equal("sears")
http(d.getAttachment("foo") as_str) should equal(att)

Attachments can also be created for non-existing documents. In that case, the document also gets created along with the attachment. Have a look at the test suite for the spec.

Handling Bulk Inserts / Updates / Deletes

Documents can be manipulated in bulks. Through one single POST, new documents can be simultaneously added, existing ones updated and deleted. Here is a sample session ..

// should insert 2 new documents, update 1 existing document and delete 1 

// a scala object
val s = Shop("Shoppers Stop", "refrigerator", 12500)
val d = Doc(test, "ss")

// another scala object      
val t = Address("Monroe Street", "Denver, CO", "987651")
val ad = Doc(test, "add1")

var ir:(String, String) = null
var ir1:(String, String) = null

// create a new document    
http(d add s)
ir = http(d ># %(Id._id, Id._rev))
ir._1 should equal("ss")

// create another new document      
http(ad add t)
ir1 = http(ad ># %(Id._id, Id._rev))
ir1._1 should equal("add1")

// new scala objects     
val s1 = Shop("cc", "refrigerator", 12500)
val s2 = Shop("best buy", "macpro", 1500)
val a1 = Address("Survey Park", "Kolkata", "700075")

// a dsl that adds s1 and s2, updates s and deletes t      
val d1 = bulkBuilder(Some(s1)).id("a").build 
val d2 = bulkBuilder(Some(s2)).id("b").build
val d3 = bulkBuilder(Some(s)).id("ss").rev(ir._2).build
val d4 = bulkBuilder(None).id("add1").rev(ir1._2).deleted(true).build

http(test bulkDocs(List(d1, d2, d3, d4), false)).size should equal(4)

Dependencies