Project

vial

0.0
No release in over 3 years
Vial compiles programmable fixture intent into explicit, deterministic fixtures for Rails.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

>= 0

Runtime

 Project Readme

Vial: Fixtures, Reinvented

Vial is a Ruby gem for Rails 8.1+ on Ruby 3.4+ that compiles programmable fixture intent into explicit, deterministic fixture files.

Requirements

  • Ruby 3.4+ (Ruby Box optional via RUBY_BOX=1)
  • Rails 8.1+

Support Policy

  • Ruby 3.4+ only
  • Rails 8.1+ only
  • No backward compatibility guarantees for older Ruby/Rails

Features

  • Vial Compiler: Ruby DSL input → explicit YAML fixtures output
  • Fixture Discovery: Automatically finds all YAML fixtures across configured paths
  • Model Mapping Analysis: Determines which models fixtures belong to using Rails conventions
  • Validation: Identifies orphaned fixtures that don't have corresponding models
  • Multiple Detection Methods: Supports all Rails fixture-to-model mapping methods

Installation

Add this gem to your Gemfile in the development/test group:

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

Then run:

bundle install

Usage

Compile Vial Sources

Vial reads *.vial.rb files from test/vials and writes YAML fixtures to test/fixtures:

bin/rails vial:compile

If RUBY_BOX=1 is set, Vial evaluates vial sources inside Ruby::Box for isolation.

Compile only specific vials:

bin/rails vial:compile:only[users,posts]

Note: vial:compile:only still validates the full dataset to catch global ID collisions; it only limits which files are written.

Dry run (validate and show output targets without writing files):

bin/rails vial:compile:dry_run

Example test/vials/users.vial.rb:

vial :users do
  base do
    active true
    country "MA"
  end

  variant :admin do
    role "admin"
  end

  variant :guest do
    role "guest"
    active false
  end

  sequence(:email) { |i| "user#{i}@test.local" }

  generate 10, :admin
  generate 50, :guest
end

Output: test/fixtures/users.yml with deterministic IDs and explicit records.

Global Validation

vial:compile validates the entire dataset before writing any fixtures. Any collision or duplicate label fails the compile with a precise error.

Composition

vial :base_users, abstract: true do
  base do
    active true
    country "MA"
  end
end

vial :admin_users do
  include_vial :base_users do
    override :role, "admin"
  end

  generate 2
end

include_vial composes base attributes (and sequences) only; variants are not imported. Abstract Vials are compile-time only and do not emit fixture files.

ID Strategy

Derived IDs are deterministic integers based on the identity tuple:

[vial_name, record_type, label, variants[], index]

You can set a namespace range per Vial:

vial :users, id_base: 100_000, id_range: 90_000 do
  base do
    role "user"
  end

  generate 3
end

Explicit IDs are supported and never rewritten:

vial :users do
  base do
    id 1001
    role "system"
  end

  generate 1
end

Explain a derived ID:

bin/rails vial:explain_id['users.admin.eu[3]']

Determinism & Faker

Vial seeds Ruby's RNG with Vial.config.seed (default: 1). As long as you avoid non-deterministic sources (Time.now, SecureRandom, etc.), recompiles produce identical output.

If you use Faker, seed it explicitly:

Faker::Config.random = Random.new(Vial.config.seed)

Troubleshooting

  • Vial: no vial definitions found → No *.vial.rb files were found under Vial.config.source_paths.
  • Vial: no records generated → All vials are abstract or missing generate calls.
  • Fixture ID standardization skipped for ERB → ERB fixtures are not rewritten to avoid altering dynamic content.
  • Output path looks wrong → Vial.config.output_path defaults to the first ActiveSupport::TestCase.fixture_paths entry.

Configuration

Vial.configure do |config|
  config.source_paths = [Rails.root.join("test/vials")]
  config.output_path = Rails.root.join("test/fixtures")
  config.seed = 1
end

Example Vial sources live in examples/:

  • examples/users.vial.rb
  • examples/company__users.vial.rb
  • examples/base_users.vial.rb
  • examples/admin_users.vial.rb

Use erb("...") inside a Vial file to emit raw ERB (for associations or special IDs).

Vial provides several rake tasks for fixture analysis:

Count Fixtures

Get an overview of all YAML fixtures in your project:

bin/rails vial:count_fixtures

This shows:

  • Total number of fixture files
  • Distribution by directory
  • Configured fixture paths

Note: Vial.config.output_path defaults to the first configured fixture_paths when available.

Clean Stale Vial Fixtures

Remove fixture files previously generated by Vial that no longer have a matching vial definition:

bin/rails vial:clean

Vial tracks generated fixture files in test/fixtures/.vial_manifest.yml and only removes files listed there.

Analyze Fixtures

Get detailed fixture-to-model mapping information:

bin/rails vial:analyze_fixtures

This shows:

  • Mapped fixtures with their corresponding models
  • Detection method used (fixture directive, set_fixture_class, or path inference)
  • Unmapped fixtures that may need attention
  • Any errors encountered during analysis

Validate Fixtures

Check for orphaned fixtures without models:

bin/rails vial:validate_fixtures

This task:

  • Returns success (exit 0) if all fixtures are mapped
  • Returns failure (exit 1) if unmapped fixtures are found
  • Provides suggestions for fixing unmapped fixtures

Analyze Fixture IDs

Check if fixtures use proper ActiveRecord::FixtureSet.identify:

bin/rails vial:analyze_fixture_ids

This shows:

  • Primary key type distribution (UUID, integer, string, etc.)
  • Fixtures that need ID standardization
  • Current vs expected ID values

Standardize Fixture IDs

Convert hardcoded IDs to use ActiveRecord::FixtureSet.identify:

# Preview changes without applying
bin/rails vial:standardize_fixture_ids:dry_run

# Apply changes (with confirmation prompt)
bin/rails vial:standardize_fixture_ids

This will:

  • Replace hardcoded integer IDs with <%= ActiveRecord::FixtureSet.identify(:label) %>
  • Replace hardcoded UUIDs with <%= ActiveRecord::FixtureSet.identify(:label, :uuid) %>
  • Skip string primary keys (often manually managed like slugs)
  • Ensure consistent, deterministic IDs across all fixtures

How Fixture Detection Works

Vial uses three methods to determine which model a fixture belongs to:

  1. _fixture Directive: Checks for _fixture: model_class: in YAML files
  2. set_fixture_class: Uses mappings from ActiveSupport::TestCase.set_fixture_class
  3. Path Inference: Infers models from fixture paths using Rails naming conventions

Example Output

=== Vial Fixture Analysis ===

Total fixtures found: 114
Mapped to models: 93
Unmapped fixtures: 21

=== Mapped Fixtures ===
platform/countries:
  Model: Platform::Country
  Table: platform_countries
  Detection: path_inference

active_llm_llms:
  Model: ActiveLLM::LLM
  Table: active_llm_llms
  Detection: set_fixture_class

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

License

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