I would like to store recipe document updates in a document similar to:
{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).
"_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"
}
]
}
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) {For the pizza dough update document, that view produces:
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);
}
}
{"total_rows":2,"offset":0,"rows":[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:
{"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"]}
]}
function(doc) {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:
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']);
}
}
}
{"total_rows":3,"offset":0,"rows":[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.
{"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"}
]}
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" doThat fails at first because the method is not defined:
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
1)After defining the method, then giving it the proper arity, then making the required
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
RestClient
call, I finally end up with this:def recipe_update_of(permalink)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:
url = "#{_db}/_design/recipes/_view/update_of?key=%22#{permalink}%22"
data = RestClient.get url
end
it "should return the value from the JSON" doI get that example passing with:
RestClient.
stub!(:get).
and_return('{"rows": [{"value": ["2000-09-07-recipe"]}] }')
recipe_update_of('2009-09-07-recipe').
should == ['2000-09-07-recipe']
end
def recipe_update_of(permalink)After driving the
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
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