The Setup

To make use of FluentFixtures, you’ll need to first set up a module to contain them. Each base fixture you declare will show up in this module.

For example, say I’m adding fixtures to a hypothetical codebase for acme-warehouse.com’s website. It has a set of Sequel::Model classes that are backed by a PostgreSQL database. with a schema like:

CREATE SCHEMA acme;
CREATE TABLE acme.customers (
    id serial primary key,
    first_name text NOT NULL,
    last_name text NOT NULL
);
CREATE TABLE acme.orders (
    id serial primary key,
    ordered_at timestamp with time zone DEFAULT now(),
    updated_at timestamp with time zone,
    customer_id integer REFERENCES acme.customers NOT NULL
);
CREATE TABLE acme.order_items (
    id serial primary key,
    sku text NOT NULL,
    order_id integer REFERENCES acme.orders NOT NULL
);

The codebase has three classes: Customers, Orders, and OrderItems, all in the Acme namespace:

# lib/acme.rb

require 'sequel'

module Acme

    DB = Sequel.postgres( 'acme' )

    Sequel::Model.plugin :validation_helpers
    Sequel::Model.plugin :auto_validations, not_null: :presence

    autoload :Customer, 'acme/customer'
    autoload :Order, 'acme/order'
    autoload :OrderItem, 'acme/order_item'

end

And the model classes look something like this:

# lib/acme/customer.rb

require 'sequel/model'
require 'acme' unless defined?( Acme )

class Acme::Customer < Sequel::Model( :acme__customers )

    one_to_many :orders

end

# lib/acme/order.rb

require 'sequel/model'
require 'acme' unless defined?( Acme )

class Acme::Order < Sequel::Model( :acme__orders )

    many_to_one :customer, class: 'Acme::User'
    one_to_many :order_items

end

# lib/acme/order_item.rb

require 'sequel/model'
require 'acme' unless defined?( Acme )

class Acme::OrderItem < Sequel::Model( :acme__order_items )

    many_to_one :order

end

Collections

To start the fixture library, I’ll create a new lib/acme/fixtures.rb that looks like:

# lib/acme/fixtures.rb

require 'fluent_fixtures'
require 'acme' unless defined?( Acme )

module Acme::Fixtures
    extend FluentFixtures::Collection

    fixture_path_prefix 'acme/fixtures'

end

The extend line tell FluentFixtures that the extended module is a collection of related fixtures, and the fixture_path_prefix line tells FluentFixtures where to find the files that contain the individual fixture declarations themselves.

This module will act as the main interface to all of ACME’s fixtures.

Fixtures

First, we’ll add a bare-bones customer fixture for creating instances of Acme::Customer:

# lib/acme/fixtures/customers.rb

require 'acme/fixtures'
require 'acme/customer

module Acme::Fixtures::Customers
    extend Acme::Fixtures
    fixtured_class Acme::Customer
end

This time, the extend line tells the fixture collection we just created that any fixtures declared in this module belong to it. The fixtured_class declaration tells FluentFixtures what kinds of objects these fixtures will create.

This by itself sets up some defaults based on convention. The first is the “base” fixture, which is the name of the method you’ll call on the collection to get a factory that can create Acme::Customer objects:

customer = Acme::Fixtures.customer
# => #<FluentFixtures::Factory:0x007fede4113210 for Acme::Fixtures::Customers>

customer.instance
# => #<Acme::Customer @values={}>

The base Declaration

If I wanted the base fixture to be called something else, I could also override the conventional one using the base declaration:

module Acme::Fixtures::Customers
  # ...
  base :user
end

customer = Acme::Fixtures.user
# => #<FluentFixtures::Factory:0x007fc15992a370 for Acme::Fixtures::Customers>

Obviously this is a little unintuitive, so I won’t actually do that, but the base declaration can also take a block to provide reasonable defaults. I’ll use the Faker gem to generate a default first and last name if one hasn’t already been set when the object is created:

require 'faker'

module Acme::Fixtures::Customers
  # ...
  base :customer do
    self.first_name ||= Faker::Name.first_name
    self.last_name ||= Faker::Name.last_name
  end
end

customer = Acme::Fixtures.customer
# => #<FluentFixtures::Factory:0x007fdb492cd4c8 for Acme::Fixtures::Customers>
customer.instance
# => #<Acme::Customer @values={:first_name=>"Polly", :last_name=>"Larson"}>

The block executes in the context of the new object if the base block doesn’t take an argument; you can also declare a block that accepts the new object as an argument if you prefer that.

Decorators

Hooks

Extending Other Fixtures

Sometimes you want a collection of decorators that apply to fixtures declared elsewhere. You can do this to keeps concerns which are separated in the code separate in fixtures which test it, or just to aid in organizing fixtures according to criteria other than their fixtured classes.

To do this, you can use the #additions_for declaration. It takes the name of the fixture you wish to extend as its first argument, and a block that will be evaluated in the context of the target fixture as the second:

# lib/acme/fixtures/customers.rb
require 'faker'

module Acme::Fixtures::Customers
  # ...
  base :customer do
    self.first_name ||= Faker::Name.first_name
    self.last_name ||= Faker::Name.last_name
  end
end


# lib/acme/fixtures/stripe.rb
require 'stripe'

module Acme::Fixtures::Stripe

  additions_for( :customers ) do
    decorator :with_stripe_custom_account do
      acct = Stripe::Account.create({
          :country => "US",
          :type => "custom"
      })
      self.stripe_id = acct.id
    end
  end

end

user = Acme::Fixtures.customer.with_stripe_custom_account.create
user.stripe_id
# => "acct_12QkqYGSOD4VcegJ"

RSpec