Hello camping people

I've written a Ruby library for working with CouchDB. It's a pretty thin API - 
it provides a database object, a document object (a glorified hash) and a 
design object. 

In case anyone's not familiar with CouchDB, it's a schema-less JSON document 
database with a HTTP interface. You store documents in the database, then query 
the database with MapReduce views. Your map and reduce queries are just 
JavaScript, and they're stored as fields of design documents. Generally, each 
design document corresponds to an 'app'.

So it's all lovely and simple, and I've been working on writing a module that 
plugs my API into Camping.

Models look like this:

> module MyFood::Models
>  class Recipe < CouchDocument
>  needs :name
>  needs :ingredients
>  needs :instructions
>  suggests :dates_cooked, :array
>  suggests :cost
>  end
> end




CouchDocument is a hash with some methods for pushing to and pulling from the 
document database, and some validation functions. "needs" and "suggests" both 
just refer to keys. I haven't decided exactly how to implement "suggests" yet; 
"needs" enforces the existence of particular keys before a document can be 
pushed. It also looks at the name of the class and uses it for a "kind" or 
"type" key.

I've written a small library that uses S Expressions to parse Ruby into 
JavaScript. It's limited, but for the moment it's adequate to my purposes: 
writing JavaScript MapReduce functions as Ruby blocks. This means you can do 
this:

> module MyFood::MapReduce
>  view :untried_recipes do
>  map do
>  emit(doc.name, doc) if doc.kind == "Recipe" and doc.dates_cooked.length == 0
>  end
>  end
> 
> 
>  view :cost_of_crazy_huge_indian_meal
>  map do
>  emit(doc.name, doc.cost) if doc.kind == "Recipe" and doc.cuisine == "South 
> Asian"
>  end
>  reduce do
>  return sum(values)
>  end
>  end
> end

And you get a design document with two entries in the "views" field:

> "map"=> 
> "function ( doc ) { 
>  if( doc.kind == "Recipe" && doc.dates_cooked.length == 0 ) {
>  emit(doc.name, doc)
>  } 
> }"



And:

> "map" =>
> "function ( doc ) { 
>  if( doc.kind == "Recipe" && doc.cuisine == "South Asian" ) {
>  emit(doc.name, doc.cost)
>  } 
> }", "reduce"=>
> "function ( key, values, rereduce ) { 
>  return sum(values) 
> }"



So, yeah, whatever, that's all well and good, you can write your queries and 
it'll update the design document on the server so you can just do a HTTP GET 
request to the view's URL and CouchDB will return your rows or whatever. Cool. 
The thing is, I have no idea how to unify this with Camping. I feel like a 
"relaxing" schema-less thing like this is the perfect match for really small 
web projects, and there's the added bonus that since the CouchDB settings are 
just defined in a global variable at the top of your app, you can have 
different databases - or even different database servers - for different 
Camping apps running on the same web server if you like.

There's been a bit of discussion on the list lately about how great it is that 
Ruby's controllers belong to classes - there's an intrinsic logic to it. I want 
something as intuitive as that for this query language, but I don't know how to 
do it. The earliest implementation of this I did just had a Design class, and 
when you wrote a view it'd define a method on the Design class with that view's 
name - so, above, you'd call Design.untried_recipes, or 
Design.cost_of_blahblah, and it'd query Couch for the result. That's okay until 
you have more than a small handful of views - more than that, and there's too 
much to keep track of.

The views could just belong to models, so you could call Recipe.untried, or 
something, but the thing about MapReduce is that so many of your queries don't 
naturally belong to a particular "model", and I don't really want to built an 
architecture that makes people tend to write their models and queries as if 
it's SQL and you need to normalise and get all heavy with joins and generally 
ruin your life with boredom. If you want a view with particular data, you write 
a view that gives you exactly that, you save it, CouchDB automatically caches 
it in your fancy B-trees, and you're rolling. It's beautiful, but I don't know 
how to make it user-friendly.

So uh

does anyone

have any advice?

-- Cerales (@lodoicea)


_______________________________________________
Camping-list mailing list
Camping-list@rubyforge.org
http://rubyforge.org/mailman/listinfo/camping-list

Reply via email to