Friday, August 7, 2009

Dumping CouchDB Documents with couch_design_docs

‹prev | My Chain | next›

I had planned setting up my VPS tonight, but I forgot the name of the provider that I was going to use (yup, getting old). While struggling to remember, I got to thinking about what I would do once I set up the server. After setting up the CouchDB server, I would definitely want to play with some real data. But how to get that real data?

I do not want to deal with the hassle of binary compatible versions of CouchDB (I think I am still using an old trunk version of CouchDB locally). I would also prefer not to have to do my legacy app dump—it is quite slow locally. Perhaps couch_design_docs can help?

Let's see... If it can solve this problem, that gem ought to be able to iterate over each document in my local database and dump them to the file system. Describing that iteration, in RSpec format:
    it "should be able to load each document" do
Store.stub!(:get).
with("uri/_all_docs").
and_return({ "total_rows" => 2,
"offset" => 0,
"rows" => [{"id"=>"1", "value"=>{}, "key"=>"1"},
{"id"=>"2", "value"=>{}, "key"=>"2"}]})

Store.stub!(:get).with("uri/1")
Store.should_receive(:get).with("uri/2")

@it.each { }
end
The first stubbed method handles pulling back all documents in the database. The second stub handles the get of the first record. The actual expectation in here is that iterating over each document in the store should retrieve the second record. Yah, I could have used two expectations ("should_receives"), but I really prefer on expectation per example.

An any rate, to get that example to pass:
    def each
Store.get("#{url}/_all_docs")['rows'].each do |rec|
yield Store.get("#{url}/#{rec['id']}")
end
end
If I have an each method, I might as well mixin Enumerable to get access to sweet methods like reject, select, all?, etc.:
module CouchDesignDocs
class Store
include Enumerable
#...
end
end
As I iterate over each document, I will need to store each on the file system. This seems to be a reasonable responsibility of the DocumentDirectory class. An example of this, in RSpec:
    it "should be able to save a document as JSON" do
file = mock("File", :close => true)
File.stub!(:new).and_return(file)

file.should_receive(:write).with(%Q|{"_id":"foo"}|)

@it.store_document({'_id' => 'foo'})
end
To make this example pass:
module CouchDesignDocs
class DocumentDirectory

attr_accessor :couch_doc_dir
#...
def store_document(doc)
file = File.new("#{couch_doc_dir}/#{doc['_id']}.json", "w+")
file.write(doc.to_json)
file.close
end
end
end
Putting the CouchDB store and the directory store together, I need to create an instance of each, iterate over the documents in the store, and expect to store the documents in the directory. In RSpec format:
  it "should be able to store all CouchDB documents on the filesystem" do
store = mock("Store")
store.stub!(:each).and_yield({'_id' => 'foo'})
Store.stub!(:new).and_return(store)

dir = mock("Document Directory")
DocumentDirectory.stub!(:new).and_return(dir)

dir.
should_receive(:store_document).
with({'_id' => 'foo'})

CouchDesignDocs.dump("uri", "fixtures")
end
To make that example pass, I add the following class method to CouchDesignDocs (I really need to change the name at this point):
  # Dump all documents located at <tt>db_uri</tt> into the directory
# <tt>dir>/tt>
#
def self.dump(db_uri, dir)
store = Store.new(db_uri)
dir = DocumentDirectory.new(dir)
store.each do |doc|
dir.store_document(doc)
end
end
For good measure, I reject design documents (good thing I mixed in Enumerable):
    it "should be able to store all CouchDB documents on the filesystem" do
@store.stub!(:map).and_yield([{'_id' => '_design/foo'}])
@dir.
should_not_receive(:store_document)

CouchDesignDocs.dump("uri", "fixtures")
end
This example passes with this code:
  def self.dump(db_uri, dir)
store = Store.new(db_uri)
dir = DocumentDirectory.new(dir)
store.
map.
reject { |doc| doc['_id'] =~ /^_design/ }.
each { |doc| dir.store_document(doc) }
end
I am not entirely thrilled with the added map in there. It adds no functionality and only serves to supply the reject with something that it can, uh, reject. It is effectively code solely to support the test, which is just icky. Still, I will not push the issue here—the code is functional and the compiler should optimize the map away.

I install my gem locally and add a rake task to my application:
DB = "http://localhost:5984/eee"
require 'restclient'

namespace :couchdb do
desc "Dump seed data from the database"
task :dump_docs do
CouchDesignDocs.dump(DB, "couch/seed")
end
end
Running that (with timing information because I am curious), I find:
cstrom@jaynestown:~/repos/eee-code$ time rake couchdb:dump_docs
(in /home/cstrom/repos/eee-code)

real 0m4.886s
user 0m2.404s
sys 0m0.440s
That is not bad—less than 5 seconds to dump 1000+ documents.

Examining the filesystem, I see that the documents do, indeed, exist and that they contain JSON:



I do, however, note that I am missing the attachments (need to append ?attachments=true to my RestClient request). I may want to strip the CouchDB revision information from the dumped documents. I definitely want to test uploading the seed data. It may be time to rename the couch_design_docs gem since it does much more than design documents at this point.

These are all things I can do tomorrow.

Thursday, August 6, 2009

Refactoring: A Payoff

‹prev | My Chain | next›

Today, I continue trying to resolve my too-many-page-links problem:



Yesterday, I refactored the pagination helper to put me in a better position for this work, ending up in this form:
    def pagination(query, results)
total = results['total_rows']
limit = results['limit']
skip = results['skip']

last_page = (total + limit - 1) / limit
current_page = skip / limit + 1

link = base_pagination_link(query, results)

links = []
links << edge_page_link(current_page == 1, link, current_page-1, "« Previous")

links << page_link(link, 1) if current_page != 1
# Window prior to the current page
links << (2...current_page).map { |p| page_link(link, p) }

links << %Q|<span class="current">#{current_page}</span>|

# Window after to the current page
links << (current_page+1...last_page).map { |p| page_link(link, p) }

links << page_link(link, last_page) if current_page != last_page

links << edge_page_link(current_page == last_page, link, current_page+1, "Next »")

%Q|<div class="pagination">#{links.join}</div>|
end
The two windows around the current page are indicated in bold above. At this point, the windows run all the way from the first page up to the current page (or from the current page up to the last). In other words, every single page (all 42 if there are 42 pages) are being displayed. Functionally, that is exactly where I started yesterday, but I can work with this form to make limited windows around the current page.

An RSpec example of what I need:
  context "in the middle (page 21) of a large result sets (42 pages)" do
before(:each) do
@results['skip'] = 400
@results['total_rows'] = 841
end
it "should have a link to page 1" do
pagination(@query, @results).
should have_selector("a", :content => "1")
end
it "should not have a link to page 2" do
pagination(@query, @results).
should_not have_selector("a", :href => "/recipes/search?q=foo&page=2")
end
end
After verifying that the example fails, I get it to pass by defining a start window:
    def pagination(query, results)
#...
start_window = 3
links << (start_window...current_page).map { |p| page_link(link, p) }


links << %Q|<span class="current">#{current_page}</span>|

links << (current_page+1...last_page).map { |p| page_link(link, p) }
#...
end
It passes, but starting the pagination links at 3 instead of 2 is not much of a window. To drive the window, another two examples:
    it "should not have a link to page 17" do
pagination(@query, @results).
should_not have_selector("a", :href => "/recipes/search?q=foo&page=17")
end
it "should have a link to page 18" do
pagination(@query, @results).
should have_selector("a", :href => "/recipes/search?q=foo&page=18")
end
I use two examples to accurately describe the boundary condition that I am probing here. Besides, I am not adding two failing examples—the second example passes in my current window-starting-at-page-3 state as it should pass once I have a better window.

To get those two examples to pass, I need to modify the calculation of the start_window local variable:
    def pagination(query, results)
#...
start_window = current_page - 3
links << (start_window...current_page).map { |p| page_link(link, p) }

links << %Q|<span class="current">#{current_page}</span>|

links << (current_page+1...last_page).map { |p| page_link(link, p) }
#...
end
I use similar examples to drive the implementation of the end of the window, ending up with this:
    def pagination(query, results)
#...
start_window = current_page - 3
links << (start_window...current_page).map { |p| page_link(link, p) }

links << %Q|<span class="current">#{current_page}</span>|

end_window = current_page + 3
links << (current_page+1..end_window).map { |p| page_link(link, p) }
#...
end
That works just fine for a large result set, such as the one used for these examples. If the current page is 2, then the start_window is going to be -1. That might cause problems. In fact, it breaks some existing examples:
==
Helper specs
............................F......................................

1)
'pagination should have only 2 pages, when results.size == 2 * page size' FAILED
expected following output to omit a <a>3</a>:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><div class="pagination">
<span class="inactive">« Previous</span><a href="/recipes/search?q=foo&page=-2">-2</a><a href="/recipes/search?q=foo&page=-1">-1</a><a href="/recipes/search?q=foo&page=0">0</a><span class="current">1</span><a href="/recipes/search?q=foo&page=2">2</a><a href="/recipes/search?q=foo&page=3">3</a><a href="/recipes/search?q=foo&page=4">4</a><a href="/recipes/search?q=foo&page=2">2</a><a href="/recipes/search?q=foo&page=2">Next »</a>
</div></body></html>
./spec/eee_helpers_spec.rb:214:
Rather than fixing that failing spec, I create new examples specifically describing the edge cases of being near the first page or near the last page:
  context "at the beginning (page 2) of a large result sets (42 pages)" do
before(:each) do
@results['skip'] = 20
@results['total_rows'] = 841
end
it "should not have a link to page 0" do
pagination(@query, @results).
should_not have_selector("a", :href => "/recipes/search?q=foo&page=0")
end
end
That example fails because the start window currently includes a page zero(!). To get rid of it, I add a ternary to the start_window calculation:
    def pagination(query, results)
#...
start_window = current_page > 4 ? current_page - 3 : 2
links << (start_window...current_page).map { |p| page_link(link, p) }

links << %Q|<span class="current">#{current_page}</span>|

end_window = current_page + 3
links << (current_page+1..end_window).map { |p| page_link(link, p) }
#...
end
After adding something similar for the end_window, I have all of my examples (and Cucumber scenarios) passing and my pagination looking sane:



(commit)

Nice! It may be time to set up a VPS tomorrow!

Wednesday, August 5, 2009

Refactoring: An Anticipation

‹prev | My Chain | next›

After getting an all-recipes link working yesterday, I noticed that that pagination was a little... disturbing:



Unfortunately, this means refactoring because the pagination helper is already longish:
    def pagination(query, results)
total = results['total_rows']
limit = results['limit']
skip = results['skip']

last_page = (total + limit - 1) / limit
current_page = skip / limit + 1

link = "/recipes/search?q=#{query}"

if results['sort_order']
link += "&sort=#{results['sort_order'].first['field']}"
if results['sort_order'].first['reverse']
link += "&order=desc"
end
end

links = []

links <<
if current_page == 1
%Q|<span class="inactive">« Previous</span>|
else
%Q|<a href="#{link}&page=#{current_page - 1}">&laquo; Previous</a>|
end

links << (1..last_page).map do |page|
if page == current_page
%Q|<span class="current">#{page}</span>|
else
%Q|<a href="#{link}&page=#{page}">#{page}</a>|
end
end

links <<
if current_page == last_page
%Q|<span class="inactive">Next »</span>|
else
%Q|<a href="#{link}&page=#{current_page + 1}">Next &raquo;</a>|
end

%Q|<div class="pagination">#{links.join}</div>|
end
First up, I DRY up this code. The links to various pages are all done the same way:
    def page_link(link, page, text=nil)
%Q|<a href="#{link}&page=#{page}">#{text || page}</a>|
end
The edge cases are also similar. If we are at an edge (the first or last page), then the previous / next links need to be disabled. This can be extracted out into and edge_page_link helper:
    def edge_page_link(disabled, link, page, text)
disabled ?
%Q|<span class="inactive">#{text}</span>| :
page_link(link, page, text)
end
Using these methods, the original helper method gets smaller:
    def pagination(query, results)
total = results['total_rows']
limit = results['limit']
skip = results['skip']

last_page = (total + limit - 1) / limit
current_page = skip / limit + 1

link = "/recipes/search?q=#{query}"

if results['sort_order']
link += "&sort=#{results['sort_order'].first['field']}"
if results['sort_order'].first['reverse']
link += "&order=desc"
end
end

links = []

links << edge_page_link(current_page == 1, link, current_page-1, "&laquo; Previous")

links << (1..last_page).map do |page|
if page == current_page
%Q|<span class="current">#{page}</span>|
else
page_link(link, page)
end
end

links << edge_page_link(current_page == last_page, link, current_page+1, "Next &raquo;")

%Q|<div class="pagination">#{links.join}</div>|
end
I run my specs to make sure that nothing has changed—this is critical to do whenever doing any refactoring lest you find an early change broke your examples and all subsequent work was built on a poor foundation.

I also need to refactor that map with a conditional inside it. The map builds the links to individual pages, with the conditional responsible for marking the current page. An equivalent way of accomplishing this, one that will put me in a better position moving forward, is to build all the links up to the current page, build the current page marker, and then build all of the links after the current page:
      links << (1...current_page).map { |p| page_link(link, p) }
links << %Q|<span class="current">#{current_page}</span>|
links << (current_page+1..last_page).map { |p| page_link(link, p) }
That is much nicer—no conditionals. As an aside, I am using two different range operators—the two dot (include the last value) and the three dot form (exclude the last value).

The last thing that I will do tonight is create a first and last page link. Unless the user is on the first page, there should always be a link to the first page. If the user is on page 26, there is not much need to present a link to page 2, but there should definitely be a way to get to the start of the list. Similarly, if the user is not on the last page, there should always be a way to get there. So I break out the first and last pages:
      links << page_link(link, 1) if current_page != 1

links << (2...current_page).map { |p| page_link(link, p) }

links << %Q|<span class="current">#{current_page}</span>|

links << (current_page+1...last_page).map { |p| page_link(link, p) }

links << page_link(link, last_page) if current_page != last_page
That will serve nicely as a stopping point for tonight. I have changed absolutely no functionality—29 pages still produce 29 pagination links. What I have done is cleaned up the code and put myself in a better position to add a sliding window of links around the current page.

To be clear, this is not an exercise in building good pagination—something along the lines of Rails' old classic pagination probably would have been a better option for me here. But this solution will do, and it is nice to exercise my refactoring chops from time-to-time.

Tuesday, August 4, 2009

Match Everything

‹prev | My Chain | next›

There are two related problems to note when searching for an empty string:



The first is that the recipes link (in the categories at the top of the page) is not a link. It should link to all recipes. The second problem is that a search for an empty string ought to return all results.

The first problem is quite easy—a link to /recipes/search?q= works nicely.

The second problem is a little more difficult to solve. The basic strategy is that, given an empty search string, to search for something that matches all documents instead. For my recipes, searching for a couchdb-lucene string of type:Recipe will match, er... all recipes.

Or, in RSpec format:
    it "should search for all doc of type recipe when given an empty string" do
RestClient.should_receive(:get).
with(/q=type:Recipe/).
and_return('{"total_rows":1,"skip":0,"limit":20,"rows":[]}')

get "/recipes/search?q="
end
I define a constant for the default search and use it when the query is empty:
DEFAULT_QUERY = "type:Recipe"
...
get '/recipes/search' do
@query = params[:q] == '' ? DEFAULT_QUERY : params[:q]
...
If returning all recipes, then a default sort order is a must. Sorting by date makes the most sense:
    it "should sort by date when given an empty string" do
RestClient.should_receive(:get).
with(/sort_date/).
and_return('{"total_rows":1,"skip":0,"limit":20,"rows":[]}')

get "/recipes/search?q="
end
Getting this to pass is relatively easy:
get '/recipes/search' do
@query = params[:q] == '' ? DEFAULT_QUERY : params[:q]
@sort = params[:q] == '' ? "%5Csort_date" : params[:sort]
...
Yup, easy enough! Checking in the browser, however, I find:



The search and the sort are working, but the non-user friendly search term, type:Recipe is being displayed in the refine-your-search text field. An example describing that the text field should be empty might look like:
  it "should display an empty search when searching for all recipes" do
assigns[:query] = "type:Recipe"
render("/views/_search_form.haml")
response.should have_selector("input", :name => "q", :value => "")
end
That example fails with the following:
1)
'_search_form.haml should display an empty search when searching for all recipes' FAILED
expected following output to contain a <input value='' name='q'/> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><form action="/recipes/search" id="search-form" method="get">
<input maxlength="2048" name="q" size="31" type="text" value="type:Recipe"><input name="s" type="submit" value="Search">
</form></body></html>
./spec/views/_search_form.haml_spec.rb:15:
As expected the query term is still included in the text field. To ensure that it is not included (and to get the example passing), all that is required is a simple substitution:
  %input{:type      => "text",
:value => @query.to_s.sub(%r{#{DEFAULT_QUERY}}, ''),
:name => "q",
...
After verifying in the browser that an empty refine-your-search field is now shown, I notice something that you tend to only notice when playing with really large datasets:



D'oh! I have to clean up the pagination a bit. Tomorrow.

Monday, August 3, 2009

A Stylin' Homepage

‹prev | My Chain | next›

Tonight, I need to get the homepage into order. Currently it has no CSS and looks like this:



By way of illustration, the legacy site looks like this:



In both cases, I have the content split into three sections which will correspond to a simple three column layout. I denote the right-handed column as "rhc" and the left handed column as "lhc". The main content column gets the more semantically significant name of "meals".

Important note: I do not care at all about cross browser compatibility here. This is a personal site mostly for family and friends (though it has earned a fairly decent following). As such, I exact a little professional vengeance on IE users, making them pay with a slightly less than perfect experience. Yeah, it's childish, but having developed for IE for 10+ years, I feel I've earned the right to be a little childish.

I add much in the way of style to get the colors and the text flow right, but the layout is accomplished with this bit of less CSS:
#homepage {
margin:10px 10px 75px;
padding:0 5px;
position:relative;
width:800px;

.lhc {
left:0;
position:absolute;
top:0;
width:12%;
}
.rhc {
margin:0 0 0 80%;
position:absolute;
right:0;
top:0;
width:30%;
}

.meals {
left:13%;
padding-bottom:10px;
position:relative;
width:56%;
}
}
The homepage <div> that serves as the top-level DOM element, is positioned relative to the normal flow. Doing so ensures that the absolutely positioned "lhc" and "rhc" <div> tags will be absolutely positioned relative to the homepage <div>, rather than the body of the document. Beyond that, it is a simple matter of allocating the appropriate percentages of width to the columns, along with offset margins. Add a little color and:



There are still a couple of tweaks that I need to do before deploying. The "all recipes" link is not working. Also, some of the lesser traveled pages may need some CSS love. Hopefully I can wrap both things up tomorrow.

Sunday, August 2, 2009

Generalizing to Handle Map and Reduce

‹prev | My Chain | next›

I left off work on my chain yesterday having recognized that Cucumber once again identified a bug in my code. The bug can be seen on the list of meals by month:



Specifically, the intra-month links are not the right dates (should be April 2005 and June 2005). Worse yet, they are not even links!

I can trace the trouble back to the last time that I worked on the helper method that links to previous/next date records in a CouchDB view. I claimed a mediocre solution then. It would seem that I have not even achieved that low mark.

The helper, link_to_adjacent_view_date requires a current key and a result set from a CouchDB view. It uses those inputs to determine the next record in the result set, yielding the next record back to the caller so that the caller, which knows the current context, can build the link.

The helper so far, complete with copious documentation and TODO indicative of my struggles with it:
    # TODO: use CouchDB view directly here, with limit=1 to determine
# the next record
def link_to_adjacent_view_date(current, couch_view, options={})
# If looking for the record previous to this one, then we seek a
# date prior to the current one - build a Proc capable of
# finding that
compare = options[:previous] ?
Proc.new { |date_fragment, current| date_fragment < current} :
Proc.new { |date_fragment, current| date_fragment > current}

# If looking for the record previous to this one, then we need
# to reverse the list before using the compare Proc to detect
# the record
next_result = couch_view.
send(options[:previous] ? :reverse : :map).
detect{|result| compare[result['key'], current.to_s]}

# If a next record was found, then return link text - either by
if next_result
if block_given?
yield next_result['value']
else
next_uri = next_result['key'].gsub(/-/, '/')
%Q|<a href="/meals/#{next_uri}">#{next_result['key']}</a>|
end
else
""
end
end
I am not particularly fond of pulling back then entire view result in order to accomplish this. It does work, though. It works for links between recipes, individual meals, and years. It just does not work for links between months.

The ultimate source of trouble is that I am trying to use this helper for both regular CouchDB view and reduced views. One size does not always fit all and this is a good example. It is also a good example of me not providing sufficient context in my specs, as I will show in a moment.

In the link_to_adjacent_view_date helper, things break down when yielding back to the Haml template to build the links. The value that is being yielded is:
...
if block_given?
yield next_result['value']
else
...
That works when the value is something like what can be found in regular (non-reduced) views:
cstrom@jaynestown:~/repos/eee-code$ curl \
http://localhost:5984/eee/_design/meals/_view/by_date_short
{"total_rows":500,"offset":0,"rows":[
//...
{"id":"2005-04-20","key":"2005-04-20","value":{"title":"Tuna Casserole like Grammy's","date":"2005-04-20"}},
{"id":"2005-04-22","key":"2005-04-22","value":{"title":"Hot Lips: The Day After","date":"2005-04-22"}},
{"id":"2005-05-01","key":"2005-05-01","value":{"title":"Ode to the Farmers' Market","date":"2005-05-01"}},
{"id":"2005-05-02","key":"2005-05-02","value":{"title":"May Twoth","date":"2005-05-02"}},
{"id":"2005-05-03","key":"2005-05-03","value":{"title":"Panelles!","date":"2005-05-03"}},
//...
]}
Problems arise when the value contains less information, as with reduced views. The entire point of a reduce is to distill the information in the normal view down to some kind of summary representation, such as a count of the records with a particular key. This is what the count_by_month map-reduce view does:
cstrom@jaynestown:~/repos/eee-code$ curl \
http://localhost:5984/eee/_design/meals/_view/count_by_month?group=true
//...
{"key":"2005-03","value":6},
{"key":"2005-04","value":5},
{"key":"2005-05","value":7},
{"key":"2005-06","value":6},
{"key":"2005-07","value":5},
//...
]}
Trying to build a link to a date with a value of "7" is not going to do much good. I could use the key in this case, but that is not enough information to link to meals and recipes. This is where the one-size fits all approach to maps and reduces breaks down.

I can resolve this by passing both the key and the value to the calling block. That will require some code changes, but mostly why did I miss this in the first place? The specs for the by-month version of the method:
describe "link_to_adjacent_view_date" do
context "couchdb view by_month" do
before(:each) do
@by_month = [{"key" => "2009-04", "value" => "foo"},
{"key" => "2009-05", "value" => "bar"}]
end
...
Gah! That's what I get for testing with data that does not accurately represent the live data.

So I change to more reduce-like data:
    before(:each) do
@by_month = [{"key" => "2009-04", "value" => "1"},
{"key" => "2009-05", "value" => "2"}]
end
And I update one of the block examples so that it can make use of the updated pre-condition:
    it "should link to the CouchDB view's key and value, if block is given" do
link_to_adjacent_view_date("2009-04", @by_month) do |key, value|
%Q|<a href="/foo">#{key}</a>|
end.
should have_selector("a",
:href => "/foo",
:content => "2009-05")
end
Now the example fails like it ought to:
1)
'link_to_adjacent_view_date couchdb view by_month should link to the CouchDB view's key and value, if block is given' FAILED
expected following output to contain a <a href='/foo'>2009-05</a> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><a href="/foo">2</a></body></html>
./spec/eee_helpers_spec.rb:341:

Finished in 0.106256 seconds

60 examples, 1 failure
It fails because the link_to_adjacent_view_date helper is not yielding both the key and value needed in the example. Adding the key makes the example pass:
...
if block_given?
yield next_result['key'], next_result['value']
else
...
Of course that breaks just about every one of my view examples, but they are easy to fix—I just need to update the Haml views to work with two values being yielded by link_to_adjacent_view_date, instead of 1.

With that, I finally have all of my specs passing and all of my Cucumber scenarios:
cstrom@jaynestown:~/repos/eee-code$ cucumber features -i
...
32 scenarios (7 undefined, 25 passed)
272 steps (22 skipped, 24 undefined, 226 passed)

Saturday, August 1, 2009

So Many Yaks

‹prev | My Chain | next›

I enjoyed a nice break these past few days adding features to couch_design_docs, but it is time to get back to work on the main task at hand. Last I left off with my cookbook application, I had but a few pages to style.

Before I dive back into CSS though, it has been a while since last I ran all of my Cucumber scenarios. Hopefully, they all still run....

They do not. In fact I cannot even run single scenarios:
cstrom@jaynestown:~/repos/eee-code$ cucumber features/recipe_search.feature:53 -b
wrong number of arguments (0 for 1) (ArgumentError)
./features/support/../../eee.rb:24:in `before'
./features/support/../../eee.rb:24
/usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
/usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:31:in `polyglot_original_require'
/home/cstrom/.gem/ruby/1.8/gems/polyglot-0.2.5/lib/polyglot.rb:54:in `require'
./features/support/env.rb:5
/usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
/usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:31:in `polyglot_original_require'
/home/cstrom/.gem/ruby/1.8/gems/polyglot-0.2.5/lib/polyglot.rb:54:in `require'
/home/cstrom/.gem/ruby/1.8/gems/cucumber-0.3.92/bin/../lib/cucumber/cli/main.rb:99:in `require_files'
/home/cstrom/.gem/ruby/1.8/gems/cucumber-0.3.92/bin/../lib/cucumber/cli/main.rb:108:in `each_lib'
/home/cstrom/.gem/ruby/1.8/gems/cucumber-0.3.92/bin/../lib/cucumber/cli/main.rb:106:in `each'
/home/cstrom/.gem/ruby/1.8/gems/cucumber-0.3.92/bin/../lib/cucumber/cli/main.rb:106:in `each_lib'
/home/cstrom/.gem/ruby/1.8/gems/cucumber-0.3.92/bin/../lib/cucumber/cli/main.rb:99:in `require_files'
/home/cstrom/.gem/ruby/1.8/gems/cucumber-0.3.92/bin/../lib/cucumber/cli/main.rb:53:in `execute!'
/home/cstrom/.gem/ruby/1.8/gems/cucumber-0.3.92/bin/../lib/cucumber/cli/main.rb:26:in `execute'
/home/cstrom/.gem/ruby/1.8/gems/cucumber-0.3.92/bin/cucumber:9
/home/cstrom/.gem/ruby/1.8/bin/cucumber:19:in `load'
/home/cstrom/.gem/ruby/1.8/bin/cucumber:19
Craaap. It has been a while since I last ran my Cucumber scenarios. Did I install a gem that is now conflicting?

I eventually track this down to a conflict between the before filter in my Sinatra application and a method of the same name in Cucumber::StepMother (great name by the way).

I give it a good try—a good long try—but eventually have to concede defeat for the day. Each change I make seems to get me inches closer when I have miles still to go. Maybe I'll use that work or maybe I'll toss it. Most likely the latter, so I stash it away:
cstrom@jaynestown:~/repos/eee-code$ git stash save \
"Trying to solve before filter conflict with Sinatra module namespace"
Saved working directory and index state "On master: Trying to solve before filter conflict with Sinatra module namespace"
HEAD is now at 660ad44 Whoops! Need to use one directory level up now that couch_design_docs handles more than design docs
For now, I resolve the problem the cheap way—with a conditional around the before block in the Sinatra application:
if ENV['RACK_ENV'] != 'test'
before do
content_type 'text/html', :charset => 'UTF-8'
end
end
Eeew. I do not see my first attempt staying in the stash for long. But, it works. I can now run a single scenario:
cstrom@jaynestown:~/repos/eee-code$ cucumber features/recipe_search.feature:53 -b
Sinatra::Test is deprecated; use Rack::Test instead.
Feature: Search for recipes

So that I can find one recipe among many
As a web user
I want to be able search recipes

Scenario: Paginating results # features/recipe_search.feature:53
Given 50 yummy recipes # features/step_definitions/recipe_search.rb:119
And a 1 second wait to allow the search index to be updated # features/step_definitions/recipe_search.rb:196
When I search for "yummy" # features/step_definitions/recipe_search.rb:200
Then I should see 20 results # features/step_definitions/recipe_search.rb:243
And I should see 3 pages of results # features/step_definitions/recipe_search.rb:247
And I should not be able to go to a previous page # features/step_definitions/recipe_search.rb:251
When I click page 3 # features/step_definitions/recipe_search.rb:217
Then I should see 10 results # features/step_definitions/recipe_search.rb:243
And I should not be able to go to a next page # features/step_definitions/recipe_search.rb:251
When I click the previous page # features/step_definitions/recipe_search.rb:221
Then I should see 20 results # features/step_definitions/recipe_search.rb:243
And I should be able to go to a previous page # features/step_definitions/recipe_search.rb:256
When I click the next page # features/step_definitions/recipe_search.rb:221
Then I should see 10 results # features/step_definitions/recipe_search.rb:243
When I visit page -1 # features/step_definitions/recipe_search.rb:225
Then I should see page 1 # features/step_definitions/recipe_search.rb:261
When I visit page "foo" # features/step_definitions/recipe_search.rb:225
Then I should see page 1 # features/step_definitions/recipe_search.rb:261
When I visit page 4 # features/step_definitions/recipe_search.rb:225
Then I should see page 1 # features/step_definitions/recipe_search.rb:261

1 scenario (1 passed)
20 steps (20 passed)
0m2.333s
Oh, thank heavens.

After ensuring that my RSpec examples still pass with that conditional, I can finally see if all of my Cucumber scenarios are still passing:
cstrom@jaynestown:~/repos/eee-code$ cucumber features
...

Failing Scenarios:
cucumber features/recipe_search.feature:111 # Scenario: Invalid search parameters
cucumber features/recipe_details.feature:15 # Scenario: Viewing a recipe with non-active prep time
cucumber features/browse_meals.feature:18 # Scenario: Browsing a meal in a given month

32 scenarios (3 failed, 7 undefined, 22 passed)
272 steps (3 failed, 33 skipped, 24 undefined, 212 passed)
Aw, nuts!

If nothing else, my earlier before vs. before yak shaving forced me to update all of my gems. I now have a new version of Cucumber installed that makes things a little easier to resolve. The format of the failing scenarios is such that I can copy & past to run the failing scenario in isolation:
cstrom@jaynestown:~/repos/eee-code$ cucumber features/recipe_search.feature:111
Sinatra::Test is deprecated; use Rack::Test instead.
Feature: Search for recipes

So that I can find one recipe among many
As a web user
I want to be able search recipes

Scenario: Invalid search parameters # features/recipe_search.feature:111
Given 5 "Yummy" recipes # features/step_definitions/recipe_search.rb:119
And a 0.5 second wait to allow the search index to be updated # features/step_definitions/recipe_search.rb:196
When I search for "" # features/step_definitions/recipe_search.rb:200
Then I should see no results # features/step_definitions/recipe_search.rb:316
And I should see an empty query string # features/step_definitions/recipe_search.rb:324
expected following output to contain a <input[@name=query][@value='']/> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head>
<title>EEE Cooks</title>
<link href="/stylesheets/style.css" rel="stylesheet" type="text/css">
</head>
<html><body>
<div id="header">
<div id="eee-header-logo">
<a href="/">
<img alt="Home" src="/images/eee_corner.png"></a>
</div>
</div>
<ul id="eee-categories">
<li><a href="/recipes/search?q=category:italian">Italian</a></li>
<li><a href="/recipes/search?q=category:asian">Asian</a></li>
<li><a href="/recipes/search?q=category:latin">Latin</a></li>
<li><a href="/recipes/search?q=category:breakfast">Breakfast</a></li>
<li><a href="/recipes/search?q=category:chicken">Chicken</a></li>
<li><a href="/recipes/search?q=category:fish">Fish</a></li>
<li><a href="/recipes/search?q=category:meat">Meat</a></li>
<li><a href="/recipes/search?q=category:salad">Salad</a></li>
<li><a href="/recipes/search?q=category:vegetarian">Vegetarian</a></li>
<a>Recipes</a>
</ul>
<form action="/recipes/search" method="get">
<input maxlength="2048" name="q" size="31" type="text" value=""><input name="s" type="submit" value="Search">
</form>
<p class="no-results">
No results matched your search. Please refine your search
</p>
<div id="footer"></div>
</body></html>
</html>
(Spec::Expectations::ExpectationNotMetError)
features/recipe_search.feature:117:in `And I should see an empty query string'
When I search for an invalid lucene search term like "title:ingredient:egg" # features/step_definitions/recipe_search.rb:213
Then I should see no results # features/step_definitions/recipe_search.rb:316
And I should see an empty query string # features/step_definitions/recipe_search.rb:324

Failing Scenarios:
cucumber features/recipe_search.feature:111 # Scenario: Invalid search parameters

1 scenario (1 failed)
8 steps (1 failed, 3 skipped, 4 passed)
Ah, the query parameter should be "q", not "query". That is an easy enough fix:
Then /^I should see an empty query string$/ do
response.should have_selector("input[@name=q][@value='']")
end
The next failing Cucumber scenario is similarly easy to get passing. Some added tags require some RegExps where a string had sufficed before. The last failing scenario uncovers an actual bug:
cstrom@jaynestown:~/repos/eee-code$ cucumber features/browse_meals.feature:18
Sinatra::Test is deprecated; use Rack::Test instead.
Feature: Browse Meals

So that I can find meals made on special occasions
As a person interested in exploring meals and how they drive certain recipes
I want to browse meals by date

Scenario: Browsing a meal in a given month # features/browse_meals.feature:18
Given a "Even Fried, They Won't Eat It" meal enjoyed in May 2009 # features/step_definitions/meal_details.rb:1
And a "Salad. Mmmm." meal enjoyed in April 2009 # features/step_definitions/meal_details.rb:1
And a "Almost French Onion Soup" meal enjoyed in September 2003 # features/step_definitions/meal_details.rb:1
When I view the list of meals prepared in May of 2009 # features/step_definitions/meal_details.rb:47
Then I should see the "Even Fried, They Won't Eat It" meal among the meals of this month # features/step_definitions/meal_details.rb:81
And I should not see the "Salad. Mmmm." meal among the meals of this month # features/step_definitions/meal_details.rb:85
And I should not see a link to June 2009 # features/step_definitions/meal_details.rb:97
When I follow the link to the list of meals in April 2009 # features/step_definitions/meal_details.rb:57
Could not find link with text or title or id "April 2009" (Webrat::NotFoundError)
features/browse_meals.feature:27:in `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 # features/step_definitions/meal_details.rb:85
And I should see the "Salad. Mmmm." meal among the meals of this month # features/step_definitions/meal_details.rb:81
And I should see a link to May 2009 # features/step_definitions/meal_details.rb:93
And I should not see a link to February 2009 # features/step_definitions/meal_details.rb:97
When I follow the link to the list of meals in September 2003 # features/step_definitions/meal_details.rb:57
Then I should see the "Almost French Onion Soup" meal among the meals of this month # features/step_definitions/meal_details.rb:81
And I should see a link to April 2009 # features/step_definitions/meal_details.rb:93

Failing Scenarios:
cucumber features/browse_meals.feature:18 # Scenario: Browsing a meal in a given month

1 scenario (1 failed)
15 steps (1 failed, 7 skipped, 7 passed)
Looking at the actual meals-by-month web page (it's nice to be able to do that now), I see that the intra-month links are not actual links and have the wrong date:



That turns out to be another yak. I have had my fill of yaks for the day, so I will start with that one tomorrow.