Fabrication
object generation for Ruby
Fabrication is a simple and powerful object generation library. Just define Fabricators -- the schematics for your objects -- and quickly Fabricate objects as needed anywhere in your app or specs.
Currently supported object types are:
- Plain old Ruby objects
- ActiveRecord objects
- Mongoid Documents
- Sequel Models
- DataMapper Resources
Installation
Fabrication is tested against Ruby 1.9.2, 1.9.3, and Rubinius.
(version 1.2.0 is the last release with 1.8 compatibility)
To use it with Bundler, just add it to your gemfile.
gem 'fabrication'
Fabricators defined in the right place are automatically loaded so no additional requires are necessary.
spec/fabricators/**/*fabricator.rb
test/fabricators/**/*fabricator.rb
Configuration
You can specify where the fabricators are loaded from with a configuration option …
Fabrication.configure do |config|
config.fabricator_dir = "data/fabricators"
end
… or you can pass an array of locations.
Fabrication.configure do |config|
config.fabricator_dir = ["data/fabricators", "spec/fabricators"]
end
Defining Fabricators
Arguments
The first argument to the fabricator is the name you will use when fabricating objects or defining associations. It should be the symbolized form of the class name.
class Person; end
Fabricator(:person)
To use a different name from the class, you must specify :from =>
:symbolized_class_name as the second argument.
Fabricator(:adult, :from => :person)
The value of :from can be either a class name or the name of another
fabricator.
Attributes
The block for the Fabricator does not take a block variable. You can simply list the attributes to be generated and they will be created in order of declaration.
Fabricator(:person) do
name 'Greg Graffin'
profession 'Professor/Musician'
end
To produce dynamic values, you can pass a block to the attribute.
Fabricator(:person) do
name { Faker::Name.name }
profession { %w(Butcher Baker Candlestick\ Maker).sample }
end
You can also access the current state of the object being generated with each attribute. Note that attributes are processed in order of declaration, so only fields above the current one will be available.
Fabricator(:person) do
name { Faker::Name.name }
email { |person| "#{person.name.parameterize}@example.com" }
end
Associations
You can associate another fabricator by just writing the attribute name.
Fabrication will look up a fabricator of that name, generate the object, and
set it in the current object. This is great for belongs_to associations.
Fabricator(:person) do
vehicle
end
…is equivalent to…
Fabricator(:person) do
vehicle { Fabricate(:vehicle) }
end
You can specify which fabricator to use in that situation as well.
Fabricator(:person) do
ride(:fabricator => :vehicle)
end
…is equivalent to…
Fabricator(:person) do
ride { Fabricate(:vehicle) }
end
Fabrication will lazily generate ActiveRecord associations by default. If you
define has_many :widgets, it will wait to generate the widgets until the
getter for the association is accessed. You can override this by appending !
to the name of the association in the Fabricator. You would typically want to
do this in the case of a belongs_to or any other required association.
Fabricator(:person) do
mother!
father!
end
You can also generate arrays of objects with the count parameter. The attribute block receives the object being generated as well as the incrementing value.
Fabricator(:person) do
children(:count => 3) { |parent, i| Fabricate(:person, :parent => parent) }
end
Inheritance
You can inherit attributes from other fabricators by using the :from attribute.
Fabricator(:llc, :from => :company) do
type "LLC"
end
Setting the :from option will inherit the class and all the attributes from the named Fabricator.
You can also explicitly specify the class being fabricated with the :class_name parameter.
Fabricator(:llc, :class_name => :company) do
type "LLC"
end
Callbacks
You can specify callbacks in your Fabricator that are separate from the object’s callbacks.
If you have an object with required arguments in the constructor, you can use
the on_init callback to supply them.
Fabricator(:location) do
on_init { init_with(30.284167, -81.396111) }
end
To hook into Fabrication’s build cycle for the object, you can use
after_build and after_create.
Fabricator(:place) do
after_build { |place| place.geolocate! }
after_create { |place| Fabricate(:restaurant, :place => place) }
end
The callbacks are all stackable, meaning that you can declare multiple in a fabricator and they will not be clobbered when you inherit another fabricator.
Aliases
You can provide aliases for a fabricator by supplying the :aliases option to the Fabricator call.
Fabricator(:thingy, aliases: [:widget, :wocket])
You can now call Fabricate with :thingy, :widget, or :wocket and receive back the generated object.
Reloading
If you need to reset fabrication back to its original state after it has been loaded, call:
Fabrication.clear_definitions
This is useful if you are using something like Spork and reloading the whole environment is not desirable.
Fabricating Objects
The Basics
The simplest way to Fabricate an object is to pass the Fabricator name into Fabricate.
Fabricate(:person)
That will return an instance of Person using the attributes you defined in the Fabricator.
To set additional attributes or override what is in the Fabricator, you can pass a hash to Fabricate with the fields you want to set.
Fabricate(:person, :first_name => "Corbin", :last_name => "Dallas")
The arguments to Fabricate always take precedence over anything defined in the Fabricator.
Fabricating With Blocks
In addition to the hash, you can pass a block to Fabricate and all the features of Fabricator’s available to you at object generation time.
Fabricate(:person, :name => "Franky Four Fingers") do
addiction "Gambling"
fingers(:count => 9)
end
The hash will overwrite any fields defined in the block.
Building
If you are using an ORM with a save method, sometimes it is necessary to just
build and not actually save objects. In that case, you can use
Fabricate.build and skip the save step. All the normal goodness when
Fabricating is available for building as well.
Fabricate.build(:person)
Attributes Hash
You can also receive the object back in the form of a hash. This processes all the fields defined, but doesn’t actually create the object. If you have ActiveSupport it will be a HashWithIndifferentAccess, otherwise it will be a regular Ruby hash.
Fabricate.attributes_for(:company)
Sequences
A sequence allows you to get a series of numbers unique within the current process. Fabrication provides you with an easy and flexible means for keeping track of sequences.
You can create a sequence that starts at 0 anywhere in your app with a simple command.
Fabricate.sequence
# => 0
# => 1
# => 2
You can name them by passing an argument to sequence.
Fabricate.sequence(:name)
# => 0
# => 1
# => 2
If you want to specify the starting number, you can do it with a second parameter. It will always return the seed number on the first call and it will be ignored with subsequent calls.
Fabricate.sequence(:number, 99)
# => 99
# => 100
# => 101
If you are generating something like an email address, you can pass it a block and the block response will be returned.
Fabricate.sequence(:name) { |i| "Name #{i}" }
# => "Name 0"
# => "Name 1"
# => "Name 2"
You can use the shorthand notation if you are using them in your fabricators.
Fabricate(:person) do
ssn { sequence(:ssn, 111111111) }
email { sequence(:email) { |i| "user#{i}@example.com" } }
end
# => <Person ssn: 111111111, email: "[email protected]">
# => <Person ssn: 111111112, email: "[email protected]">
# => <Person ssn: 111111113, email: "[email protected]">
Rails 3
You can configure Rails 3 to produce fabricators when you generate models by
specifying it in your config/application.rb. Use this if you are using rspec:
config.generators do |g|
g.test_framework :rspec, :fixture => true
g.fixture_replacement :fabrication
end
… and this if you are using test/unit:
config.generators do |g|
g.test_framework :test_unit, :fixture_replacement => :fabrication
g.fixture_replacement :fabrication, :dir => "test/fabricators"
end
Once it’s setup, a fabricator will be generated whenever you generate a model.
rails generate model widget
Will produce:
spec/fabricators/widget_fabricator.rb
Fabricator(:widget) do
end
Cucumber Steps
Installing
Packaged with the gem is a generator which will load some handy cucumber steps into your step_definitions folder.
rails generate fabrication:cucumber_steps
Step Definitions
With a Widget Fabricator defined, you can easily fabricate a single “widget”.
Given 1 widget
To fabricate a single “widget” with specified attributes:
Given the following widget:
| name | widget_1 |
| color | red |
| adjective | awesome |
To fabricate multiple “widgets”:
Given 10 widgets
To fabricate multiple “widgets” with specified attributes:
Given the following widgets:
| name | color | adjective |
| widget_1 | red | awesome |
| widget_2 | blue | fantastic |
...
To fabricate “wockets” that belong to widget you already fabricated:
And that widget has 10 wockets
To fabricate “wockets” with specified attributes that belong to your widget:
And that widget has the following wocket:
| title | Amazing |
| category | fancy |
That will use the most recently fabricated “widget” and pass it into the wocket Fabricator. That requires your “wocket” to have a setter for a “widget”.
In more complex cases where you’ve already created “widgets” and “wockets” and associated them with other objects, to set up an association between the former two:
And that wocket belongs to that widget
You can verify that some number of objects were persisted to the database:
Then I should see 1 widget in the database
You can also verify that a specific object was persisted:
Then I should see the following widget in the database:
| name | Sprocket |
| gears | 4 |
| color | green |
That will look up the class defined in the fabricator for “widget” and run a where(…) with the parameterized table as an argument. It will verify that there is only one of these objects in the database, so be specific!
Transforms
You can define transforms to apply to tables in the cucumber steps. They work
on both vertical and horizontal tables and allow you to remap column values.
You can provide string data and perform logic on it to set objects instead.
You can put them in your spec/fabricators folder or whatever you have
configured.
For example, you can define transforms on all columns named “company”. It will pass the strings from the cells into a lambda and set the return value to the attribute on the generated object.
Fabrication::Transform.define(:company, lambda{ |company_name| Company.where(:name => company_name).first })
You can invoke it by putting the expected text in the cells and matching the column name to the symbol.
Scenario: a single object with transform to apply
Given the following company:
| name | Widgets Inc |
Given the following division:
| name | Southwest |
| company | Widgets Inc |
Then that division should reference that company
Scenario: multiple objects with transform to apply
Given the following company:
| name | Widgets Inc |
Given the following divisions:
| name | company |
| Southwest | Widgets Inc |
| North | Widgets Inc |
Then they should reference that company
When the divisions are generated, they will receive the company object as looked up by the lambda.
Extras
Getting Help
Email the fabrication mailing list if you need extra help or have specific questions.
You can also view the raw version of this documentation.
Vim
Vim users can add Fabrication support by adding this to your .vimrc.
autocmd User Rails Rnavcommand fabricator spec/fabricators -suffix=_fabricator.rb -default=model()
You can then open Fabricator files like this.
:Rfabricator your_model
Make Syntax
If you are migrating to Fabrication from Machinist, you can include make syntax
to help ease the transition. Simply require fabrication/syntax/make and you
will get make and make! mixed into your classes.
Contributing
I (paulelliott) am actively maintaining this project. If you would like to contribute, please fork the project, make your changes on a feature branch, and submit a pull request.
Naturally, the Fabrication source is available on Github as is the source for the Fabrication website.
To run rake successfully:
- Clone the project
- Install mongodb and sqlite3 (brew install …)
- Install bundler (gem install bundler)
- Run
bundlefrom the project root - Run
rakeand the test suite should be all green!