0.0
No release in over a year
Your factories are now classes.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

~> 1.8
~> 3.12

Runtime

~> 7.0, >= 7.0.6
~> 7.0, >= 7.0.6
 Project Readme

ActionFactory

A lightweight, object-oriented factory library for Ruby and Rails that centralizes record creation with minimal overhead.

Philosophy

ActionFactory is designed to be a lightweight alternative to more feature-rich factory libraries. It's built for teams that:

  • Use fixtures as their primary testing strategy but need factories for specific use cases
  • Want centralized record creation without the overhead of replacing their entire test data strategy
  • Prefer object-oriented design with explicit factory classes over DSLs
  • Need ActiveRecord integration but want to keep it optional and modular

Unlike other factory libraries, ActionFactory doesn't try to replace your entire test data strategy. Instead, it complements fixture-based workflows by providing a clean way to create records when fixtures aren't the right tool for the job.

Key Features

  • Object-oriented factory classes with clear inheritance
  • Lightweight with minimal dependencies
  • Optional ActiveRecord integration with association helpers
  • Flexible strategies (build vs create)
  • Traits for composable variations
  • Sequences for unique values
  • Lifecycle callbacks using ActiveModel::Callbacks

Installation

Via bundler:

bundle add action_factory

Or install it yourself:

gem install action_factory

Usage

Setup

Create a base factory class:

class ApplicationFactory < ActionFactory::Base
end

If you're using ActiveRecord, add the association helpers:

class ApplicationFactory < ActionFactory::Base
  include ActionFactory::ActiveRecord
end

Creating Your First Factory

class UserFactory < ApplicationFactory
  attribute(:name) { "Hank Green" }
  attribute(:email_prefix) { name.downcase.gsub(" ", ".") }
  sequence(:email) { |i| "#{email_prefix}#{i}@example.com" }
  attribute(:role) { "user" }

  trait(:admin) do
    instance.role = "admin"
  end

  trait(:activated) do
    instance.activate!
  end
end

Using Factories

ActionFactory provides three ways to create instances, from most convenient to most explicit:

1. Module-Level Methods (Recommended)

# Build an instance (doesn't save to database)
user = ActionFactory.build(:user)
# => #<User name="Hank Green", email="hank.green1@example.com", role="user">

# Create an instance (saves to database)
user = ActionFactory.create(:user)
# => #<User id=1, name="Hank Green", email="hank.green2@example.com", role="user">

# With attributes
user = ActionFactory.create(:user, name: "Jane Doe")
# => #<User id=2, name="Jane Doe", email="jane.doe3@example.com", role="user">

# With traits
admin = ActionFactory.create(:user, :admin)
# => #<User id=3, name="Hank Green", email="hank.green4@example.com", role="admin">

# With traits and attributes
admin = ActionFactory.create(:user, :admin, :activated, name: "Jane Doe")
# => #<User id=4, name="Jane Doe", email="jane.doe5@example.com", role="admin">

2. Factory Class Methods

# Build an instance
user = UserFactory.build
# => #<User name="Hank Green", email="hank.green1@example.com", role="user">

# Create an instance
user = UserFactory.create(name: "Jane Doe")
# => #<User id=1, name="Jane Doe", email="jane.doe2@example.com", role="user">

# With traits
admin = UserFactory.create(:admin, :activated)
# => #<User id=2, name="Hank Green", email="hank.green3@example.com", role="admin">

3. Factory Instance (Most Explicit)

# Useful when you need to inspect factory state or customize behavior
factory = UserFactory.new(:admin, name: "Jane Doe")
factory.traits        # => [:admin]
factory.attributes    # => {:name=>"Jane Doe"}
user = factory.create # => #<User id=1, name="Jane Doe", role="admin">

Using Helpers (Optional)

For even shorter syntax in tests, include ActionFactory::Helpers:

RSpec.configure do |config|
  config.include ActionFactory::Helpers
end

# In your tests - just like using ActionFactory.build/create
user = build(:user)  # Equivalent to ActionFactory.build(:user)
user = create(:user, name: "John Doe")
user = create(:user, :admin, :activated)

Note: The helper methods are convenience wrappers. We recommend using ActionFactory.build and ActionFactory.create directly for better clarity about where the methods come from.

Getting the Factory Class

If you need access to the factory class itself:

factory_class = ActionFactory.factory_for(:user)
# => UserFactory

# Useful for metaprogramming or inspecting factory configuration
factory_class.assignments
factory_class.klass  # => User

Sequences

Sequences generate unique values each time they're called:

class UserFactory < ApplicationFactory
  sequence(:email) { |i| "user#{i}@example.com" }
end

user1 = ActionFactory.create(:user)  # => email: "user1@example.com"
user2 = ActionFactory.create(:user)  # => email: "user2@example.com"

You can reference other attributes in sequences:

class UserFactory < ApplicationFactory
  attribute(:username) { "jdoe" }
  sequence(:email) { |i| "#{username}#{i}@example.com" }
end

user = ActionFactory.create(:user, username: "janedoe")  # => email: "janedoe1@example.com"

Traits

Traits allow you to define variations of your factory:

class PostFactory < ApplicationFactory
  attribute(:title) { "My Post" }
  attribute(:status) { "draft" }

  trait(:published) do
    instance.status = "published"
    instance.published_at = Time.current
  end

  trait(:with_long_title) do
    instance.title = "This is a very long title that exceeds normal length"
  end
end

post = ActionFactory.create(:post, :published, :with_long_title)

Associations (ActiveRecord)

When you include ActionFactory::ActiveRecord, you get powerful association helpers:

class PostFactory < ApplicationFactory
  include ActionFactory::ActiveRecord

  attribute(:title) { "My Post" }
  attribute(:body) { "Post content here" }

  # Automatically builds/creates associated records
  association :author, factory: :user, strategy: :create
end

# When you create a post, it automatically creates an author
post = ActionFactory.create(:post)
post.author  # => #<User id=1, ...>

Association Options

Without a block - Uses the specified strategy or inherits from parent:

# Always create the author, even when building the post
association :author, factory: :user, strategy: :create

# Use the same strategy as the parent (build post = build author, create post = create author)
association :author, factory: :user

# Apply traits to the associated record
association :author, factory: :user, traits: [:admin, :activated]

With a block - Full control over association creation:

# The block receives (runner, strategy) where strategy is :build or :create
association :author do |runner, strategy|
  # Option 1: Use the parent's strategy
  runner.run(strategy)
end

association :author do |runner, strategy|
  # Option 2: Always create, regardless of parent strategy
  runner.run(:create)
end

association :author do |runner, strategy|
  # Option 3: Conditional logic
  if strategy == :create
    runner.run(:create, :verified)  # Created posts get verified authors
  else
    runner.run(:build)
  end
end

association :author do |runner, strategy|
  # Option 4: Custom logic, bypass the factory entirely
  User.find_by(email: "default@example.com") || runner.run(strategy)
end

Association Inheritance

Associations can be overridden in subclasses:

class PostFactory < ApplicationFactory
  association :author, factory: :user
end

class PublishedPostFactory < PostFactory
  # Override to always use an admin
  association :author, factory: :user, traits: [:admin]
end

Callbacks

ActionFactory uses ActiveModel::Callbacks to provide lifecycle hooks:

after_initialize        # After the instance is initialized
before_assign_attributes # Before attributes are assigned
around_assign_attributes # Around attribute assignment
after_assign_attributes  # After attributes are assigned
before_create           # Before saving to database
around_create           # Around saving to database
after_create            # After saving to database

Example usage:

class MyModelFactory < ApplicationFactory
  after_initialize :set_defaults
  before_create :prepare_for_save
  after_create :send_notification

  private

  def set_defaults
    instance.some_attribute = true if attributes[:some_attribute].blank?
  end

  def prepare_for_save
    instance.normalize_data
  end

  def send_notification
    NotificationService.send(instance)
  end
end

Custom Initialization and Persistence

You can customize how instances are initialized and persisted:

class UserFactory < ApplicationFactory
  # Custom initialization
  to_initialize do |factory|
    User.new.tap do |user|
      user.created_by_factory = true
    end
  end

  # Custom persistence
  to_create do |factory|
    instance.save(validate: false)
    instance.reload
  end
end

Or configure globally:

ActionFactory.configure do |config|
  config.initialize_method = :new  # default
  config.persist_method = :save!   # default
end

Factory Registration

By default, ActionFactory infers the model class from the factory name:

class UserFactory < ApplicationFactory
end
# => Creates User instances

For custom mappings, use register:

class AdminFactory < ApplicationFactory
  register class_name: "User"
end
# => Creates User instances

class SpecialUserFactory < ApplicationFactory
  register factory_name: :special, class_name: "User"
end
# => Available as create(:special), creates User instances

Inheritance

Factories support inheritance, making it easy to create variations:

class UserFactory < ApplicationFactory
  attribute(:name) { "John Doe" }
  attribute(:role) { "user" }
end

class AdminFactory < UserFactory
  attribute(:role) { "admin" }
  attribute(:permissions) { "all" }
end

admin = ActionFactory.create(:admin)
# => #<User name="John Doe", role="admin", permissions="all">

Patterns and Best Practices

Fixture-First, Factory-Second

Use fixtures for your standard test data and factories for:

  • Tests that need many variations of the same record
  • Tests that require specific attribute combinations
  • Integration tests that need to create records with associations
  • Tests that need to create records with state that's hard to represent in fixtures
# test/fixtures/users.yml
admin:
  name: "Admin User"
  email: "admin@example.com"
  role: "admin"

# Use fixtures for standard cases
test "admin can access dashboard" do
  user = users(:admin)
  assert user.can_access_dashboard?
end

# Use factories for variations
test "user with specific permissions" do
  user = ActionFactory.create(:user, permissions: {read: true, write: false, delete: false})
  assert_not user.can_delete?
end

Keep Factories Simple

Factories should create valid, minimal records:

# Good - minimal and clear
class UserFactory < ApplicationFactory
  attribute(:email) { "user@example.com" }
  attribute(:name) { "Test User" }
end

# Avoid - too much setup logic
class UserFactory < ApplicationFactory
  attribute(:email) { "user@example.com" }
  attribute(:name) { "Test User" }
  after_create :create_profile
  after_create :send_welcome_email
  after_create :add_to_mailing_list
  after_create :create_default_preferences
end

Use Traits for Variations

class UserFactory < ApplicationFactory
  attribute(:status) { "pending" }

  trait(:active) do
    instance.status = "active"
    instance.activated_at = Time.current
  end

  trait(:suspended) do
    instance.status = "suspended"
    instance.suspended_at = Time.current
  end
end

Association Strategies

Choose the right strategy for your associations:

class PostFactory < ApplicationFactory
  # Build for unit tests (faster, no database hits for associations)
  association :author, factory: :user, strategy: :build

  # Create for integration tests (proper foreign keys and database constraints)
  association :author, factory: :user, strategy: :create

  # Conditional for flexibility
  association :author do |runner, strategy|
    # Use the same strategy as the parent
    runner.run(strategy)
  end
end

Configuration

ActionFactory.configure do |config|
  # Method called to initialize new instances (default: :new)
  config.initialize_method = :new

  # Method called to persist instances (default: :save!)
  config.persist_method = :save!
end

Comparison with Other Libraries

vs FactoryBot: ActionFactory is lighter weight and designed to complement fixtures rather than replace them. It uses explicit factory classes instead of a DSL, making it easier to use object-oriented patterns like inheritance and composition.

vs Fabrication: Similar philosophy of lightweight factories, but ActionFactory has built-in ActiveRecord association support and uses a more traditional Ruby class structure.

vs Fixtures: ActionFactory generates fresh data for each test, avoiding issues with shared mutable state. Use factories when you need variations or complex setups that are hard to express in static YAML files.

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

License

The gem is available as open source under the terms of the MIT License.