Sunday, May 31, 2009

Breadcrumbs

‹prev | My Chain | next›

On tap for tonight are some breadcrumbs for the show meal page. For a meal prepared today (May 31, 2009), there should be breadcrumb links to the list of meals in May, and to the list of meals in 2009.

The link to meals this year, in RSpec notation:
  it "should display a breadcrumb link to the other meals in this year" do
render("/views/meal.haml")
response.should have_selector(".breadcrumbs a",
:href => "/meals/#{@year}")
end
(the @year instance variable comes from the before(:each) block).

To make this example pass, I add the following Haml code:
- date = Date.parse(@meal['date'])
.breadcrumbs
%a{:href => "/meals/#{date.year}"}= date.year
It is debatable if parsing the date string, as I do here, or splitting the date string on dashes (e.g. "2009-05-31".split(/-/)[0]) is the easiest thing to do. I make an educated guess here that things will be easier for me if I have a Date object.

With the year breadcrumb in place, it is time for the month breadcrumb. The RSpec example:
  it "should display a breadcrumb link to the other meals in this month" do
render("/views/meal.haml")
response.should have_selector(".breadcrumbs a",
:href => "/meals/#{@year}/#{"%02d" % @month}")
end
(Note: I'm using the % shortcut for sprintf in that example)

To make that example pass, I make use of the strftime method on the date object (that date object did come in handy):
- date = Date.parse(@meal['date'])
.breadcrumbs
%a{:href => "/meals/#{date.year}"}= date.year
>
%a{:href => "/meals/#{date.strftime("%Y/%m")}"}= date.strftime("%B")
From cheat strftime, the options that are being used:
    %B - The  full  month  name (``January'')
%m - Month of the year (01..12)
%Y - Year with century


With that code work done, I am ready to move on back out to the Cucumber scenario:



I use Cucumber's suggestion to define the next, very simple, step:
When /^I click the "([^\"]*)" link$/ do |text|
click_link text
end
That simple step gets me tantalizingly close to being done with this scenario. There is much blue (not reached, but implemented steps) and only three remaining steps to be implemented (two of them the same):



I implement the next step (Then I should see "Focaccia!" in the list of meals):
Then /^I should see "([^\"]*)" in the list of meals$/ do |title|
response.should have_selector("a", :content => title)
end
Unfortunately, when I run the scenario through Cucumber, I find:
cstrom@jaynestown:~/repos/eee-code$ cucumber features -n \
-s "Browsing a meal on a specific date"
Feature: Browse Meals

So that I can find meals made on special occasions
As a person interested in finding meals
I want to browse meals by date

Scenario: Browsing a meal on a specific date
Given a "Focaccia" recipe from March 3, 2009
Given a "Focaccia!" meal with the "Focaccia" recipe on the menu
When I view the "Focaccia!" meal
Then I should see the "Focaccia!" title
And I should see a "Focaccia" recipe link in the menu
When I click the "March" link
Then I should see "Focaccia!" in the list of meals
expected following output to contain a <a>Focaccia!</a> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<h1>Meals from March 2009</h1>
<div class="navigation">

|

</div>
<div class="meals">

<h2>
<span class="date">2009-03-03</span>
<span class="title">Focaccia!</span>
</h2>
<p>meal summary</p>
<div class="menu"><p><a href="/recipes/2009-03-03-focaccia">Focaccia</a></p></div>
</div>
</body></html>
(Spec::Expectations::ExpectationNotMetError)
features/browse_meals.feature:44:in `Then I should see "Focaccia!" in the list of meals'
When I click the "Focaccia" link
And I click the "2009" link
Then I should see "Focaccia!" in the list of meals
When I click the "Focaccia!" link
And I click the "Focaccia" link
Then I should see the "Focaccia" recipe

1 scenario
1 failed step
5 skipped steps
1 undefined step
6 passed steps
Aw nuts. When I implemented the list of meals, I failed to actually link to the meal. That is a fairly easy thing to resolve. Tomorrow.

I will again leave myself at red in the Red-Green-Refactor cycle to quickly pick up where I leave off today.

Saturday, May 30, 2009

Meal Menu Items

‹prev | My Chain | next›

I left off yesterday with a failing Cucumber step, making it easy to remember where to pick up today:
cstrom@jaynestown:~/repos/eee-code$ cucumber features -n \
> -s "Browsing a meal on a specific date"
Feature: Browse Meals

So that I can find meals made on special occasions
As a person interested in finding meals
I want to browse meals by date

Scenario: Browsing a meal on a specific date
Given a "Focaccia!" meal enjoyed on March 3, 2009
And a "Focaccia" recipe from March 3, 2009
When I view the "Focaccia!" meal
Then I should see the "Focaccia!" title
And I should see a link to the "Focaccia" recipe in the menu
expected following output to contain a <a>the "Focaccia" recipe in the menu</a> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<h1>
Focaccia!
</h1>
<div id="summary">
<p>meal summary</p>
</div>
<div id="description">
<p>meal description</p>
</div>
</body></html>
(Spec::Expectations::ExpectationNotMetError)
features/browse_meals.feature:42:in `And I should see a link to the "Focaccia" recipe in the menu'
...
Before I get recipes in the meal's menu, I need to get the menu itself presented via the Haml template. In order to describe how the Haml template will display the menu, I add a single menu item to the sample meal in the before(:each) block in the RSpec specification:

before(:each) do
@title = "Meal Title"
@summary = "Meal Summary"
@description = "Meal Description"
assigns[:meal] = @meal = {
'title' => @title,
'summary' => @summary,
'description' => @description,
'menu' => ["Peanut Butter and Jelly Sandwich"]
}
end
I would like to display the menu after the summary, in CSS3 terms: "#summary + #menu". The example that I would like to have pass is:
  it "should include the menu after the summary" do
render("/views/meal.haml")
response.should have_selector("#summary + ul#menu li",
:content => "Peanut Butter and Jelly Sandwich")
end
I can get that example to pass with some more minimalistic Haml:
#summary
= wiki @meal['summary']

%ul#menu
- @meal['menu'].each do |menu_item|
%li= menu_item
To get the recipes in the menu items, I need only call the wiki helper, which applies the recipe_link helper to [recipe:...] wiki text. I am not going to test that the Haml template properly pulls the recipe into the menu—too much composition in a unit test for my taste. I will leave that to the Cucumber scenario. To drive the Haml template, I will use an example of simple Textile wikifying:
  it "should wikify the menu items" do
assigns[:meal]['menu'] << "foo *bar* baz"
render("/views/meal.haml")
response.should have_selector("ul#menu li strong",
:content => "bar")
end
A simple invocation of the wiki helper makes that example pass:
%ul#menu
- @meal['menu'].each do |menu_item|
%li= wiki(menu_item)
Back in Cucumber, I realize that I need to create the recipe before I create the meal (so that the meal can include the recipe in its menu). With some quick rearranging of the scenario text, I am able to verify that recipe links in meal menu items do, indeed, work as expected:



(commit)

Tomorrow, it looks like I will be working through the breadcrumbs on the meal page.

Friday, May 29, 2009

Simple Meal Attributes

‹prev | My Chain | next›

Next up in the meal details scenario is to actually see some details of the meal. Specifically:
Then I should see the "Focaccia!" title
Cucumber is telling me to use this as starting point:
Then /^I should see the "([^\"]*)" title$/ do |arg1|
pending
end
Little more than that is actually required to verify this step:
Then /^I should see the "([^\"]*)" title$/ do |title|
response.should have_selector("h1", :content => title)
end
That step fails, of course, since the Sinatra action is not pulling back the meal from CouchDB and, even if it was, there is no Haml template to present the CouchDB data. So let's add these...

I implement the body of the show meal action such that it retrieves the meal data from CouchDB:
      it "should request the meal from CouchDB" do
RestClient.
should_receive(:get).
with(/2009-05-13/).
and_return('{"title":"Foo"}')

get "/meals/2009/05/13"
end
With Sinatra pulling the data from CouchDB, it is time to present that data via a Haml view.

For now, I will describe displaying the meal data proper (links to breadcrumbs, other meals and recipes will come later). To display these attributes, I supply each of my examples with:
  before(:each) do
@title = "Meal Title"
@summary = "Meal Summary"
@description = "Meal Description"
assigns[:meal] = @meal = {
'title' => @title,
'summary' => @summary,
'description' => @description
}
end
The specs that describe showing these three meal attributes:
  it "should display the meal's title" do
render("/views/meal.haml")
response.should have_selector("h1", :content => @title)
end

it "should display the meal's summary" do
render("/views/meal.haml")
response.should have_selector("#summary", :content => @summary)
end

it "should display a description of the meal" do
render("/views/meal.haml")
response.should have_selector("#summary + #description",
:content => @description)
end
I still have not decided if I like Haml, but it does make implementing these three examples simple:
%h1
= @meal['title']

#summary
= @meal['summary']

#description
= @meal['description']
Before working my way out, I am also going to note that both the meal summary and description should be wiki-fied. The example for a wiki-fied summary:
  it "should wikify the meal's summary" do
assigns[:meal]['summary'] = "paragraph 1\n\nparagraph 2"
render("/views/meal.haml")
response.should have_selector("#summary p", :content => "paragraph 1")
end
That fails the first time I run it:
1)
'meal.haml should wikify the meal's summary' FAILED
expected following output to contain a <#summary p>paragraph 1</#summary p> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<h1>
Meal Title
</h1>
<div id="summary">
paragraph 1

paragraph 2
</div>
<div id="description">
Meal Description
</div>
</body></html>
./spec/views/meal.haml_spec.rb:28:
Adding a call to my wiki helper makes that pass:
#summary
= wiki @meal['summary']
After following the same path to wiki-fy the meal description, it is time to work my way back out to Cucumber to verify that I have put everything together properly:
cstrom@jaynestown:~/repos/eee-code$ cucumber features -n \
> -s "Browsing a meal on a specific date"
Feature: Browse Meals

So that I can find meals made on special occasions
As a person interested in finding meals
I want to browse meals by date

Scenario: Browsing a meal on a specific date
Given a "Focaccia!" meal enjoyed on March 3, 2009
And a "Focaccia" recipe from March 3, 2009
When I view the "Focaccia!" meal
Then I should see the "Focaccia!" title
And I should see a link to the "Focaccia" recipe in the menu
expected following output to contain a <a>the "Focaccia" recipe in the menu</a> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<h1>
Focaccia!
</h1>
<div id="summary">
<p>meal summary</p>
</div>
<div id="description">
<p>meal description</p>
</div>
</body></html>
(Spec::Expectations::ExpectationNotMetError)
features/browse_meals.feature:42:in `And I should see a link to the "Focaccia" recipe in the menu'
When I click the "March" link
Then I should see "Focaccia!" in the list of meals
When I click the "Focaccia" link
And I click the "2009" link
Then I should see "Focaccia!" in the list of meals
When I click the "Focaccia!" link
And I click the "Focaccia" link
Then I should see the "Focaccia" recipe

1 scenario
1 failed step
8 undefined steps
4 passed steps
Indeed, I have made it past the Meal should display a title step. I am now onto the next step, where it should display a recipe menu item.

That's a good stopping point for tonight. I like stopping on Red in the Red-Green-Refactor cycle. It makes it easy know where to pick up next!
(commit)

Thursday, May 28, 2009

The Simplest Meal Possible

‹prev | My Chain | next›

The next scenario up is "Browsing a meal on a specific date":
  Scenario: Browsing a meal on a specific date

Given a "Focaccia!" meal enjoyed on March 3, 2009
When I view the meal
Then I should see the "Focaccia!" title
And I should be able to follow a link to the list of meals in March of 2009
That's a pretty weak Cucumber scenario. It describes only very superficially what a user would expect to see on a meal page (a title and a link to the list of meals in that month).

Users should also be able to follow links to:

  • the list of meal in the current year (the year and month serving a breadcrumbs)

  • recipes prepared for the current meal

  • the previous and subsequent meals prepared

That's a lot not described by that scenario. So let's split it into two scenarios—one for browsing on a particular date and one between dates:
  Scenario: Navigating between meals

Given a "Focaccia!" meal enjoyed on March 3, 2009
And a "Star Wars: The Dinner" meal enjoyed on February 28, 2009
And "Pumpkin is a Very Exciting Vegetable" meal enjoyed on December 3, 2008
When I view the "Focaccia!" meal
Then I should see the "Focaccia!" title
When I click "Star Wars: The Dinner"
Then I should see the "Star Wars: The Dinner" title
When I click "Pumpkin is a Very Exciting Vegetable"
Then I should see the "Pumpkin is a Very Exciting Vegetable" title
When I click "Star Wars: The Dinner"
Then I should see the "Star Wars: The Dinner" title
When I click "Focaccia!"
Then I should see the "Focaccia!" title

Scenario: Browsing a meal on a specific date

Given a "Focaccia!" meal enjoyed on March 3, 2009
And a "Focaccia" recipe from March 3, 2009
When I view the "Focaccia!" meal
Then I should see the "Focaccia!" title
And I should see a link to the "Focaccia" recipe in the menu
When I click the "March" link
Then I should see "Focaccia!" in the list of meals
When I click the "Focaccia" link
And I click the "2009" link
Then I should see "Focaccia!" in the list of meals
When I click the "Focaccia!" link
And I click the "Focaccia" link
Then I should see the "Focaccia" recipe
That's more like it! There are many new steps to be implemented, so I may as well get started immediately.

First up in the "Browsing a meal on a specific date" scenario is the Given step, "Given a "Focaccia!" meal enjoyed on March 3, 2009". When I run the entire scenario, it shows up in the output a yellow (not implemented). That's odd, I have yet to implement a "Given a meal" step?

Actually, I have:
Given /^a "([^\"]*)" meal enjoyed in (.+)$/ do |title, date_str|
Why doesn't that match "Given a "Focaccia!" meal enjoyed on March 3, 2009"? Ah, the text is on March 3, but the step implementation would only match in March 3. I can fix that with a simple RegExp character class:
Given /^a "([^\"]*)" meal enjoyed [io]n (.+)$/ do |title, date_str|
With that I have one Given step done. Cucumber tells me that I need to get the next one done:
You can implement step definitions for missing steps with these snippets:

Given /^a "([^\"]*)" recipe from March 3, 2009$/ do |arg1|
pending
end
I take that and implement it in features/recipe_details.rb:
Given /^a "([^\"]*)" recipe from (.+)$/ do |title, 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"
}

RestClient.put "#{@@db}/#{@recipe_permalink}",
recipe.to_json,
:content_type => 'application/json'
end
Now I have reached the when-I-visit-the meal step, which I implement as:
When /^I view the "([^\"]*)" meal$/ do |arg1|
visit("/meals/#{@meal_permalink}")
response.status.should == 200
end
That step fails, of course, so it is time to work my way into the actual code. The example that describes the desired behavior (simply that it responds OK):
    describe "GET /meals/YYYY/MM/DD" do
it "should respond OK" do
get "/meals/2009/05/28"
response.should be_ok
end
end
And the simplest thing to make this code pass is:
get %r{/meals/(\d+)/(\d+)/(\d+)} do |year, month, day|
end
Back in the Cucumber scenario, I expect the visit-the-meal step to pass, but am disappointed to find:
cstrom@jaynestown:~/repos/eee-code$ cucumber features \
-s "Browsing a meal on a specific date"
Feature: Browse Meals

So that I can find meals made on special occasions
As a person interested in finding meals
I want to browse meals by date

Scenario: Browsing a meal on a specific date # features/browse_meals.feature:36
Given a "Focaccia!" meal enjoyed on March 3, 2009 # features/step_definitions/meal_details.rb:1
And a "Focaccia" recipe from March 3, 2009 # features/step_definitions/recipe_details.rb:137
When I view the "Focaccia!" meal # features/step_definitions/meal_details.rb:32
expected: 200,
got: 404 (using ==)
Diff:
@@ -1,2 +1,2 @@
-200
+404
(Spec::Expectations::ExpectationNotMetError)
features/browse_meals.feature:40:in `When I view the "Focaccia!" meal'
...
Ah, I am storing meals in CouchDB in ISO 8601 format, but the URLs for the Sinatra app have slashes (e.g. 2009/05/28). A simple gsub in the visit step will resolve my issue:
When /^I view the "([^\"]*)" meal$/ do |arg1|
visit("/meals/#{@meal_permalink.gsub(/-/, '/')}")
response.status.should == 200
end
And now I have three passing steps:



Next up, serious "inside" work, retrieving the meal, displaying it and the necessary links to implement the remaining steps in that scenario. Tomorrow.
(commit)

Wednesday, May 27, 2009

Inventory Before Moving On

‹prev | My Chain | next›

First up tonight, I follow the advice of an astute reader and probe a date boundary in a little more depth. In my browse-meals-by-month Cucumber scenario, I had only probed navigation between months, but failed to perform similar navigation across a year boundary.

To address this, I add a meal from 2003 to the scenario (in bold):
  Scenario: Browsing a meal in a given month

Given a "Even Fried, They Won't Eat It" meal enjoyed in May 2009
And a "Salad. Mmmm." meal enjoyed in April 2009
And a "Almost French Onion Soup" meal enjoyed in September 2003
When I view the list of meals prepared in May of 2009
Then I should see the "Even Fried, They Won't Eat It" meal among the meals of this month
And I should not see the "Salad. Mmmm." meal among the meals of this month
And I should not see a link to June 2009
When I follow the link to the list of meals in April 2009
Then I should not see the "Even Fried, They Won't Eat It" meal among the meals of this month
And I should see the "Salad. Mmmm." meal among the meals of this month
And I should see a link to May 2009
And I should not see a link to February 2009
When I follow the link to the list of meals in September 2003
Then I should see the "Almost French Onion Soup" meal among the meals of this month
And I should see a link to April 2009
Running the scenario now, everything is passing:



Nice! I didn't need to add any code, but now have assurance that it will work, even across year boundaries.

With that, I am considering that scenario complete.
(commit)

Before moving onto the next scenario, I run all of the Cucumber scenarios to ensure that I haven't accidentally broken something and to see where I'm at. Where I am at is:



135 passed steps and only 4 step undefined am I really that close to being done? Sadly no.

I need to do a quick inventory of what is left before I am done:
  • The home page

  • CSS—everything is unstyled at this point

  • Deploy CouchDB design documents

  • Advanced search interface

  • RSS Feeds

  • Mailing lists

  • Feedback forms

  • Cookbook collection—a list of the cookbooks (linked to via our Amazon associates account) from which various recipes draw inspiration

Since I am replacing an existing site, I probably cannot get away with omitting many of those. I ought to be able to deploy without the advanced search form, the cookbook collection, and the mailing lists, at least initially. But I will definitely need a home page and CSS. I probably should not orphan the RSS feed either.

That means more Cucumber scenarios. First up, RSS feeds...

I like to anthropomorphize all non-human actors. It helps me to conceptualize what really needs to be done. Nothing too fancy in the case of RSS consumers:
 Feature: RSS

So that I tell my user when there are updates to this great cooking site
As an RSS bot
I want to be able to consume your RSS

Scenario: Meal RSS

Given 20 yummy meals
When I access the meal RSS feed
Then I should see the 10 most recent meals
And I should see the summary of each meal

Scenario: Recipe RSS

Given 20 delicious, easy to prepare recipes
When I access the recipe RSS feed
Then I should see the 10 most recent recipes
And I should see the summary of each recipe
Additionally, I add a general site-wide feature description with 5 scenarios (individual step text not included for brevity's sake):
Feature: Site

So that I may explore many wonderful recipes and see the meals in which they were served
As someone interested in cooking
I want to be able to easily explore this awesome site

Scenario: Quickly scanning meals and recipes accessible from the home page

Scenario: Exploring food categories (e.g. Italian) from the homepage

Scenario: Give feedback to the authors of this fantastic site

Scenario: Give feedback to the authors on a yummy meal

Scenario: Give feedback to the authors on a delicious recipe

(commit)

With that, I am up to 54 undefined Cucumber steps. That's somewhat deflating, but should serve as a fairly realistic estimate of the outstanding work remaining before I can deploy.

And I'll get started with the first of those 54 steps tomorrow.

Tuesday, May 26, 2009

Bold Cucumber Scenario

‹prev | My Chain | next›

Another Cucumber scenario went green yesterday. Green, but not much bold:



Bold text in Cucumber output indicates that it is consumed for direct use when the running individual steps. For example, consider the first Given step:
Given a "Even Fried, They Won't Eat It" meal enjoyed in May of 2009
This steps builds a meal, using the text "Even Fried, They Won't Eat It" as the title and the text "May of 2009" as the date for the meal.

As much as possible, it is a good idea to use Cucumber text when implementing the steps behind them. Re-using text means that the human readable text in the scenario is usually matching up with human readable text in the application itself. As a general rule of thumb, the more human readable a web page, the better.

There is no bold text in the four should / should not steps that check for links to dates. Each of those has to hard-code the application representation of those date:
Then /^I should see a link to May of 2009$/ do
response.should have_selector("a", :content => "2009-05")
end
"May of 2009" is easier to read, which is why I wrote it that way in the feature description. I should have carried that readability over into the application. If I do so now, I can also increase the bold in the Cucumber output, DRYing things up a bit as well.

First up is a helper to translate date fragments like "2009-05" into "May 2009". In other words:
describe "month_text" do
it "should increase readability of an ISO8601 date fragment" do
month_text("2009-05").
should == "May 2009"
end
end
This can be implemented with a little help from strftime:
    def month_text(date_frag)
Date.parse("#{date_frag}-01").strftime("%B %Y")
end
To get the navigation between month to use this date format, I would like to pass it to the link_to_adjacent_view_date helper as an optional block:
    it "should link to block text, if supplied" do
link_to_adjacent_view_date("2009-04", @count_by_month) do
"foo"
end.
should have_selector("a",
:href => "/meals/2009/05",
:content => "foo")
end
More specifically, I want the link_to_adjacent_view_date helper to yield with the adjacent date fragment so that it can be humanized by month_text:
    it "should link to block text + date, if block is given" do
link_to_adjacent_view_date("2009-04", @count_by_month) do |date|
"foo #{date} bar"
end.
should have_selector("a",
:href => "/meals/2009/05",
:content => "foo 2009-05 bar")
end
Getting those two examples to pass is a simple matter of yielding appropriately in the helper. Something along the lines of:
        link_text = block_given? ? yield(next_result['key']) : next_result['key']
With those examples passing, I update the meals-by-months Haml view such that the navigation links use the new optional block and the humanizing month_text helper:
%div.navigation
=link_to_adjacent_view_date(@month, @count_by_year, :previous => true) { |d| month_text(d) }
|
=link_to_adjacent_view_date(@month, @count_by_year) { |d| month_text(d) }
Finally (after verifying that all specs still pass), I work my way back out to the Cucumber scenario. I remove the "of"s from the steps so that they read "I should see a link to May 2009" (instead of "May of 2009"). Then I can consolidate the step implementations down to:
Then /^I should see a link to (.+)$/ do |date|
response.should have_selector("a", :content => date)
end

Then /^I should not see a link to (.+)$/ do |date|
response.should_not have_selector("a", :content => date)
end
Running the scenario now, I find much more bold text in the output:



A little bit of refactoring and my code is DRYer and more human-readable.
(commit)

That completes this red-green-refactor cycle. Tomorrow, it's on to the next scenario.

Monday, May 25, 2009

Meals by Month Goes Green (with a little helper from Cucumber)

‹prev | My Chain | next›

I left off yesterday with a failing Cucumber step, making it easy to know where to start today:
cstrom@jaynestown:~/repos/eee-code$ cucumber -n features -s "Browsing a meal in a given month"
Feature: Browse Meals

So that I can find meals made on special occasions
As a person interested in finding meals
I want to browse meals by date

Scenario: Browsing a meal in a given month
Given a "Even Fried, They Won't Eat It" meal enjoyed in May of 2009
And a "Salad. Mmmm." meal enjoyed in April of 2009
When I view the list of meals prepared in May of 2009
Then I should see the "Even Fried, They Won't Eat It" meal among the meals of this month
And I should not see the "Salad. Mmmm." meal among the meals of this month
And I should not see a link to June of 2009
When I follow the link to the list of meals in April of 2009
Then I should not see the "Even Fried, They Won't Eat It" meal among the meals of this month
And I should see the "Salad. Mmmm." meal among the meals of this month
expected following output to contain a <h2>Salad. Mmmm.</h2> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<h1>Meals from 2009</h1>
<div class="navigation">

|

</div>
<ul>
<li>
<a href="/meals/id-2009-05-01">Even Fried, They Won't Eat It</a>
</li>
</ul>
</body></html>
(Spec::Expectations::ExpectationNotMetError)
features/browse_meals.feature:28:in `And I should see the "Salad. Mmmm." meal among the meals of this month'
And I should not see a link to February of 2009
And I should see a link to May of 2009

1 scenario
1 failed step
2 undefined steps
8 passed steps
I barely even glanced at the failure yesterday, but today I notice that the H1 title is describing "Meals from 2009" rather than "Meals from April, 2009". Somehow the link being followed to "to the list of meals in April of 2009" is taking Cucumber to the list of meals in 2009 instead.

Before in-depth investigation of the helper responsible for those links, I think now is a good time to align the language of the helper with its usage. Specifically, it should be less year-oriented and more date fragment-oriented:
    def link_to_year_in_set(current, couch_view, options={})
compare_years = options[:previous] ?
Proc.new { |year, current_year| year < current_year} :
Proc.new { |year, current_year| year > current_year}

next_result = couch_view.
send(options[:previous] ? :reverse : :map).
detect{|result| compare_years[result['key'].to_i, current.to_i]}

if next_result
%Q|<a href="/meals/#{next_result['key']}">#{next_result['key']}</a>|
else
""
end
end
The more date fragment oriented rewrite:
    def link_to_adjacent_view_date(current, couch_view, options={})
compare = options[:previous] ?
Proc.new { |date_fragment, current| date_fragment < current} :
Proc.new { |date_fragment, current| date_fragment > current}

next_result = couch_view.
send(options[:previous] ? :reverse : :map).
detect{|result| compare[result['key'], current.to_s]}

if next_result
%Q|<a href="/meals/#{next_result['key']}">#{next_result['key']}</a>|
else
""
end
end
After updating various specs and consuming Haml templates to use the renamed function, I take a closer look at the link output. The CouchDB by_month map-reduce views that are of the form (after being parsed into Ruby):
{"value"=>3, "key"=>"2009-04"}
The value indicates the total number of meals in the month indicated by the key (in the above example, there were 3 meals from April of 2009).

Looking back to the output of the helper, I would end up with the following for the "2009-04" example:
<a href="/meals/2009-04">2009-04</a>
The problem is that the Sinatra app does not recognize "/meals/2009-04" as a by-month URL because the dash does match the RegExp:
get %r{/meals/(\d+)/(\d+)} do |year, month|
...
end
That URL does match the by-year action (which completely ignores the month portion of the URL):
get %r{/meals/(\d+)} do |year|
...
end
Yet again, Cucumber has caught a composition error on my part. The individual components all work according to specification—one can easily make the case that the URL "/meal/2009-04" is a valid by-month URL—but the assembly of those pieces into a whole missed a vital piece of information.

I could address this oversight by making the URL accept dashes or by making the helper replace dashes with slashes. I opt for the latter—I simply prefer the simplicity of an all slash URL.

The example that I use to describe this behavior, including a by-month context, is:
  context "couchdb view by_month" do
before(:each) do
@count_by_month = [{"key" => "2009-04", "value" => 3},
{"key" => "2009-05", "value" => 3}]
end
it "should link to the next month after the current one" do
link_to_adjacent_view_date("2009-04", @count_by_month).
should have_selector("a",
:href => "/meals/2009/05")
end
end
That fails, as expected, because the href still has a dash in it:
cstrom@jaynestown:~/repos/eee-code$ spec spec/eee_helpers_spec.rb 
.............................F

1)
'link_to_adjacent_view_date couchdb view by_year should link to the next month after the current one' FAILED
expected following output to contain a <a href='/meals/2009/05'/> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><a href="/meals/2009-05">2009-05</a></body></html>
./spec/eee_helpers_spec.rb:228:

Finished in 0.025192 seconds

30 examples, 1 failure
To make it pass, I add a computation of the next_uri to the helper:
    def link_to_adjacent_view_date(current, couch_view, options={})
compare = options[:previous] ?
Proc.new { |date_fragment, current| date_fragment < current} :
Proc.new { |date_fragment, current| date_fragment > current}

next_result = couch_view.
send(options[:previous] ? :reverse : :map).
detect{|result| compare[result['key'], current.to_s]}

if next_result
next_uri = next_result['key'].gsub(/-/, '/')
%Q|<a href="/meals/#{next_uri}">#{next_result['key']}</a>|

else
""
end
end
Not only does that make that example pass, but, working my way back out to the Cucumber scenario, I find that all currently defined step are passing as well!



Even better, those last two steps are trivial to implement:
Then /^I should not see a link to February of 2009$/ do
response.should_not have_selector("a", :content => "2009-02")
end

Then /^I should see a link to May of 2009$/ do
response.should have_selector("a", :content => "2009-05")
end
And now the entire scenario is passing:
cstrom@jaynestown:~/repos/eee-code$ cucumber -n features \
-s "Browsing a meal in a given month"
Feature: Browse Meals

So that I can find meals made on special occasions
As a person interested in finding meals
I want to browse meals by date

Scenario: Browsing a meal in a given month
Given a "Even Fried, They Won't Eat It" meal enjoyed in May of 2009
And a "Salad. Mmmm." meal enjoyed in April of 2009
When I view the list of meals prepared in May of 2009
Then I should see the "Even Fried, They Won't Eat It" meal among the meals of this month
And I should not see the "Salad. Mmmm." meal among the meals of this month
And I should not see a link to June of 2009
When I follow the link to the list of meals in April of 2009
Then I should not see the "Even Fried, They Won't Eat It" meal among the meals of this month
And I should see the "Salad. Mmmm." meal among the meals of this month
And I should not see a link to February of 2009
And I should see a link to May of 2009

1 scenario
11 passed steps
Yay!
(commit)

My red-green-refactor development cycle has gotten me to green. There is a definite opportunity to refactor the should / should not see a link to steps. I will tackle that next before working my way onto the next scenario.

Sunday, May 24, 2009

Ah, Sweet Re-Use

‹prev | My Chain | next›

Having finished up work inside the meals-by-month Haml template last night, it's back out to the Cucumber scenario to double check things and see what's next.

After a quick fix (adding menu items to the Given step), I am at:



Looking at those next two steps, I get to thinking that they are very similar to the link to next / previous years work that I already did. The current implementation:
    def link_to_year_in_set(current, couch_view, options={})
compare_years = options[:previous] ?
Proc.new { |year, current_year| year < current_year} :
Proc.new { |year, current_year| year > current_year}

next_result = couch_view.
send(options[:previous] ? :reverse : :map).
detect{|result| compare_years[result['key'].to_i, current.to_i]}

if next_result
%Q|<a href="/meals/#{next_result['key']}">#{next_result['key']}</a>|
else
""
end
end
Aside from the method name and variable names seemingly tied to a year, there is nothing in there that is specific to years—except the to_i call on the years (won't work on "2009-05"). If I use strings instead, that helper ought to work just as well for ISO 8601 substrings (e.g. 2009-05). The above line, reworked for strings:
        detect{|result| compare_years[result['key'], current.to_s]}
After running all of my specs and Cucumber scenarios to verify that the change had no effect on the underlying behavior, I add this to meals-by-month Haml template:
%div.navigation
=link_to_year_in_set("#{@year}-#{@month}", @count_by_year, :previous => true)
|
=link_to_year_in_set("#{@year}-#{@month}", @count_by_year)
To verify that this is working, I implement the next two Cucumber scenario steps as:
Then /^I should not see a link to June of 2009$/ do
response.should_not have_selector("a", :content => "2009-06")
end

When /^I follow the link to the list of meals in April of 2009$/ do
click_link "2009-04"
end
And they pass!



Not sure why that next scenario is failing, but it'll serve as an obvious spot to pick up tomorrow.

Saturday, May 23, 2009

I Already Did That?

‹prev | My Chain | next›

The last spec in the meals-by-month Haml spec is:
  it "should include recipe titles in the menu items"
Recipes are included in meal items (or anywhere in the text of meals and recipes) in a wiki format:
[recipe:2009/05/23/lemon_dressing]
Specifying how this will work:
  it "should include recipe titles in the menu items" do
assigns[:meals]['rows'][0]['value'][0]["menu"] =
[" Salad with [recipe:2009/05/23/lemon_dressing] "]

render("/views/meal_by_month.haml")
response.should have_selector(".menu",
:content => "Salad with Lemon Dressing")
end
In that example, I add a menu item that contains a wiki recipe link. After rendering the template, I expect that the title of that recipe will be included in the menu section of the output.

That spec has no chance of working right now because I have yet to even supply the recipe with the appropriate title. I need to figure out how I am going to supply that to the template. Them's implementation details, so let's have a look at how the rest of the wikifying code is implemented so far:
    def wiki(original)
text = (original || '').dup
text.gsub!(/\b(\d+)F/, "\\1° F")
text.gsub!(/\[kid:(\w+)\]/m) { |kid| kid_nicknames[$1] }
text.gsub!(/\[recipe:(\S+)\]/m) { |r| recipe_link($1) }
RedCloth.new(text).to_html
end
Hey, what's that recipe_link line in there? That looks exactly like what I wanted to implement. Turns out, I already did implement it:
    def recipe_link(permalink)
recipe = JSON.parse(RestClient.get("#{_db}/#{permalink}"))
%Q|<a href="/recipes/#{recipe['_id']}">#{recipe['title']}</a>|
end
Weird that I forgot doing that. It seems that implementing and re-implementing this application over-and-over may be finally catching up with me.

At any rate, if I stub out that RestClient call to return a recipe with a title, this spec may already be working! The spec with the RestClient stub:
  it "should include recipe titles in the menu items" do
assigns[:meals]['rows'][0]['value'][0]["menu"] =
[" Salad with [recipe:2009/05/23/lemon_dressing] "]

self.stub!(:_db).and_return("")

RestClient.
stub!(:get).
and_return('{"title":"Lemon Dressing"}')

render("/views/meal_by_month.haml")
response.should have_selector(".menu",
:content => "Salad with Lemon Dressing")
end
That :_db stub is another implementation detail, mostly owing to the way that I implemented my Sinatra / RSpec setup. With those two additions, the spec passes. Nice! No new code and I have the last meals-by-month Haml spec passing.

Since I did not have to add new code, but I do recall a use case that I overlooked in the original implementation, it can't hurt to do so now, while I am in the code already. Recipe wiki text of the form [recipe:2009/05/23/lemon_dressing Salad Dressing] should contain the supplied text, "Salad Dressing". The example describing this behavior:
    it "should wikify recipe URIs, using supplied text for the link" do
RestClient.stub!(:get).
and_return('{"_id":"id-123","title":"Title"}')

wiki("[recipe:id-123 Different Title]").
should have_selector("a",
:href => "/recipes/id-123",
:content => "Different Title")
end
I implement that with a non-greedy RegExp match (so that other text doesn't accidentally get slurped in):
      text.gsub!(/\[recipe:(\S+)\s(.+?)\]/m) { |r| recipe_link($1, $2) }
That changes the arity of recipe_link. So that it works with or without the title, I update the recipe_link helper thusly:
    def recipe_link(permalink, title=nil)
recipe = JSON.parse(RestClient.get("#{_db}/#{permalink}"))
%Q|<a href="/recipes/#{recipe['_id']}">#{title || recipe['title']}</a>|
end
So now I am ready to move on back out to the Cucumber scenario. Tomorrow.

Friday, May 22, 2009

Menu Items

‹prev | My Chain | next›

Let's see, where was I?
cstrom@jaynestown:~/repos/eee-code$ rake
(in /home/cstrom/repos/eee-code)

==
Sinatra app spec
.....................

Finished in 0.263321 seconds

21 examples, 0 failures

==
Helper specs
............................

Finished in 0.021924 seconds

28 examples, 0 failures

==
View specs
....**...................................

Pending:

meal_by_month.haml should include the menu items (Not Yet Implemented)
./spec/views/meal_by_month.haml_spec.rb:52

meal_by_month.haml should include recipe titles in the menu items (Not Yet Implemented)
./spec/views/meal_by_month.haml_spec.rb:54

Finished in 0.221946 seconds

41 examples, 0 failures, 2 pending
Ah yes, I need to get menu items included in the meals-by-month listing. Thanks RSpec!

To specify how the Haml template will present the CouchDB menu items, I need to add menu items to the CouchDB data structure in the spec's before(:each):
  before(:each) do
assigns[:meals] = {
'rows' => [
...
{ "value" => [{ "_id" => '2009-05-15',
"date" => '2009-05-15',
"title" => 'Meal 2',
"summary" => 'Meal 2 Summary',
"menu" => %w(foo bar baz),
"_attachments" => {"image2.jpg" => { }}
}]
}
]
}
...
end
Then I specify how this data will be used by the template:
  it "should include the menu items" do
render("/views/meal_by_month.haml")
response.should have_selector(".menu",
:content => "foo, bar, baz")
end
The spec fails because I have yet to instruct the template how to render the data. So I do so:
...
%p= meal['value'][0]['summary']
.menu= meal['value'][0]['menu'].join(', ')
With that, I am down to to one pending spec:
cstrom@jaynestown:~/repos/eee-code$ rake
...
==
View specs
.....*...................................

Pending:

meal_by_month.haml should include recipe titles in the menu items (Not Yet Implemented)
./spec/views/meal_by_month.haml_spec.rb:60

Finished in 0.229601 seconds

41 examples, 0 failures, 1 pending

(commit)

I will pick that up tomorrow and then move my way back out to the Cucumber scenario to pick up the next thing on the checklist.

Thursday, May 21, 2009

Meal Thumbnails

‹prev | My Chain | next›

I'm back inside some view specs today. Specifically, I need to add images to the meals-in-a-month view. The meal JSON includes an _attachments attribute that describes the image, so I add them to the before(:each) block:
        { "value" => [{ "_id"     => '2009-05-14',
"date" => '2009-05-14',
"title" => 'Meal 1',
"summary" => 'Meal 1 Summary',
"_attachments" => {"image1.jpg" => { }}
}]
},
The example then becomes:
  it "should include a thumbnail image of the meal" do
render("/views/meal_by_month.haml")
response.should have_selector("img",
:src => "/images/2009-05-14/image1.jpg",
:width => "200",
:height => "150")
end
Running the specification fails as expected:
cstrom@jaynestown:~/repos/eee-code$ spec ./spec/views/meal_by_month.haml_spec.rb
...F**

Pending:

meal_by_month.haml should include the menu items (Not Yet Implemented)
./spec/views/meal_by_month.haml_spec.rb:51

meal_by_month.haml should include recipe titles in the menu items (Not Yet Implemented)
./spec/views/meal_by_month.haml_spec.rb:53

1)
'meal_by_month.haml should include a thumbnail image of the meal' FAILED
expected following output to contain a <img height='150' width='200' src='/images/2009-05-14/image1.jpg'/> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<h1>Meals from 2009-05</h1>
<div class="meals">
<h2>
<span class="date">2009-05-14</span>
<span class="title">Meal 1</span>
</h2>
<p>Meal 1 Summary</p>
<h2>
<span class="date">2009-05-15</span>
<span class="title">Meal 2</span>
</h2>
<p>Meal 2 Summary</p>
</div>
</body></html>
./spec/views/meal_by_month.haml_spec.rb:45:

Finished in 0.012593 seconds

6 examples, 1 failure, 2 pending
Happily, I already have an image_link helper method for links to CouchDB images. I add it to the meal_by_month.haml template:
%h1= "Meals from #{@year}-#{@month}"

.meals
- @meals["rows"].each do |meal|
= image_link meal['value'][0]
%h2
%span.date= meal['value'][0]['date']
%span.title= meal['value'][0]['title']
%p= meal['value'][0]['summary']
Running the spec, I still get a failure:
1)
'meal_by_month.haml should include a thumbnail image of the meal' FAILED
expected following output to contain a <img height='150' width='200' src='/images/2009-05-14/image1.jpg'/> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<h1>Meals from 2009-05</h1>
<div class="meals">
<img src="/images/2009-05-14/image1.jpg"><h2>
<span class="date">2009-05-14</span>
<span class="title">Meal 1</span>
</h2>
<p>Meal 1 Summary</p>
<img src="/images/2009-05-15/image2.jpg"><h2>
<span class="date">2009-05-15</span>
<span class="title">Meal 2</span>
</h2>
<p>Meal 2 Summary</p>
</div>
</body></html>
./spec/views/meal_by_month.haml_spec.rb:46:
Ah, I do not have width and height attributes in the output. As currently defined, image_link does not accept an attributes hash. It should accept something like:
    it "should include image attributes" do
image_link(@doc, :alt => "foo").
should have_selector("img", :alt => "foo")
end
To make that example pass, I update the helper to accept an optional attributes hash and build attributes thusly:
    def image_link(doc, options={ })
#...
attrs = options.map{|kv| %Q|#{kv.first}="#{kv.last}"|}.join(" ")
%Q|<img #{attrs} src="/images/#{doc['_id']}/#{filename}"/>|
end
Updating the Haml template to supply the width and height attributes gets the example passing.

Wednesday, May 20, 2009

Meal Summaries, By Month

‹prev | My Chain | next›

In order to display a list of meals in a month, we need the meal's title, date, summary, list of menu items, and the image. That is pretty much the entire meal, so we might as well pull the entire thing back in the CouchDB view. Thus, the view becomes:
    "by_month": {
"map": "function (doc) {
if (doc['type'] == 'Meal') {
emit(doc['date'].substring(0, 4) + '-' + doc['date'].substring(5, 7), doc);
}
}",
"reduce": "function(keys, values, rereduce) { return values; }"
},
"count_by_month": {
"map": "function (doc) {
if (doc['type'] == 'Meal') {
emit(doc['date'].substring(0, 4) + '-' + doc['date'].substring(5, 7), 1);

}
}",
"reduce": "function(keys, values, rereduce) { return sum(values); }"
}
With that in place, I need to update the before(:each) example code block for the Haml specs:
  before(:each) do
assigns[:meals] = {
'rows' => [
{ "value" => [{"date" => '2009-05-14', "title" => 'Meal 1'}]},
{ "value" => [{"date" => '2009-05-15', "title" => 'Meal 2'}]},

]
}
assigns[:year] = 2009
assigns[:month] = '05'
assigns[:count_by_year] = [{"key" => "2009-04", "value" => 3},
{"key" => "2009-05", "value" => 3}]
end
The bits in bold were changed to look like CouchDB records. With that passing, I specify that the meals-by-month listing should include the meal date and summary:
  it "should include each meal's date in the title" do
render("/views/meal_by_month.haml")
response.should have_selector("h2", :content => "2009-05-14")
end

it "should include each meal's summary" do
render("/views/meal_by_month.haml")
response.should have_selector("p", :content => "Meal 2 Summary")
end
After padding the before(:each) block with data for both of these, I get the examples passing by modifying the Haml view:
%h1= "Meals from #{@year}-#{@month}"

.meals
- @meals["rows"].each do |meal|
%h2
%span.date= meal['value'][0]['date']
%span.title= meal['value'][0]['title']
%p= meal['value'][0]['summary']
I am also going to want to include the menu items and a meal thumbnail. Both of these are going to be tricky, so I'll defer that for another day. So that I do not forget, I add some reminders in the Haml spec:
  it "should include a thumbnail image of the meal"

it "should include the menu items"

it "should include recipe titles in the menu items"
Before calling it a day (or moving on to those pending specs), I work my way back out to the Cucumber senario. I have done enough work that I should be able to check off one or two more steps. This is also a good time to test the complete stack of Sinatra, Haml, and CouchDB. The entire scenario:
  Scenario: Browsing a meal in a given month

Given a "Even Fried, They Won't Eat It" meal enjoyed in May of 2009
And a "Salad. Mmmm." meal enjoyed in April of 2009
When I view the list of meals prepared in May of 2009
Then I should see the "Even Fried, They Won't Eat It" meal among the meals of this month
And I should not see the "Salad. Mmmm." meal among the meals of this month
And I should not see a link to June of 2009
When I follow the link to the list of meals in April of 2009
Then I should not see the "Even Fried, They Won't Eat It" meal among the meals of this month
And I should see the "Salad. Mmmm." meal among the meals of this month
And I should not see a link to February of 2009
And I should see a link to May of 2009
Already done is viewing the list of meals in May of 2009. Now I can verify that the meal from May of 2009 is included and that the meal from April of 2009 is not by implementing the two steps:
Then /^I should see the "([^\"]*)" meal among the meals of this month$/ do |title|
response.should have_selector("h2", :content => title)
end

Then /^I should not see the "([^\"]*)" meal among the meals of this month$/ do |title|
response.should_not have_selector("h2", :content => title)
end
Thankfully, for once, Cucumber proves that I have gotten this assembled correctly.

Tuesday, May 19, 2009

Deep Inside View By Month

‹prev | My Chain | next›

Continuing work on viewing meals by month, I work my way into the Sinatra application tonight. Specifying how the application will use the meals-per-month CouchDB views:
    describe "GET /meals/YYYY/MM" do
it "should respond OK" do
get "/meals/2009/05"
response.should be_ok
end

it "should ask CouchDB for meal from year YYYY and month MM" do
RestClient.
should_receive(:get).
with(/key=...2009-05/).
and_return('{"rows": [] }')

get "/meals/2009/05"
end

it "should ask CouchDB how many meals from all months" do
RestClient.
should_receive(:get).
with(/meals.+count_by_month/).
and_return('{"rows": [{"key":"2009-04","value":3},
{"key":"2009-05","value":3}]}')

get "/meals/2009/05"
end
end
That's very similar to the meal-by-year specification. Not surprisingly the code that makes these examples pass is also similar to the code that made the meal-by-year examples pass:
get %r{/meals/(\d+)/(\d+)} do |year, month|
url = "#{@@db}/_design/meals/_view/by_year?group=true&key=%22#{year}-#{month}%22"
data = RestClient.get url
@meals = JSON.parse(data)
@year = year

url = "#{@@db}/_design/meals/_view/count_by_month?group=true"
data = RestClient.get url
@count_by_year = JSON.parse(data)['rows']

haml :meal_by_month
end
While I'm at it, I also add the meals by_month and count_by_month CouchDB views:
{
"views": {
"by_month": {
"map": "function (doc) {
if (doc['type'] == 'Meal') {
emit(doc['date'].substring(0, 7), [doc['_id'], doc['title']]);
}
}",
"reduce": "function(keys, values, rereduce) { return values; }"
},
"count_by_month": {
"map": "function (doc) {
if (doc['type'] == 'Meal') {
emit(doc['date'].substring(0, 7), 1);
}
}",
"reduce": "function(keys, values, rereduce) { return sum(values); }"
}
},
"language": "javascript"
}
They are completely based on the by-year views. I will certainly have to add something to them, but I will defer the addition until I really need it.

With that, it is time to start fleshing out the Haml view. For meals-by-month, we display the title, the summary and a thumbnail image. The example that describes the title (with the setup):
describe "meal_by_month.haml" do
before(:each) do
assigns[:meals] = {
'rows' => [
{ "value" => [['2009-05-14', 'Meal 1']]},
{ "value" => [['2009-05-15', 'Meal 2']]},
]
}
assigns[:year] = 2009
assigns[:month] = '05'
assigns[:count_by_year] = [{"key" => "2009-04", "value" => 3},
{"key" => "2009-05", "value" => 3}]

end

it "should include each meal's title" do
render("/views/meal_by_month.haml")
response.should have_selector("h2", :content => "Meal 1")
end
end
To make that example pass, I need a simple iterator over the meals in the result set (along with some semantic markup):
%h1= "Meals from #{@year}-#{@month}"

.meals
- @meals["rows"].each do |meal|
%h2= meal['value'][0][1]
That will suffice as a stopping point for tonight. I will pick up with the remaining view specs tomorrow.

Monday, May 18, 2009

Outside Meals in a Month

‹prev | My Chain | next›

With browsing meals by year done, I get started on browsing them by month tonight. The Cucumber scenario:
  Scenario: Browsing a meal in a given month

Given a "Even Fried, They Won't Eat It" meal enjoyed in May of 2009
And a "Salad. Mmmm." meal enjoyed in April of 2009
When I view the list of meals prepared in May of 2009
Then I should see the "Even Fried, They Won't Eat It" meal among the meals of this month
And I should not see the "Salad. Mmmm." meal among the meals of this month
And I should not see a link to June of 2009
When I follow the link to the list of meals in April of 2009
Then I should not see the "Even Fried, They Won't Eat It" meal among the meals of this month
And I should see the "Salad. Mmmm." meal among the meals of this month
And I should not see a link to February of 2009
And I should see a link to May of 2009
I can implement the first two step of this scenario with a variation of the meal creation step from the first meal scenario:
Given /^a "([^\"]*)" meal enjoyed in (.+)$/ do |title, date_str|
date = Date.parse(date_str)

permalink = "id-#{date.to_s}"

meal = {
:title => title,
:date => date.to_s,
:serves => 4,
:summary => "meal summary",
:description => "meal description",
:type => "Meal"
}

RestClient.put "#{@@db}/#{permalink}",
meal.to_json,
:content_type => 'application/json'
end
Past experience has taught me that, for the "visit" step, that I should both visit the link and verify that it the responds OK:
When /^I view the list of meals prepared in May of 2009$/ do
visit("/meals/2009/05")
response.status.should == 200
end
I like checking the status as a way of verifying this step. It should fail now (because the action being requested does not exist) and it should not fail in the future. It should fail now, but it doesn't:



Ah, it is passing because it is using the year action (matching the RegExp):
get %r{/meals/(\d+)} do |year|
...
end
To get this passing for real, I add a more specific Sinatra action:
get %r{/meals/(\d+)/(\d+)} do |year, month|
end
That's a good stopping point for tonight. I'll work my way inside to implement functionality tomorrow.

Sunday, May 17, 2009

Another Link in the Chain, Another Bug Caught by Cucumber

‹prev | My Chain | next›

I left off yesterday having gotten links to previous and next years' meals working. Next up is to work my way out from the detailed code back to the Cucumber scenario. If I have done my job well, the scenario will pass without changes...
cstrom@jaynestown:~/repos/eee-code$ cucumber -n features/browse_meals.feature \
> -s "Browsing a meal in a given year"
Feature: Browse Meals

So that I can find meals made on special occasions
As a web user
I want to browse meals by date

Scenario: Browsing a meal in a given year
Given a "Even Fried, They Won't Eat It" meal enjoyed in 2009
And a "Salad. Mmmm." meal enjoyed in 2008
When I view the list of meals prepared in 2009
Then the "Even Fried, They Won't Eat It" meal should be included in the list
expected following output to contain a <li a>Even Fried, They Won't Eat It</li a> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<h1>Meals from 2009</h1>
<div class="navigation">

|

</div>
<ul></ul>
</body></html>
(Spec::Expectations::ExpectationNotMetError)
features/browse_meals.feature:12:in `Then the "Even Fried, They Won't Eat It" meal should be included in the list'
And the "Salad. Mmmm." meal should not be included in the list
When I follow the link to the list of meals in 2008
Then the "Even Fried, They Won't Eat It" meal should not be included in the list
And the "Salad. Mmmm." meal should be included in the list

1 scenario
1 failed step
4 skipped steps
3 passed steps
Ah, nuts.

Ooh, I forgot to add the type to the meal in the Given step. The CouchDB meals views are predicated on this attribute, so adding it ought to resolve the trouble (in bold):
Given /^a "([^\"]*)" meal enjoyed in (\d+)$/ do |title, year|
date = Date.new(year.to_i, 5, 13)

permalink = "id-#{date.to_s}"

meal = {
:title => title,
:date => date.to_s,
:serves => 4,
:summary => "meal summary",
:description => "meal description",
:type => "Meal"
}

RestClient.put "#{@@db}/#{permalink}",
meal.to_json,
:content_type => 'application/json'
end
Well, that fixes the above error, but the 2008 link is still not showing:
cstrom@jaynestown:~/repos/eee-code$ cucumber -n features/browse_meals.feature \
-s "Browsing a meal in a given year"
Feature: Browse Meals

So that I can find meals made on special occasions
As a web user
I want to browse meals by date

Scenario: Browsing a meal in a given year
Given a "Even Fried, They Won't Eat It" meal enjoyed in 2009
And a "Salad. Mmmm." meal enjoyed in 2008
When I view the list of meals prepared in 2009
Then the "Even Fried, They Won't Eat It" meal should be included in the list
And the "Salad. Mmmm." meal should not be included in the list
When I follow the link to the list of meals in 2008
Could not find link with text or title or id "2008" (Webrat::NotFoundError)
features/browse_meals.feature:14:in `When I follow the link to the list of meals in 2008'
Then the "Even Fried, They Won't Eat It" meal should not be included in the list
And the "Salad. Mmmm." meal should be included in the list

1 scenario
1 failed step
2 skipped steps
5 passed steps
After some quick print $stderr debugging (it's how real programmers debug), I find that the Sinatra app was not parsing the JSON returned from the CouchDB view:
get %r{/meals/(\d+)} do |year|
url = "#{@@db}/_design/meals/_view/by_year?group=true&key=%22#{year}%22"
data = RestClient.get url
@meals = JSON.parse(data)
@year = year

url = "#{@@db}/_design/meals/_view/count_by_year?group=true"
data = RestClient.get url
@count_by_year = data['rows']

haml :meal_by_year
end
Ah geez, I hate having to to spec JSON parsing (twice). Then again, if I had not skipped that in the first place, I would not have hit a problem here. I also could have avoided this by using CouchRest to manage the JSON <=> Ruby serialization. If I mess up like this again, I will have to switch. I may end up switching anyway.

I choose not to spec the JSON parsing. I would not be testing behavior, just implementation. Besides, I would have to jump through the same kind of hoops that I had to the other night for RestClient calls. The updated implementation:
  url = "#{@@db}/_design/meals/_view/count_by_year?group=true"
data = RestClient.get url
@count_by_year = JSON.parse(data)['rows']
With that, the scenario fails in exactly the same way!
cstrom@jaynestown:~/repos/eee-code$ cucumber -n features/browse_meals.feature \
-s "Browsing a meal in a given year"
Feature: Browse Meals

So that I can find meals made on special occasions
As a web user
I want to browse meals by date

Scenario: Browsing a meal in a given year
Given a "Even Fried, They Won't Eat It" meal enjoyed in 2009
And a "Salad. Mmmm." meal enjoyed in 2008
When I view the list of meals prepared in 2009
Then the "Even Fried, They Won't Eat It" meal should be included in the list
And the "Salad. Mmmm." meal should not be included in the list
When I follow the link to the list of meals in 2008
Could not find link with text or title or id "2008" (Webrat::NotFoundError)
features/browse_meals.feature:14:in `When I follow the link to the list of meals in 2008'
Then the "Even Fried, They Won't Eat It" meal should not be included in the list
And the "Salad. Mmmm." meal should be included in the list

1 scenario
1 failed step
2 skipped steps
5 passed steps
How badly did I mess up last night?

This failure is caused by my implementation of the link to the next year's meals:
    def link_to_next_year(current, couch_view)
next_result = couch_view.detect do |result|
result['key'].to_i > current.to_i
end
if next_result
%Q|<a href="/meals/#{next_result['key']}">#{next_result['key']}</a>|
else
""
end
end
Somehow, I thought this would work both for the next and previous years—the latter by reversing the list of years. That does not work because, even with the years reversed when sending them to this helper, I am still asking for a year that is greater than the current year. I was so badly confused that I am not even sure how much sense that makes. What I expected to happen was this:
describe "link_to_next_year" do
before(:each) do
@count_by_year = [{"key" => "2008", "value" => 3},
{"key" => "2009", "value" => 3}]
end
it "should link to the previous before the current one" do
link_to_next_year(2009, @count_by_year.reverse).
should have_selector("a",
:href => "/meals/2008")
end
But that fails with no output.

To get this working I need to tell link_to_next_year to link to the previous year as needed. First off, I will rename the function to link_to_year_in_set. The default should be the subsequent year, but and option should link to the previous year.

I re-work the implementation such that the :previous option drives the detection Proc used as well as the reversal of the set:
    def link_to_year_in_set(current, couch_view, options={})
compare_years = options[:previous] ?
Proc.new { |year, current_year| year < current_year} :
Proc.new { |year, current_year| year > current_year}

next_result = couch_view.
send(options[:previous] ? :reverse : :map).
detect{|result| compare_years[result['key'].to_i, current.to_i]}

if next_result
%Q|<a href="/meals/#{next_result['key']}">#{next_result['key']}</a>|
else
""
end
end
With that fixed (and all of the code updated to use the new helper), I give the Cucumber scenario one last try and find it working as expected:
cstrom@jaynestown:~/repos/eee-code$ cucumber -n features/browse_meals.feature \
> -s "Browsing a meal in a given year"
Feature: Browse Meals

So that I can find meals made on special occasions
As a web user
I want to browse meals by date

Scenario: Browsing a meal in a given year
Given a "Even Fried, They Won't Eat It" meal enjoyed in 2009
And a "Salad. Mmmm." meal enjoyed in 2008
When I view the list of meals prepared in 2009
Then the "Even Fried, They Won't Eat It" meal should be included in the list
And the "Salad. Mmmm." meal should not be included in the list
When I follow the link to the list of meals in 2008
Then the "Even Fried, They Won't Eat It" meal should not be included in the list
And the "Salad. Mmmm." meal should be included in the list

1 scenario
8 passed steps
The lesson to learn from tonight is that Cucumber scenarios can really save you—even when you think you are implementing something relatively simple.
(commit)

Saturday, May 16, 2009

Between Years

‹prev | My Chain | next›

Up next in the browsing meals by year scenario is navigating between years:



Nice! That step is easy to implement thanks to webrat:
When /^I follow the link to the list of meals in 2008$/ do
click_link "2008"
end
That fails, of course, because I have yet to add links to previous / next years. So let's do that (and account for boundary conditions while we're at it). Into the code...

When the user is looking at 2008 meals, the application needs a count of the meals in 2007 and 2009 to whether or not to link to them. I could load in all the meals from both years using the same view that lists all meals in a given year and then do a count on the results or I can just ask CouchDB to do the count itself:
    "count_by_year": {
"map": "function (doc) {
if (doc['type'] == 'Meal') {
emit(doc['date'].substring(0, 4), 1);
}
}",
"reduce": "function(keys, values, rereduce) { return sum(values); }"
}
Describing how the /meals/YYYY action will use this view:
      it "should ask CouchDB how many meals by year" do
RestClient.
should_receive(:get).
with(/meals.+count_by_year/).
and_return('{"rows": [] }')

get "/meals/2009"
end
To use the results of the view to display links to the next / previous years' worth of meals, I will use a helper. Getting my BDD on:
describe "link_to_next_year" do
before(:each) do
@count_by_year = [{"key" => "2008", "value" => 3},
{"key" => "2009", "value" => 3}]
end
it "should link to the next year after the current one" do
link_to_next_year(@current_year, 2008).
should have_selector("a",
:href => "/meals/2009")
end
end
I can get this example passing by detecting the next year after the current one:
    def link_to_next_year(current, couch_view)
next_result = couch_view.detect do |result|
result['key'].to_i > current
end
%Q|<a href="/meals/#{next_result['key']}">#{next_result['key']}</a>|
end
Accounting for boundary conditions, when there are no more results, no link should be rendered:
  it "should return empty if there are no more years" do
link_to_next_year(2009, @count_by_year).
should == ""
end
This example can be made to pass by adding a conditional to the earlier definition of link_to_next_year:
    def link_to_next_year(current, couch_view)
next_result = couch_view.detect do |result|
result['key'].to_i > current.to_i
end
if next_result
%Q|<a href="/meals/#{next_result['key']}">#{next_result['key']}</a>|
else
""
end
end
That is a good stopping point for tonight. I will work my way back out to the feature tomorrow.

Friday, May 15, 2009

A View and a View

‹prev | My Chain | next›

The first view that I need to create renders the list of meals for a given year. We tend to have a lot of meals in a year, so this list is a simple unordered list.

In the view specification, before(:each) example, two meals suffice to create a list:
  before(:each) do
assigns[:meals] = @meal =
[
{
'title' => 'Meal 1',
'_id' => '2009-05-14'
},
{
'title' => 'Meal 2',
'_id' => '2009-05-14'
}
]
end
The two examples that describe the list are:
  it "should display a list of meals" do
render("/views/meal_by_year.haml")
response.should have_selector("ul li", :count => 2)
end

it "should link to meals" do
render("/views/meal_by_year.haml")
response.should have_selector("li a", :content => "Meal 1")
end
The Haml code that makes these examples pass is:
%ul
- @meal.each do |meal|
%li
%a{:href => "/meals/#{meal['_id']}"}= meal['title']
One view down...
(commit)

The other view that I need to create is a corresponding CouchDB view. I need to map each document to the year in which it was prepared, pointing to the meal ID and title (enough information to hyperlink to the meal). Additionally, a simple reduction pointing to the meal ID and title will allow me to group recipes by the year. The view that I want is:
{
"views": {
"by_year": {
"map": "function (doc) { emit(doc['date'].substring(0, 4), [doc['_id'], doc['title']]); }",
"reduce": "function(keys, values, rereduce) { return values; }"
}
},
"language": "javascript"
}
The date is an ISO 8601 string (YYYY first), which is the reason for the substring(0,4) call on it.

With that in place, and some non-trivial reworking of the views to match the actual data structure from the map-reduce rather than what I had guessed it would be, I can work my way back out to the Cucumber scenario to define how the next two steps behave—on the 2009 meals page, the 2009 recipe should be included, but not the 2008 recipe:
Then /^the "([^\"]*)" meal should be included in the list$/ do |title|
response.should have_selector("li a", :content => title)
end

Then /^the "([^\"]*)" meal should not be included in the list$/ do |title|
response.should_not have_selector("a", :content => title)
end
All that is left is to verify that clicking through to the 2008 meals works:



(commit)

Tomorrow.