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
[email protected]
http://rubyforge.org/mailman/listinfo/camping-list