Sunday, September 6, 2009

Learning the New Feature

‹prev | My Chain | next›

After a little preliminary work on the "Recipes with Updates" feature yesterday, I am ready to start work in earnest today. The Cucumber scenario that is driving this work already has several of the steps defined (thanks to yesterday's work):



The first two steps in this scenario are very similar:
  Given a "Buttermilk Pancake" recipe with "buttermilk" in it
Given a "Buttermilk Pancake" recipe on another day with "lowfat milk" in it
The text inside the quotes is already treated as an argument in the Cucumber step definition. The only other difference between the two steps is the presence of the text, "on another day". I will use the additional text to choose the date for the recipe:
Given /^a "([^\"]*)" recipe (.*)with "([^\"]*)" in it$/ do |title, on, ingredient|
date = (on == "") ? Date.new(2009, 9, 5) : Date.new(2000, 9, 5)
permalink = date.to_s + "-" + title.downcase.gsub(/\W/, '-')

recipe = {
:title => title,
:date => date,
:preparations => [{'ingredient' => {'name' => ingredient}}]
}

RestClient.put "#{@@db}/#{permalink}",
recipe.to_json,
:content_type => 'application/json'
end
Thus, if the text "on another day" is present in the step text, the recipe will have a date of September 5, 2000, otherwise the recipe date will be on the same day, but in 2009.

The remainder of the scenario references the two recipes by the differing ingredient (e.g. the recipe with "buttermilk" or the recipe with "lowfat milk"). So, while I am working on the step that creates these recipes, I store their CouchDB location in a lookup by the ingredient name:
  @permalink_identified_by ||= { }
@permalink_identified_by[ingredient] = permalink
The next undefined Cucumber step describes marking one recipe as an update to the other. In its very first incarnation, the cookbook was XML document based, we stored the updates in the following format:
<updates>
<update year="2001" month="09" day="02" label="potatoes"/>
<update year="2003" month="08" day="17" label="potatoes">
<desc>
Updating one of our very first recipes. The old picture was really
poor. Not much to complain about with regards to the write-up.
</desc>
</update>
</updates>
To update for JSON format needed in CouchDB, I define the next step as:
When /^the "([^\"]*)" recipe is marked as update of the "([^\"]*)" recipe$/ do |arg1, arg2|
update = {
:type => "Update",
:name => "buttermilk pancakes",
:updates => [
{ :id => @permalink_identified_by[arg2] },
{ :id => @permalink_identified_by[arg1] }
]
}

RestClient.put "#{@@db}/buttermilk_pancake_updates",
update.to_json,
:content_type => 'application/json'
end
The next step, "When I visit the recipe with buttermilk in it", is an easy visit step:
When /^I visit the recipe with "([^\"]*)" in it$/ do |ingredient|
visit "/recipes/#{@permalink_identified_by[ingredient]}"
end
With that I have the scenario passing the first 4 steps, but failing on the fifth:
cstrom@jaynestown:~/repos/eee-code$ cucumber -s \
features/recipe_replacement.feature:14
Sinatra::Test is deprecated; use Rack::Test instead.
Feature: Updating recipes in our cookbook

As an author
I want to mark recipes as replacing old one
So that I can record improvements and retain previous attempts for reference

Scenario: A previous version of the recipe
Given a "Buttermilk Pancake" recipe with "buttermilk" in it
And a "Buttermilk Pancake" recipe on another day with "lowfat milk" in it
When the "buttermilk" recipe is marked as update of the "lowfat milk" recipe
And I visit the recipe with "buttermilk" in it
Then I should see a link to the previous recipe with "lowfat milk" in it
expected following output to contain a <a>the previous recipe with "lowfat milk" in it</a> tag:
...
At this point it is time to start exploring. I have no means to determine when a recipe is an update to a previous recipe or if an recipe has been updated. Without that, I cannot get that next step passing.

Fortunately, CouchDB has a nice mechanism for prototyping to learn: temporary views. I create an "update" doc in the DB:
{
"_id": "roasted_potato_updates",
"_rev": "2-980816893",
"updates": [
{
"id": "2001-09-02-potatoes"
},
{
"id": "2003-08-17-potatoes"
}
],
"type": "Update"
}
After that, I try to create a view that will key off the latest recipe ("2003-08-17-potatoes" for the above document) with values of the updated recipes ("2001-09-02-potatoes" above). It takes a little doing, but I eventually work my way down to this map function:
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);
}
}
That works as desired:



Having learned through my prototype, I think this is a good place to stop. Tomorrow, I will make sure that I can do the opposite (lookup updates to a recipe) and then start driving development of this feature again.

No comments:

Post a Comment