Sunday, March 14, 2010

BDDing Out of a Hole

‹prev | My Chain | next›

Yesterday was pretty much a mess. I am adding generators to my couch_docs gem, which pushes to / dumps from CouchDB. Hopefully today's link in the chain will go a little better.

First up, picking up where I left off:



I left myself a failing RSpec example to point the way today. Specifically, I need to be able to generate a library template. The spec is failing with a SystemExit exception because the lib generator is not known. So, let's make it known:
# More to come...
GENERATORS = %w{lib}

#...
if @command == "generate"
generator = args.shift
if GENERATORS.include? generator
Generator.generate(generator.to_sym, *args)
else
say "Invalid generator #{generator}"
exit
end
else
#...
That should get things passing... But:
1)
NameError in 'CouchDocs::CommandLine generate should be able to generate a library template'
uninitialized constant CouchDocs::CommandLine::Generator
./spec/command_line_spec.rb:36:in `new'
./spec/command_line_spec.rb:36:
Ah, I have finally gotten to the point at which I can create the Generator class. For now, I define the simplest thing that can possibly make my failing example pass:
module CouchDocs
class Generator
def self.generate(generator, *args)
end
end
end
With, that... my example still fails:
1)
Spec::Mocks::MockExpectationError in 'CouchDocs::CommandLine generate should be able to generate a library template'
<CouchDocs::Generator (class)> expected :generate with (:lib, "utils") once, but received it 0 times
./spec/command_line_spec.rb:37:
Ah, I've got everything in the wrong place here. I am trying to generate the template in the same place that I am parsing options. Once I split the generate command out into the run portion of the CommandLine:
#...
when "generate"
Generator.generate(@options[:generator].to_sym,
*@options[:args])
#...
Everything works. Well, "works" is a bit of a stretch. All of the specs pass, but the Generator is on the sparse side.

I am working through the lib template generator first because it is the simplest. It will generate only a single, empty template in the _design/__lib directory. The couch_docs gem will insert the contents of that file in the presence of a !code macro. From a generator perspective, all I need to do is create a blank template. The template RSpec example:
describe CouchDocs::Generator do
context "lib" do
it "should pull in the lib template" do
Generator.
should_receive(:lib_template)

Generator.generate(:lib, 'utils')
end
end
end
To make that pass:
module CouchDocs
class Generator
def self.generate(generator, *args)
template =
self.send("#{generator}_template".to_sym)
end

protected
def self.lib_template; "" end
end
Next up, I need to make the _design/__lib directory if it doesn't already exist:
    it "should make the __lib directory (if it doesn't alreaddy exist)" do
FileUtils.
should_receive(:mkdir_p).
with("_design/__lib")

Generator.generate(:lib, 'utils')
end
Making that pass is easy enough:
    def self.generate(generator, *args)
template =
self.send("#{generator}_template".to_sym)

FileUtils.mkdir_p("_design/__lib")
end
Finally, I need to write that template:
    it "should open the blank lib template for writing" do
File.
should_receive(:new).
with("_design/__lib/utils.js", "a").
and_return(@file)

Generator.generate(:lib, 'utils')
end
The @file in there is a null object setup in the before(:each) block. I am opening the file in append mode so that files do not accidentally over-write existing code. Ideally I would prefer a dialog system similar to Rails, but, as I found yesterday, rubigen feels like overkill. To get that example passing, I add the File.new code:
    def self.generate(generator, *args)
template =
self.send("#{generator}_template".to_sym)

FileUtils.mkdir_p("_design/__lib")

File.new("_design/__lib/#{args.first}.js", "w")
end
I drive the write behavior with a similar example.

This is a decent stopping point for the day. Next up, I need to drive the slightly more sophisticate map-reduce generators. This has been a real struggle so far. I know how the Rails generator works. I really want to start breaking this out into classes that hold manifests, write templates, etc.. But I am staying true to BDD and will see where it leads me.

Day #42

No comments:

Post a Comment