‹prev | 
My Chain | 
next›Before this new 
CouchDB / 
Sinatra version of 
EEE Cooks is ready to replace the legacy rails site, there are a few more features that need to be completed:
- draft meals and recipes
 - alternate preparations for recipes
 - obsolete recipes (replaced by newer recipes)
 - mini-calendar for the homepage
 
After first ensuring that I have 
Cucumber feature descriptions for each of these things (
commit, 
commit), I get started on the draft document feature.
I write the feature from the perspective of the cookbook.  I considered writing it from the perspective of a web user, but why would a user care about draft vs. published recipes?  Someone reading a cookbook does not care how the recipes got there, just that the recipes that they see are of a certain quality.  I am not building an author interface—I will likely do that in a separate application another day.  So, it seemed proper to anthropomorphize the cookbook, making it care about the quality control:
Feature: Draft vs. Published Meals and Recipes
  As a cookbook dedicated to quality
  So that I can present only the best meals and recipes
  I want to hide drafts
The first scenario deals with how the cookbook hides draft recipes from the user:
    Scenario: Navigating between recipes
      Given "Recipe #1", published on 2009-08-01
        And "Recipe #2", drafted on 2009-08-05
        And "Recipe #3", published on 2009-08-10
        And "Recipe #4", drafted on 2009-08-20
       When I show "Recipe #1"
       Then there should be no link to "Recipe #2"
       When I am asked for the next recipe
       Then "Recipe #3" should be shown
        And there should be no next link
        And there should be a link to "Recipe #1"
I can get those three 
Given steps defined in one place:
Given /^"([^\"]*)", (\w+) on ([-\d]+)$/ do |title, status, date_str|
  date = Date.parse(date_str)
  recipe_permalink = date.to_s + "-" + title.downcase.gsub(/[#\W]+/, '-')
  recipe = {
    :title        => title,
    :date         => date,
    :summary      => "#{title} summary",
    :instructions => "#{title} instructions",
    :published    => status == 'published',
    :type => "Recipe"
  }
  RestClient.put "#{@@db}/#{recipe_permalink}",
    recipe.to_json,
    :content_type => 'application/json'
end
Each Given step has a title, a status, and a date in exactly the same place in the Given string.  Cucumber RegExp support comes in quite handy here.  The recipe creation is very similar to other recipe step definitions that I have done.  The only reason that I needed a new step definition at all was the 
:published attribute.  That survived the import from the legacy Rails application, but is doing nothing in the application just yet.
The first step towards using the 
:published attribute is to probe what happens when the cookbook displays the first recipe:
When /^I show "Recipe #1"$/ do
  visit("/recipes/#{@recipe_1_permalink}")
end
I opt 
not to use the template generated by Cucumber for this step, which would have read:
When /^I show "([^\"]*)"$/ do |arg1|
  pending
end
The title alone (the stuff that would have been between the quotes) is not sufficient for determining the URL of the recipe.  I could alter the feature text to read:
When I show "Recipe #1" from 2009-08-01
But then I would be duplicating the date in both the Given and When steps only to support the Cucumber steps.  I loathe the idea of sacrificing the readability of the feature text just to support step definitions.
With the When step out of the way, I define my first Then step—that there should be no link to the draft recipe—as:
Then /^there should be no link to "([^\"]*)"$/ do |title|
  response.should_not have_selector("a", :content => title)
end
This step fails:
cstrom@jaynestown:~/repos/eee-code$ cucumber features/draft_recipes.feature:7
Sinatra::Test is deprecated; use Rack::Test instead.
Feature: Draft vs. Published Meals and Recipes
  As a cookbook dedicated to quality
  So that I can present only the best meals and recipes
  I want to hide drafts
  Scenario: Navigating between recipes          # features/draft_recipes.feature:7
    Given "Recipe #1", published on 2009-08-01  # features/step_definitions/draft.rb:1
    And "Recipe #2", drafted on 2009-08-05      # features/step_definitions/draft.rb:1
    And "Recipe #3", published on 2009-08-10    # features/step_definitions/draft.rb:1
    And "Recipe #4", drafted on 2009-08-20      # features/step_definitions/draft.rb:1
    When I show "Recipe #1"                     # features/step_definitions/draft.rb:22
    Then there should be no link to "Recipe #2" # features/step_definitions/draft.rb:26
      expected following output to omit a <a>Recipe #2</a>:
...
        <div class="navigation">
          ‹
          
          |
          <a href="/recipes/2009-08-05-recipe-2">Recipe #2 (August  5, 2009)</a>
          ›
        </div>
...
       (Spec::Expectations::ExpectationNotMetError)
      features/draft_recipes.feature:13:in `Then there should be no link to "Recipe #2"'
    When I am asked for the next recipe         # features/draft_recipes.feature:14
    Then "Recipe #3" should be shown            # features/draft_recipes.feature:15
    And there should be no next link            # features/draft_recipes.feature:16
    And there should be a link to "Recipe #1"   # features/draft_recipes.feature:17
Failing Scenarios:
cucumber features/draft_recipes.feature:7 # Scenario: Navigating between recipes
1 scenario (1 failed)
10 steps (1 failed, 4 undefined, 5 passed)
0m0.583s
This signals to me that I need to work my way into the code—draft recipes are still being shown.  But...
The links between recipes (like intra-meal links) are being built by a helper method that, in turn, uses the results of a CouchDB view.  The by-date map function that I am currently using:
function (doc) {
  if (doc['type'] == 'Recipe') {
    emit(doc['date'], {'id':doc['_id'],'title':doc['title'],'date':doc['date']});
  }
}All I need to do is add to the conditional in there:
function (doc) {
  if (doc['type'] == 'Recipe' && doc['published'] == true) {
    emit(doc['date'], {'id':doc['_id'],'title':doc['title'],'date':doc['date']});
  }
}I am using my 
couch_docs gem in a 
Before block to assemble and load those design docs before each Cucumber run.  So all I have to is re-run the scenario:

Cool, that should be all of the actual work required for this scenario.  The remaining step definitions are all one liners:
When /^I am asked for the next recipe$/ do
  click_link "Recipe #"
end
Then /^"([^\"]*)" should be shown$/ do |title|
  response.should have_selector("h1", :content => title)
end
Then /^there should be no next link$/ do
  response.should_not have_selector("a", :content => "Recipe #4")
end
Then /^there should be a link to "([^\"]*)"$/ do |title|
  response.should have_selector("a", :content => title)
end
With that, the scenario is complete:

A previous scenario does end up failing because of the new published restriction.  After addressing that (by publishing the recipes in the Given step), I have 36 completed scenarios:
cstrom@jaynestown:~/repos/eee-code$ cucumber features -i 
...
36 scenarios (7 undefined, 29 passed)
308 steps (17 skipped, 36 undefined, 255 passed)
0m35.558s
(
commit)
Tomorrow, I hope to complete the published vs. draft feature.  It ought to be straight forward to get this working for both recipe searching and meal navigation.