Boston.rb Hackfest 5/07 Post Mortem
By now, I think it’s a running joke that we’d work on the recommendable plugin we started a month or so ago.
Instead, we worked on the Factory stuff. This article captures most of the ideas of using factories for testing and reasons for using them instead of fixtures.
The crew tonight:
As the article mentions, someone did implement a plugin which captures most of these ideas.
There were a few reasons (and some more which I don’t remember) why we didn’t like this plugin.
- Plugin, not a gem, so unusable outside of rails
- Pollutes the namespace of
- No tests
So, the journey for better Factories begins. The basic ideas/goals we settled on is this:
- Define your factories in
- Be able to define a factory for a class with default options
- Be able to define alternate factories for a class, for example, to create an admin user versus a normal user
- Flexibility for defining default attributes, ie be able to call other methods
- Choice of making new instance, persisting a new instance, stubbing an instance, or getting valid attributes
- Flexible API for actually calling the factories
So we started out with a basic API that Dan Croak had outlined:
As for usage of the factories, we had 3 possibilities:
I’m not going to say much about these, aside from the first being the simplest to implement, the second being the most explicit about what you are doing, and the last is the most concise.
Because it seemed like the simplest, we started implementing the first. I believe that the other two syntaxes can actually be emulated using it.
To simulate the second, as we register factories, we can create the appropriate methods, ie
To simulate the third, we can build on the second, and probably do some magic to delegate from
TestCase to our Factory.
For simplicity’s sake, we started with our work from last week’s hackfest on bostonrb.org.
We added the snippet for defining factories into our
test_helper.rb, made a new model unit test which used the factory methods, and started a Factory class.
We basically ran the test, watched it blow up horribly, and added some code, until eventually the test passed.
You can see the work we ended up in the hackfest repository under ‘boston_rb’
Of particular interest are:
So, I’m just go over a few of the more interesting bits of the implementation:
This is the entrance into defining our factories.
The implementation isn’t very interesting. We just call the block on Factory. We were originally making new instances of Factory, and passing that to the block, but we then realized that this made it a little trickier to have factory methods be class-level methods.
This is what you use to define how you the factory behaves.
It takes a block which defines how to create new attributes. If you look at the snippet:
event.date are actually taking blocks. This allows us to do
Time.now, and have this not actually be invoked until object creation.
Factory.add actually passes your block a magical
Hash which captures this attribute/attribute creation data. We’ll talk about this has in a little bit.
After we get back from the block, we store in
@@model_templates, keyed by the model and kind of the factory.
The Factory methods
We provide three factory methods:
valid_attributes: a Hash of valid attributes for the object
new: a new unpersisted instance, using
create: a persisted instance, using
You invoke them like:
The way we parse the arguments is a little tricky. Initially, we only supported the first 2 lines. This meant our method signature looked like:
When we went to support the 3rd line, we thought to go like:
By doing so, you break the 2nd line, because
kind would get set to our
activesupport provides a solution:
Array#extract_options. Basically, it looks at the
Array, and pops the last element off the end if it’s a
Hash. We use it like:
We made a subclass of
Hash which lets you define keys like:
Such that it’s equivalent to:
Here’s the implementation:
BlockfulHash is what gets passed to the block given to
So, we have something workable here:
- Uses the syntax we laid out for defining factory
- Supports the first syntax for using factories (
As far as what is left to do:
- Extract it out of the boston_rb code base
- More tests
Some links and other ideas tossed around over the course of the night: