Tuesday, July 7, 2009

Tail Recursion is My Current Golden Hammer

‹prev | My Chain | next›

Picking up from last night, I am still keen on exploring mapping directory structures into JSON data structures. The ultimate goal is to create a CouchDB design document (which are stored in JSON format).

For example, I would like to take a document in couch/_design/lucene/transform.js to result in a JSON document along the lines of:
{
"lucene": {
"transform": <<contents of transform.js>>
}
}
I devised a poor man's solution last night, but would like something that will work with directories that are deeper than 1 level. I also favor tail recursion because I have been learning Erlang. It may be something of a golden hammer for me, but it's a fun golden hammer. Besides, I am prototyping to learn.

First up, for each .js file, I build an array of the directories, basename of the file and the contents the file. For couch/_design/lucene/transform.js, I end up with something like this:
["lucene", "transform", "<<contents of transform.js>>"]
I can get that with:
  Dir["#{DESIGN_DOC_DIR}/**/*.js"].each do |filename|
path = File.dirname(filename).
split(/\//)
path << File.basename(filename, ".js")

file = File.new(filename)
path << file.read
end
Using a tail-recursive method, I can then build the desired data structure:
  def build_doc(list)
key = list.first
if (list.length > 2)
{ key => build_doc(list[1,list.length]) }
else
{ key => list.last }
end
end
Trying this out in and IRB session, I am getting my desired results:
>>   def build_doc(list)
>> key = list.first
>> if (list.length > 2)
>> { key => build_doc(list[1,list.length]) }
>> else
?> { key => list.last }
>> end
>> end
=> nil
>> build_doc(%w{1 2 3 4 5})
=> {"1"=>{"2"=>{"3"=>{"4"=>"5"}}}}
So I feel safe to give this a try with my existing design docs being added in the Cucumber features/support/env.rb file. I add this to my Before block:
  def build_doc(list)
key = list.first
if (list.length > 2)
{ key => build_doc(list[1,list.length]) }
else
{ key => list.last }
end
end

docs = { }
DESIGN_DOC_DIR = "couch/_design"
Dir["#{DESIGN_DOC_DIR}/**/*.js"].each do |filename|
path = File.dirname(filename).
gsub(/#{DESIGN_DOC_DIR}\//, '').
split(/\//)
path << File.basename(filename, ".js")

file = File.new(filename)
path << file.read

docs.merge!(build_doc(path))
end

docs.each do |document_name, doc|
RestClient.put "#{@@db}/_design/#{document_name}",
doc.to_json,
:content_type => 'application/json'
end
To make sure that everything is still working, I run one of the recipe search Cucumber scenarios (which would fail without a good lucene design document):
cstrom@jaynestown:~/repos/eee-code$ cucumber features -n -s "Matching a word in the recipe summary"
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: Matching a word in the recipe summary
Given a "pancake" recipe with a "Yummy!" summary
And a "french toast" recipe with a "Delicious" summary
And a 0.5 second wait to allow the search index to be updated
When I search for "yummy"
Then I should see the "pancake" recipe in the search results
And I should not see the "french toast" recipe in the search results

1 scenario
6 passed steps
Yay!

I rather like that. It is especially nice that the transform.js file is pure javascript, making is easily tested. I may very well delete my prototype and implement for real tomorrow.

No comments:

Post a Comment