standard_procedure_fabrik

Fixtures, fittings and seeds


License
LGPL-2.1
Install
gem install standard_procedure_fabrik -v 0.1.0

Documentation

Fabrik

Simplify your database seeds and test fixtures.

Fabrik is a factory for your ActiveRecord models.

It's designed to be used in both your test cases and in your database seeds.

And I think the API is pretty nice and the code is pretty simple.

Database seeds

Database seeds are great.

Screwed up your development database? Just drop it, rebuild it and load in your starting data.

Added a new feature and want to show people what it looks like? Add some new records into your seeds, run them, then you've got a nice set of demo data, all pre-loaded.

For this to work, seeds need to be idempotent. When you run your seeds against a database with existing data, it should only insert the new stuff, leaving the old untouched.

Fabrik does this.

Specs and tests

I love RSpec. Other test frameworks (cough; minitest) are available.

And if you're writing Rails apps, your tests or specs are, most likely, going to be using ActiveRecord models which end up hitting the database. That's just how Rails works, no matter what the isolationists say.

So, when setting up your test fixtures, you want to set up the smallest amount of data to prove your case.

And, because tests are documentation, you want that setup to be there, next to the test code, so anyone reading it can understand how everything hangs together.

Fabrik does this.

Configuration

Fabrik allows you to configure three aspects of how you create your objects.

Default attributes

When you're writing a spec, you probably only care about one aspect of the model involved.

So Fabrik allows you to set default attributes; then when you're creating a model, you can specify the ones you're interested in and ignore all the rest.

db.configure do
  with Person do
    defaults first_name: -> { "Alice" }, last_name: -> { Faker::Name.last_name }, age: -> { 33 }
  end
end

@alice = db.people.create last_name: "Anteater"

puts @alice.first_name # => Alice 
puts @alice.last_name # => Anteater 
puts @alice.age # => 33

Uniqueness

When you've got a database packed with existing data, you don't want your seeds to fail because of a uniqueness constraint. Or your tests to fail because suddenly they're finding two records when they were only expecting one.

So Fabrik lets you define what makes a model unique. Then when you create it, it checks for an existing record first and only creates a new one if the original is not found.

db.configure do
  with Person do
    search_using :email
  end
end
@alice = db.people.create first_name: "Alice", last_name: "Aardvark", email: "alice@example.com"
@zac = db.people.create first_name: "Zac", last_name: "Zebra", email: "alice@example.com"

@alice == @zac # => true 
puts @zac.first_name # => Alice

Special processing

Some models are special. They can't live on their own. Their invariants won't let them. Maybe a Company cannot exist without a CEO. While your main application logic can ensure that's always the case, when you're writing your tests, it becomes a pain specifying it over and over again.

So Fabrik lets you define specific processing that happens after a new record is created.

db.configure do
  with Company do 
    defaults name: -> { Faker::Company.name }
    after_create do |company, db|
      db.employees.create company: company, role: "CEO"
    end
  end 
  with Employee do 
    defaults first_name: -> { Faker::Name.first_name }, last_name: -> { Faker::Name.last_name }, role: -> { "Cleaner" }
  end
end
db.companies.create name: "MegaCorp"
puts Company.find_by(name: "MegaCorp").employees.size # => 1

References

You've created a load of models. And you need to reference them to create more models. You could search for them by hand, but that's for chumps.

So Fabrik let's you give your models a label. And then refer back to that model using the label.

db.people.create :alice, first_name: "Alice"

puts db.people[:alice] # => Alice

Classes and naming

You've got a load of models in your application. Fabrik allows you to configure some rules for each one. But not all of them need special processing. Why waste your time telling Fabrik about all these classes when you've got more important things to do?

So Fabrik takes a guess at any names you give it and tries its best to figure out which class you mean.

class Intergalactic::Spaceship < ApplicationRecord
  # whatever
end

db.intergalactic_spaceships.create :ufo

puts Intergalactic::Spaceship.count # => 1

Or maybe you're writing a Rails engine and all your classes are namespaced. Typing long names is so boring.

So Fabrik lets you register an alternative name for your classes.

db.configure do 
  with MyAccountsPackageEngine::Invoice, as: :invoice do 
    defaults due_date: -> { 7.days.from_now }
  end
end 

db.invoices.create :overdue_invoice, due_date: 1.day.ago 

Installation

Add standard_procedure_fabrik to your Gemfile

bundle add standard_procedure_fabrik

If you're only using it for tests, add it to your test group. If you're using it for seeds, add it with all your other gems. While you're at it, add Faker as well - at the very least, it will probably make you smile with some of the stuff it generates for your test data.

Usage

Localised (for tests)

Create an instance of a Fabrik::Database, configure it and use it.

db = Fabrik::Database.new 
db.configure do 
  with Person do 
    defaults first_name: -> { Faker::Name.first_name }, last_name: -> { Faker::Name.last_name }, email: -> { Faker::Internet.email }
  end
end

db.people.create :alice, first_name: "Alice"

In an RSpec:

RSpec.describe "Whatever" do 
  let(:db) { Fabrik::Database.new }
  before do 
    db.configure do 
      # ... whatever
    end
  end
end

In a minitest ... I don't know, I've not used minitest in years but I'm sure it's easy enough.

Global (for seeds and generally creating stuff)

Configure the global Fabrik::Database, then use it.

Fabrik.configure do  
  with Person do 
    defaults first_name: -> { Faker::Name.first_name }, last_name: -> { Faker::Name.last_name }, email: -> { Faker::Internet.email }
  end
end

Fabrik.db.people.create :alice, first_name: "Alice"

Watch out - because this uses a global instance, it's not thread-safe. That's not an issue if you're just using it for database seeds or in most test runners(single-threaded or parallelised with multiple processes). But it might cause problems if you're using threads to parallelise your tests, or you're reconfiguring while your application is running.

Development

Important note: this gem is not under the MIT Licence - it's under the LGPL. This may or may not make it suitable for your use. I have reasons for this, which don't belong in a README. But the net result is, this library is open-source, it's free software and you can license your own code any way you want. But if you change this code, you have to publish those changes under the same rules.

So, fork the repo, bundle install, add RSpecs for the changes you want to make and rake spec to run the tests. Then send me a pull request. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/standard-procedure/fabrik.

Code of Conduct

Don't be a dick.

If you think you're being reasonable but most people think you're being a dick, then you're not being reasonable, you're actually being a dick.

We're not computers - we're human beings. And human beings are soft, squishy and emotional. That's just how it is.