Thursday, May 08, 2008

Model factories for Rails

In looking for alternatives to fixtures in Rails I found the Object Mother/Factory pattern. The only thing I don't like about it is that all your examples are in a seperate file somewhere. I wrote a monkey patch for ActiveRecord that lets you put basic validating examples in your model code that you can call from your tests. I like to use random data in my factories, but it's not required. It's really nice to not have to build the object trees by hand for each test. You can pass in any attributes you want to override the defaults.

(Readable code)

## category.rb, using random data to find unknown edge cases
minimum_example do
set_name = Faker::Lorem.word
sometimes { set_name = 'Materials' }

{
:name => Faker::Lorem.unique,
:set_name => set_name
}
end

## product.rb model
minimum_example do |attrs|
# override attributes from the caller
cat = attrs[:category] || example(:category)

{
# The category set_name is random, we might get a material
:name => cat.is_material? ? nil : Faker::Lorem.unique,
:category => cat,
:material_type => cat.is_material? ? example(:material_type) : nil
}
end
optional_example do |obj|
# you can modify the object directly
obj.color = 'blue'

# or just return more attributes
{
:size => 23
}
end
# anything_example will be alternate types of examples
material_example do
{
:inv_category => example(:inv_category, :set_name => 'Materials'),
:material_type => example(:material_type)
}
end
bob_example { { :color => 'blue' }} #any name works

Product.material_example #use autogenerated examples
Product.bob_example
Product.example
Product.example(:category => nil)
Product.example(:category => nil, :save => false) # intantiate the object w/o saving

# use optional_example, for non-required attributes
Product.example(:category => nil, :full => true)

# a more complete example excerpt
should 'list unique purchase_quotes' do
quote = Quote.example(:full => true)
pq1 = PurchaseQuote.example
pq2 = PurchaseQuote.example

pq2.purchase_quote_items << quotable =""> quote.quotables[0], :vendor => pq2.vendor)
quote.quotables.each do |q|
pq1.purchase_quote_items << quotable =""> q, :vendor => pq1.vendor)
end

list = quote.purchase_quotes
list2 = list.uniq
assert_equal list2.size, list.size
end


In writing some of these examples, I have no recollection whan attributes are required or how to build the complete object tree to create a valid product, but the code does it for me. That reduces the deterrent to write tests and therefor we have more tests.

There are many uses once you have easy access to a generator for random data, you can fill up your database with varied test data and run things against it.

No comments: