Monday, September 7, 2009

Pizza Dough is Tough to Get Right

‹prev | My Chain | next›

I would like to store recipe document updates in a document similar to:
{
"_id": "pizza_dough_updates",
"_rev": "2-1463622047",
"type": "Update",
"updates": [
{
"id": "2001-12-30-pizza"
},
{
"id": "2002-01-28-pizza_crust"
},
{
"id": "2002-03-28-pizza_dough"
}
]
}
The order of the "updates" attribute is significant—the last entry is the most up-to-date version of the recipe and the others are considered deprecated. The updates are hashes so that we can add a description of why we felt the need to replace an earlier incarnation (e.g. the old photo was not of sufficient quality or the ingredient list needed to be tweaked).

Last night, I played around with CouchDB's Futon interface for building map-reduce views. I eventually got a view that given a recipe ID, produces a list of the previous versions of the same recipe:
function(doc) {
if (doc['type'] == 'Update') {
var num = doc['updates'].length;
var old = [];
for (var i=0; i<num-1; i++) {
old[i] = doc['updates'][i]['id'];
}
emit(doc['updates'][num-1]['id'], old);
}
}
For the pizza dough update document, that view produces:
{"total_rows":2,"offset":0,"rows":[
{"id":"pizza_dough_updates",
"key":"2002-03-28-pizza_dough",
"value":["2001-12-30-pizza","2002-01-28-pizza_crust"]}
,
{"id":"roasted_potato_updates",
"key":"2003-08-17-potatoes",
"value":["2001-09-02-potatoes"]}
]}
Today, I continue to play with Futon to accomplish the opposite—given a recipe ID, lookup if it has been updated and, if so, by which recipe. The "updated by" map view is:
function(doc) {
if (doc['type'] == 'Update') {
var num = doc['updates'].length;
for (var i=0; i<num-1; i++) {
emit(doc['updates'][i]['id'], doc['updates'][num-1]['id']);
}
}
}
For every update in an update document, the view should contain the ID of the update and the ID of the last update. For the pizza dough update document, I get:
{"total_rows":3,"offset":0,"rows":[
{"id":"roasted_potato_updates","key":"2001-09-02-potatoes","value":"2003-08-17-potatoes"},
{"id":"pizza_dough_updates","key":"2001-12-30-pizza","value":"2002-03-28-pizza_dough"},
{"id":"pizza_dough_updates","key":"2002-01-28-pizza_crust","value":"2002-03-28-pizza_dough"}

]}
This is an example of CouchDB's ability to emit more than one result per-document. The pizza dough update doc emitted two results—one for each of tried and not-so-true versions of the recipe.

I am reasonably happy with my "update_of" and "updated_by" recipe views. I may not end up with the same views, but the output seems solid. Soo... it's time to move back into driving code by example.

Describing the recipe_update_of helper function with RSpec:
describe "recipe_update_of" do
it "should ask CouchDB" do
RestClient.
should_receive(:get).
with(/update_of.+key=.+2009-09-07/).
and_return('{"rows": [] }')
recipe_update_of('2009-09-07')
end
end
That fails at first because the method is not defined:
1)
NoMethodError in 'update_of should ask CouchDB'
undefined method `recipe_update_of' for #<Test::Unit::TestCase::Subclass_12:0xb6aa1a34>
./spec/eee_helpers_spec.rb:487:

Finished in 0.106194 seconds

70 examples, 1 failure
After defining the method, then giving it the proper arity, then making the required RestClient call, I finally end up with this:
    def recipe_update_of(permalink)
url = "#{_db}/_design/recipes/_view/update_of?key=%22#{permalink}%22"
data = RestClient.get url
end
Since I need real data, not JSON returned from this helper, I add a second example to drive the desired return value of the helper:
  it "should return the value from the JSON" do
RestClient.
stub!(:get).
and_return('{"rows": [{"value": ["2000-09-07-recipe"]}] }')
recipe_update_of('2009-09-07-recipe').
should == ['2000-09-07-recipe']
end
I get that example passing with:
    def recipe_update_of(permalink)
url = "#{_db}/_design/recipes/_view/update_of?key=%22#{permalink}%22"
data = RestClient.get url
results = JSON.parse(data)['rows']
results.first && results.first['value']
end
After driving the recipe_updated_by helper in a similar fashion, I am ready to call it a night. I will pick things up tomorrow by using these helpers in the Haml view for recipes and ultimately working my way back out to the Cucumber scenario to mark it as complete.

No comments:

Post a Comment