Friday, July 24, 2009

A Mediocre Solution for Finding Adjacent CouchDB View Records

‹prev | My Chain | next›

I have one remaining open issue in my GitHub tracker: links between recipes are missing. I made a go at fixing the problem last night, but had to back out when the fix was growing too large.

Part of the problem is that the helper method responsible for linking to the next record in a CouchDB view has grown long and complicated:
    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] ? { |date_fragment, current| date_fragment < current} : { |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
if next_result
next_uri = next_result['id'].gsub(/-/, '/')
link_text = block_given? ?
yield(next_result['id'], next_result['value']) :

%Q|<a href="/meals/#{next_uri}">#{link_text}</a>|
The most obvious obstacle in adapting this helper for use with recipes is the line that produces the <a> tag—the "/meals" URI-space is hard coded in there. But how to drive the URI-space in that method?

The quickest means of accomplishing this is to add a urispace argument to the link_to_adjacent_view_date helper. That is not an acceptable option in my mind because link_to_adjacent_view_date already has an explicit arity of 3. I barely remember what the three arguments do—adding a fourth is going to make it impossible. Hiding it in the options hash argument is disingenuous, at best.

I called it an explicit arity of 3 earlier because this helper takes a fourth block argument. It is the responsibility of the block to build the link text. Given that the actual arity is 4, adding yet another argument is right out. But I ought to be able to increase the responsibility of the block—instead of building the link text, it can build the entire link.

In other words, given a CouchDB view with results from April and May of 2009:
    before(:each) do
@by_month = [{"key" => "2009-04", "value" => "foo"},
{"key" => "2009-05", "value" => "bar"}]
Then, when working with the record adjacent to April 2009, I should be able to build a link to the value from May, "bar" in this case:
    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 |rec|
%Q|<a href="/foo">#{rec}</a>|
should have_selector("a",
:href => "/foo",
:content => "bar")
I can make that pass with a somewhat simpler output conditional:
      # If a next record was found, then return link text
if next_result
if block_given?
yield next_result['value']
next_uri = next_result['key'].gsub(/-/, '/')
%Q|<a href="/meals/#{next_uri}">#{next_result['key']}</a>|
If a block if given, the result of the conditional is the entire block itself. If no block is given, then a default, meal-centric (for backward compatibility) string results.

The specs all pass for the helper. As expected, the specs for the meal view fail (since it is not longer generating a link to the text. After correcting that, I drive intra-recipe links. The specs are largely based on the intra-meal links, making the process relatively easy. And, like that, intra-recipe links are working:

I close the issue with that fix, but I am not happy with the ultimate solution. I think that Rails concepts and habits have had a bad influence on me in this case. Specifically, I am treating helpers like Rails's view helpers and RestClient calls to CouchDB as model interactions. Neither is the case. In simple Sinatra apps, helpers can help views or controllers and RestClient is pulling back Hash objects, not MVC models.

Ultimately, I think a cleaner solution would have been to have the link_to_adjacent_view_date helper request the appropriate CouchDB view directly, using limit=1 to pull back the single next record. Since I have this working, I leave that to another day with a TODO note.

No comments:

Post a Comment