Fabrication
Github Repo: http://www.github.com/paulelliott/fabrication
One of my takeaways from RailsConf this year was inspiration to write a new gem. I was sitting at a fast testing round table discussion and people kept bringing up the use of build versus create in factories.
That gave me the idea to lazily generate activerecord associations. Doing that allows you to define your entire object graph in your Fabricators and only generate the pieces you need for the spec you are running. When the association is first accessed, the Fabricators will generate the defined objects, persist them to the database, and return the set as if they were there to begin with. While I was at it, I figured it would be handy to be able to generate regular old ruby objects and Mongoid documents, so I baked in support for doing that as well! Let’s say we have a Person object that is just a regular old class in Ruby. We can define a Fabricator with static or evaluated attribute values as seen below. You can then Fabricate as many as you want and override specific fields as you go.
class Person; attr_accessor :first_name, :last_name, :age, :friends end
Fabricator(:person) do
first_name “Joe”
last_name { Faker::Name.last_name }
age { rand(100) }
friends(:count => 10) { |index| Fabricate(:person) }
end
person = Fabricate(:person, :last_name => “Schmoe”)
Notice that you don’t need to pass a parameter to the block. I figured that would just be extra and unnecessary syntax. Also note the :count parameter. When generating a collection, this allows you to tell Fabrication how many of the object you want it to generate. It will automatically build that many and return an array of the new objects. The block receives the index of the generation as a parameter in case you need it. If we want to get a little bit more fancy, we can generate active record models as well.
class Company < ActiveRecord::Base
has_many :divisions
belongs_to :location
end
Fabricator(:company) do
divisions(:count => 3) { |company, index| Fabricate(:division, :company => company) }
after_create { |company| company.update_attribute(:location, Fabricate(:location) }
end
company = Fabricate(:company)
# At this point, we have a location but no divisions.
Division.count
# => 0, because they haven’t been created.
company.divisions.any?
# => true, because the divisions get created when the “getter” for the association is called.
Division.count
# => 3, because now they exist!
Of course, with great power comes great responsibility. This feature is very handy but can also be very dangerous. It is something you need to keep in mind as you are writing your specs. And finally, you can generate Mongoid Documents very easily.
class Author
include Mongoid::Document
field :name
field :books => Array
end
Fabricator(:author) do
name { Faker::Name.name }
books(:count => 5) { Fabricate(:book) }
end
author = Fabricate(:author)
It is that simple! I have been successfully using this in a new Rails 3/Mongoid project I am working on. It won’t step on the toes of other frameworks, so you can also use it alongside your existing factory_girl or machinist implementation. Please check it out, try it out in your projects, and let me know what you think! Also report any bugs or feature requests you find!