FactorySeeder

A powerful Ruby gem that simplifies database seeding using your existing FactoryBot factories, traits, and associations.

Features

  • Automatic Factory Detection: Scans your FactoryBot factories automatically
  • Trait Support: Use existing traits to create varied data
  • Association Handling: Manage complex relationships between models
  • Web Interface: Beautiful web UI for visual seeding with Rails Engine integration
  • CLI Interface: Command-line tool for quick seeding
  • Custom Seeds System: Define reusable seeds with parameter validation
  • Preview Mode: See what data will be generated before creating it
  • Configuration: Flexible configuration for different environments
  • Auto-reload: Changes to custom seeds are automatically reloaded without server restart

Installation

Add this line to your application's Gemfile:

group :development, :test do
  gem 'factory_seeder'
end

Then execute:

bundle install

Quick Start

1. Initialize FactorySeeder

bundle exec factory_seeder init

This creates:

  • config/initializers/factory_seeder.rb - Configuration file
  • db/seeds_factory_seeder.rb - Sample seeds file

2. List Available Factories

bundle exec factory_seeder list

The CLI now displays detailed information for each factory including class name, traits, associations, and key attributes - matching the web interface experience.

3. Generate Seeds

Interactive Mode

bundle exec factory_seeder generate

Direct Mode

bundle exec factory_seeder generate user --count=10 --traits=admin,vip

The generate and preview commands now use config.default_count and config.default_strategy when options are omitted.

With Custom Attributes

bundle exec factory_seeder generate user --count=5 --attributes='{"email":"admin@example.com","role":"admin"}'

4. Web Interface (Rails Engine)

Mount the engine in your Rails application routes:

# config/routes.rb
Rails.application.routes.draw do
  mount FactorySeeder::Engine => "/factory-seeder"
end

Then access the web interface at http://localhost:3000/factory-seeder

Features:

  • Visual factory listing with detailed metadata
  • Trait selection with checkboxes
  • Custom attribute inputs for each factory field
  • Data preview before generation
  • Real-time generation
  • Auto-reload: Custom seeds under db/factory_seeds/*.rb are automatically reloaded when the Rails engine prepares a request - no server restart needed

5. Standalone Web Interface

For non-Rails projects or standalone usage:

bundle exec factory_seeder web --port=4567

The standalone web interface calls FactorySeeder.reload! before each request, so file edits take effect immediately.

Usage

Ruby API

Basic Generation

# In your seeds file
FactorySeeder.generate do |seeder|
  # Create 10 users with admin trait
  seeder.create(:user, count: 10, traits: [:admin])

  # Create posts with associations
  seeder.create_with_associations(:post, count: 5, associations: {
    author: { factory: :user, count: 1 },
    comments: { factory: :comment, count: 3 }
  })

  # Create with custom attributes
  seeder.create(:user, count: 5, attributes: {
    email: "custom@example.com",
    role: "moderator"
  })
end

Different Strategies

# Build instead of create (useful for testing)
seeder.create(:user, count: 3, strategy: :build)

Custom Seeds System

Define reusable seeds with parameter validation:

# db/factory_seeds/users.rb
FactorySeeder.define_seed(:create_users) do |builder|
  builder
    .description("Create users with customizable options")
    .integer_param(:count, required: true, min: 1, max: 100, description: "Number of users")
    .boolean_param(:admin, required: false, default: false, description: "Create admin users")
    .symbol_param(:country, required: false, default: :fr,
                  allowed_values: [:fr, :en, :es, :de], description: "User country")
end do |count:, admin: false, country: :fr|
  traits = admin ? [:admin] : []
  count.times do
    FactoryBot.create(:user, *traits, country: country)
  end
  puts "✅ Created #{count} user(s) with country: #{country}#{admin ? ' (admin)' : ''}"
end

Parameter Types

  • Integer: .integer_param(:count, min: 1, max: 100)
  • Boolean: .boolean_param(:admin, default: false)
  • Symbol: .symbol_param(:status, allowed_values: [:active, :inactive])
  • String: .string_param(:name, allowed_values: ['admin', 'user'])
  • Array: .array_param(:items, default: [])

Running Custom Seeds

# Programmatically
result = FactorySeeder.run_custom_seed(:create_users, count: 10, admin: true)

# Via web interface - navigate to custom seeds section
# Via CLI - use the custom seeds commands

CLI Commands

# List all factories with detailed metadata
factory_seeder list

# Generate seeds interactively
factory_seeder generate

# Generate specific factory
factory_seeder generate user --count=5 --traits=admin,vip

# Generate with custom attributes (JSON format)
factory_seeder generate user --count=3 --attributes='{"email":"test@example.com","admin":true}'

# Preview factory data
factory_seeder preview user --traits=admin

# Start standalone web interface
factory_seeder web --port=4567

# Initialize configuration
factory_seeder init

# List available custom seeds
factory_seeder seeds --list

# Run a specific custom seed
factory_seeder seeds development

# Run all custom seeds
factory_seeder seeds --all

# Preview what would be generated (dry run)
factory_seeder seeds development --dry_run

Configuration

Edit config/factory_seeder.rb:

FactorySeeder.configure do |config|
  # Add custom factory paths
  config.factory_paths << "spec/factories"
  config.factory_paths << "test/factories"

  # Default options
  config.default_count = 10
  config.default_strategy = :create

  # Verbose mode
  config.verbose = Rails.env.development?

  # Environment-specific settings
  config.environments = {
    development: { default_count: 50 },
    test: { default_count: 5 },
    production: { default_count: 1 }
  }
end

Rails Integration

FactorySeeder integrates seamlessly with Rails through a Rails Engine:

Features

  • Automatic Model Loading: Models are loaded before factory analysis using config.after_initialize
  • Development Reloading: In development, models are reloaded when files change via config.to_prepare
  • Conditional Loading: Only forces eager loading when necessary
  • Error Handling: Gracefully handles uninitialized constants and missing dependencies

Troubleshooting Rails Integration

"uninitialized constant" errors

If you encounter errors like NameError: uninitialized constant ModelName, the Rails engine should handle this automatically. If issues persist:

  1. Ensure all migrations are up to date: rails db:migrate
  2. Check that models are properly defined in app/models
  3. Enable verbose mode to see detailed loading information:
FactorySeeder.configure do |config|
  config.verbose = true
end

Factories not detected

If factories aren't appearing:

FactorySeeder.configure do |config|
  config.factory_paths << 'spec/factories'
  config.factory_paths << 'test/factories'
end

Factory Examples

Basic Factory

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    sequence(:email) { |n| "user#{n}@example.com" }
    password { "password123" }

    trait :admin do
      role { "admin" }
    end

    trait :vip do
      vip_status { true }
    end
  end
end

Factory with Associations

# spec/factories/posts.rb
FactoryBot.define do
  factory :post do
    title { Faker::Lorem.sentence }
    content { Faker::Lorem.paragraph }
    association :author, factory: :user

    trait :published do
      published_at { Time.current }
    end

    trait :with_comments do
      after(:create) do |post|
        create_list(:comment, 3, post: post)
      end
    end
  end
end

Advanced Usage

Environment-Specific Seeds

FactorySeeder.generate do |seeder|
  if Rails.env.development?
    seeder.create(:user, count: 100, traits: [:admin])
    seeder.create(:post, count: 500)
  elsif Rails.env.test?
    seeder.create(:user, count: 5)
    seeder.create(:post, count: 10)
  elsif Rails.env.production?
    seeder.create(:user, count: 1, traits: [:admin])
  end
end

Complex Associations

seeder.create_with_associations(:order, count: 10, associations: {
  customer: { factory: :user, traits: [:vip] },
  items: { factory: :product, count: 3 },
  shipping_address: { factory: :address, count: 1 }
})

Using Rails Model Constants

With the Rails engine, you can safely use model constants in your custom seeds:

FactorySeeder.define_seed(:create_orders_with_status) do |builder|
  # Rails models are automatically loaded
  order_statuses = if defined?(Order) && Order.const_defined?(:STATUSES)
    Order::STATUSES.map(&:to_sym)
  else
    [:pending, :confirmed, :completed, :cancelled]
  end

  builder
    .description("Create orders with specific status")
    .symbol_param(:status, required: true, allowed_values: order_statuses)
end do |status:, count: 1|
  count.times do
    Order.create!(status: status)
  end
end

Development

Setup

git clone https://github.com/wecasa/factory_seeder.git
cd factory_seeder
bundle install

Testing

bundle exec rspec

Building the Gem

gem build factory_seeder.gemspec
gem install factory_seeder-0.1.0.gem

Release Process

The gem is published to RubyGems automatically from CI using Trusted Publishing (OIDC). No API key is stored in the repo.

How it works

  • main is protected: every change lands via a pull request approved by a Wecasa engineer.
  • The release workflow (.github/workflows/release.yml) runs on every merge to main.
  • If lib/factory_seeder/version.rb has been bumped (i.e. no git tag matches the current version yet), the workflow:
    1. Runs the specs.
    2. Tags the merge commit vX.Y.Z.
    3. Builds the gem and pushes it to RubyGems via OIDC.
    4. Creates a GitHub Release with auto-generated notes and attaches the .gem file.
  • If the version on main already has a tag, the workflow is a no-op.

How to ship a new version

  1. Open a PR from a feature branch.
  2. In the same PR, bump lib/factory_seeder/version.rb following semver:
    • patch (0.1.0 -> 0.1.1) - bug fix, no API change
    • minor (0.1.0 -> 0.2.0) - new feature, backward compatible
    • major (0.1.0 -> 1.0.0) - breaking change
  3. Add a corresponding entry at the top of CHANGELOG.md.
  4. Get the PR approved by a Wecasa engineer (branch protection enforces this).
  5. Merge. The release workflow tags the commit and publishes the gem within a minute or two.

Ship without releasing

If your PR does not change the public API (docs, internal refactor, CI tweak), leave version.rb untouched. The workflow will detect that the version already has a tag and skip the release step. No manual step needed.

Gem ownership

The gem is owned by the devops@wecasa.fr RubyGems service account (MFA enabled). Human maintainers are added as co-owners via:

gem owner factory_seeder -a <email>

Trusted Publishing is configured at https://rubygems.org/profile/oidc/pending_trusted_publishers (or the gem page once a version is published) with:

  • Repository: wecasa/factory_seeder
  • Workflow filename: release.yml
  • Environment: (empty)

Architecture

Core Components

  • FactoryScanner: Automatically detects and analyzes FactoryBot factories
  • SeedGenerator: Creates database records using factories and traits
  • SeedManager: Manages custom seed definitions with parameter validation
  • Seed & SeedBuilder: Define reusable seeds with type-safe parameters
  • CLI: Command-line interface with interactive prompts
  • WebInterface: Sinatra-based web UI for visual seeding
  • Engine: Rails Engine for seamless Rails integration
  • Configuration: Flexible configuration system

Dependencies

  • Ruby: >= 2.7.0
  • FactoryBot: ~> 6.0
  • ActiveSupport: >= 6.0
  • Thor: ~> 1.0 (CLI)
  • Sinatra: ~> 2.0 (Web interface)
  • Faker: ~> 3.0 (Test data generation)
  • Zeitwerk: ~> 2.6 (Autoloading)

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

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

Support

Acknowledgments


Made with ❤️ by Wecasa