trailblazer/reform
Form objects decoupled from models.
Healthy across all four use cases
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 9mo ago
- ✓11 active contributors
- ✓Distributed ownership (top contributor 33% of recent commits)
Show 4 more →Show less
- ✓MIT licensed
- ✓CI configured
- ✓Tests present
- ⚠Slowing — last commit 9mo ago
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/trailblazer/reform)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/trailblazer/reform on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: trailblazer/reform
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/trailblazer/reform shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- Last commit 9mo ago
- 11 active contributors
- Distributed ownership (top contributor 33% of recent commits)
- MIT licensed
- CI configured
- Tests present
- ⚠ Slowing — last commit 9mo ago
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live trailblazer/reform
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/trailblazer/reform.
What it runs against: a local clone of trailblazer/reform — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in trailblazer/reform | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 288 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of trailblazer/reform. If you don't
# have one yet, run these first:
#
# git clone https://github.com/trailblazer/reform.git
# cd reform
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of trailblazer/reform and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "trailblazer/reform(\\.git)?\\b" \\
&& ok "origin remote is trailblazer/reform" \\
|| miss "origin remote is not trailblazer/reform (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "lib/reform.rb" \\
&& ok "lib/reform.rb" \\
|| miss "missing critical file: lib/reform.rb"
test -f "lib/reform/form.rb" \\
&& ok "lib/reform/form.rb" \\
|| miss "missing critical file: lib/reform/form.rb"
test -f "lib/reform/contract.rb" \\
&& ok "lib/reform/contract.rb" \\
|| miss "missing critical file: lib/reform/contract.rb"
test -f "lib/reform/form/validate.rb" \\
&& ok "lib/reform/form/validate.rb" \\
|| miss "missing critical file: lib/reform/form/validate.rb"
test -f "lib/reform/validation.rb" \\
&& ok "lib/reform/validation.rb" \\
|| miss "missing critical file: lib/reform/validation.rb"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 288 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~258d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/trailblazer/reform"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Reform is a Ruby gem that provides form objects completely decoupled from Active Record models, enabling validations and nested model composition without coupling business logic to persistence layers. It allows developers to define forms with properties, validations, and coercion independently of how data is stored, and handles synchronization back to models only when explicitly requested. Single-gem architecture: lib/reform.rb is the entry point, with core form abstraction in lib/reform/form.rb and validation delegation in lib/reform/contract.rb. Features are modular: lib/reform/form/ contains composition, coercion, populator, and prepopulate logic; lib/reform/validation/ handles validation groups. Tests mirror structure in test/ with integration tests and fixtures.
👥Who it's for
Rails developers and Ruby framework users who need to separate form validation/transformation logic from model persistence, particularly when building multi-model forms, API backends, or systems where model and form requirements diverge. Developers maintaining legacy applications that want to add validation layers without modifying core models.
🌱Maturity & risk
Production-ready and actively maintained. The project has comprehensive test coverage across test/ directory with CI pipelines for multiple Ruby implementations (MRI, JRuby, TruffleRuby) in .github/workflows/, a stable 2.2+ versioning scheme in lib/reform/version.rb, and is part of the established Trailblazer ecosystem. Recent updates indicated in CHANGES.md show ongoing development.
Low risk for current users, but watch for: Reform 2.2 removed automatic Rails file loading, requiring the separate reform-rails gem (see README), which adds a dependency chain. Single primary maintainer model typical of Trailblazer gems. Breaking changes between major versions (e.g., 1.x to 2.x) are documented but require migration. No high-CVE history visible.
Active areas of work
Active maintenance on validation improvements, dry-validation integration (lib/reform/form/dry/), and test coverage expansion. The project supports multiple Ruby versions via CI workflows. No visible active PRs in repo data, but CHANGES.md and TODO.md indicate ongoing refinements to composition, error handling, and validation group features.
🚀Get running
git clone https://github.com/trailblazer/reform.git
cd reform
bundle install
bundle exec rake test
Daily commands:
bundle exec rake test # Run full test suite
bundle exec rake test:unit # Run unit tests only
bundle exec rake benchmarking # Run benchmarks (see test/benchmarking.rb)
🗺️Map of the codebase
lib/reform.rb— Main entry point and module loader that initializes Reform and exposes the core API.lib/reform/form.rb— Core Form class that implements the form object pattern with validation and field mapping.lib/reform/contract.rb— Contract layer that decouples validation logic from models and provides composition support.lib/reform/form/validate.rb— Validation pipeline that handles dry-validation integration and error reporting.lib/reform/validation.rb— Validation abstraction layer supporting multiple validation backends (dry-validation, custom validators).lib/reform/form/composition.rb— Enables composing multiple domain models into a single form object for complex nested structures.lib/reform/form/populator.rb— Handles nested object population and initialization when creating/updating composed forms.
🧩Components & responsibilities
- Form class (lib/reform — undefined
🛠️How to make changes
Add a New Validated Form with Nested Objects
- Define form class inheriting from Reform::Form in your application (
lib/reform/form.rb) - Add properties using the property DSL (e.g., property :name, validates: { presence: true }) (
lib/reform/form.rb) - For nested objects, use collection :items or property :author with populator blocks (
lib/reform/form/composition.rb) - Define validation rules using dry-validation contract syntax in the form class (
lib/reform/validation.rb) - Test with Form#validate(input_hash) and access Form#errors for error messages (
test/validate_test.rb)
Implement Custom Validation or Error Handling
- Create custom validator or override validation method in your Form class (
lib/reform/contract/validate.rb) - Use dry-validation contract blocks or define validation rules with custom predicates (
lib/reform/validation.rb) - Access and customize errors through errors object after validate call (
lib/reform/errors.rb) - Add custom error messages by extending CustomError or using I18n with dry-validation (
lib/reform/contract/custom_error.rb)
Add Type Coercion or Input Transformation
- In your Form class, declare properties with type and optional coercion (
lib/reform/form/coercion.rb) - Define custom deserializers or use :from option to transform incoming data (
lib/reform/form.rb) - For complex transformations, use parse or :deserialize blocks in property definitions (
lib/reform/form/coercion.rb) - Test coercion with Form#validate and verify Form#model_name or property access (
test/coercion_test.rb)
Create Multi-Model Composed Form
- Define Form class and use properties map method to specify which model each property belongs to (
lib/reform/form/composition.rb) - Pass multiple model instances to Form.new(user: user_obj, profile: profile_obj) (
lib/reform/form/composition.rb) - Use populator blocks for nested object creation or retrieval logic (
lib/reform/form/populator.rb) - On Form#save, call sync to persist changes back to all composed models (
lib/reform/form.rb)
🔧Why these technologies
- dry-validation — Framework-agnostic, composable validation schemas with predicates and error handling that decouple validation from persistence
- Ruby module system (prepend/include) — Enables flexible form composition, mixins for adding features, and method override chains without inheritance overhead
- Block-based DSL (property, validates, populator) — Declarative, readable form definitions that avoid class-level boilerplate while maintaining Ruby idioms
⚖️Trade-offs already made
-
Form objects separate from models entirely
- Why: Enables reusable forms across different models, independent validation logic, and single-responsibility per object
- Consequence: Developers must manually map form fields to model attributes and implement save logic, increasing code verbosity for simple cases
-
Composition via multiple models instead of inheritance
- Why: Supports has_many/has_one relationships without forcing a single root model, enabling cross-domain forms
- Consequence: More complex configuration and potential for inconsistent state if synchronization between composed objects fails
-
Optional Rails integration; framework-agnostic core
- Why: Maximizes reusability across Sinatra, Hanami, or plain Ruby, and reduces tight coupling to Rails conventions
- Consequence: Rails developers lose automatic model hooks (before_save, after_save) and must explicitly integrate form save lifecycle
-
Type coercion via deserializers rather than implicit casting
- Why: Explicit, testable, and doesn't mask programming errors or surprise users with silent type conversions
- Consequence: Requires more upfront definition of coercion logic; less automagic for rapid prototyping
🚫Non-goals (don't propose these)
- Does not manage database persistence directly; sync/save logic is application-defined
- Does not provide automatic Rails model integration or observer pattern hooks
- Does not handle authentication or authorization
- Does not support real-time collaborative form editing
- Does not implement ORM-level dirty tracking; form tracks changes independently
🪤Traps & gotchas
- Reform 2.2+ requires
reform-railsgem for Rails-specific features; adding it to Gemfile is non-obvious if upgrading from 2.1. 2. The #sync method writes only via setter methods on models; if your model doesn't expose a setter (readonly property), sync will silently skip it. 3. Nested forms via composition require explicit populator lambdas inpropertydeclarations; without them, nested models won't auto-populate. 4. Validation errors are keyed by form property names, not model attribute names, which can confuse developers expecting ActiveModel error format. 5. The form does not auto-reload model state after sync; if model has computed fields or defaults, they won't reflect in the form unless explicitly repopulated.
🏗️Architecture
💡Concepts to learn
- Form-Model Separation — Reform's core philosophy: forms and models have different responsibilities (validation/transformation vs. persistence); understanding this decoupling prevents anti-pattern of validating at the model layer
- Property Coercion — Forms accept raw user input (strings) but need typed values; lib/reform/form/coercion.rb handles type casting before validation, a critical pattern for API forms
- Composition Pattern — Reform combines properties from multiple domain objects into a single form interface via lib/reform/form/composition.rb; essential for has_one/has_many UX without fat models
- Validation Groups — Context-dependent validation (create vs. update flows) via lib/reform/validation/groups.rb; allows one form class to enforce different rules based on operation type without branching
- Populator Callbacks — Custom lambdas in lib/reform/form/populator.rb control how nested models are instantiated from form input; critical for implementing association creation in nested forms
- Sync vs. Save — Reform separates data synchronization (#sync writes to models only) from persistence (#save calls model.save); allows forms to work with any persistence backend or none at all
- Contract Wrapping — Reform wraps lib/reform/contract.rb (validation abstraction) around forms; understanding this allows extending validation behavior without monkeypatching or inheritance
🔗Related repos
trailblazer/reform-rails— Official Rails integration gem required for Rails 5+ support; handles ActiveModel::Validations and Rails form helpers integrationtrailblazer/trailblazer— Parent framework that Reform integrates with; provides Operation/Contract pattern and endpoint/policy layers above formsdry-rb/dry-validation— Validation backend alternative to inline rules; Reform supports it via lib/reform/form/dry.rb for schema-based validationrails/rails— Optional host framework; Reform is framework-agnostic but commonly used in Rails apps via reform-rails gem
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive tests for lib/reform/form/dry.rb and dry validation integration
The repo has test/validation/dry_validation_test.rb but there's a gap in coverage for lib/reform/form/dry.rb and lib/reform/form/dry/input_hash.rb. These are core dry-validation integration files but lack dedicated unit tests. This would improve reliability of the dry-rb backend and help catch regressions.
- [ ] Review lib/reform/form/dry.rb to understand the dry validation setup logic
- [ ] Review lib/reform/form/dry/input_hash.rb for input coercion handling
- [ ] Create test/form/dry_test.rb with unit tests for dry-specific form behavior
- [ ] Add tests for input_hash transformation edge cases
- [ ] Ensure tests cover interaction between dry validation and form coercion (lib/reform/form/coercion.rb)
Add tests for lib/reform/form/composition.rb composition feature
The file structure shows lib/reform/form/composition.rb exists, and test/composition_test.rb covers basic scenarios. However, looking at the test names, there's likely missing coverage for edge cases like nested compositions, error handling, and composition with different validation backends. This is a documented feature that deserves comprehensive testing.
- [ ] Audit test/composition_test.rb to identify untested scenarios
- [ ] Add tests for multi-level nested compositions
- [ ] Add tests for composition with dry-validation backend
- [ ] Add tests for error propagation in composed forms
- [ ] Add tests for populator/prepopulator behavior in compositions
Add GitHub Actions workflow for Ruby 3.2+ and JRuby edge case testing
The repo has .github/workflows/ci.yml, ci_jruby.yml, ci_legacy.yml, and ci_truffleruby.yml, but no explicit workflow for newer Ruby versions (3.2, 3.3, head). Given Rails and Ruby ecosystem changes, this ensures Reform remains compatible with bleeding-edge versions and catches deprecation warnings early.
- [ ] Review .github/workflows/ci.yml to understand current Ruby version matrix
- [ ] Create .github/workflows/ci_ruby_head.yml for Ruby 3.2, 3.3, and ruby-head
- [ ] Configure workflow to allow soft failures on ruby-head to catch future incompatibilities
- [ ] Add matrix testing for both standard and dry-validation validation backends
- [ ] Document in CONTRIBUTING.md which Ruby versions are actively tested
🌿Good first issues
- Add comprehensive test coverage for
lib/reform/form/dry/input_hash.rb—the file exists but has minimal test representation intest/docs/validation_test.rb; write integration tests for dry-validation with various input types. - Document and test the interaction between
lib/reform/form/populator.rbandlib/reform/form/prepopulate.rb—currently there's no test showing both features used together in nested compositions; add a test case totest/composition_test.rb. - Implement missing validation group test cases in
test/docs/validation_test.rbfor the 'create' vs. 'update' pattern; currently group validation is underdocumented relative to its importance.
⭐Top contributors
Click to expand
Top contributors
- @emaglio — 33 commits
- @seuros — 19 commits
- @apotonick — 13 commits
- @fran-worley — 12 commits
- [@Alfonso Uceda](https://github.com/Alfonso Uceda) — 9 commits
📝Recent commits
Click to expand
Recent commits
94b0916— debug gem is only available for ruby engine (Alfonso Uceda)1020e66— Custom error merge! is corrected (Alfonso Uceda)115a403— Specs are corrected (Alfonso Uceda)6b23dc0— Debug gem is added (Alfonso Uceda)fd65596— Dry-validation version is changed to ~> 1.5 (Alfonso Uceda)9ebe4f2— MiniTest has been changed to Minitest (Alfonso Uceda)f311809— Ruby 2.7 moved to legacy CI (Alfonso Uceda)3c73a23— actions/checkout updated to v5 (Alfonso Uceda)2c03cb6— Ruby 3.3 and 3.4 added to CI (Alfonso Uceda)ab73e18— Managed by Terraform (seuros)
🔒Security observations
Reform is a form validation library with a generally sound architectural approach. The main security concerns are: (1) inability to audit dependencies without the actual Gemfile/gemspec contents, (2) potential inconsistencies in validation logic across multiple validation handlers, and (3) data serialization/deserialization patterns that require careful review. The codebase appears well-structured for security with separation of concerns in form handling, validation, and contract enforcement. No obvious hardcoded secrets, SQL injection vectors, or XSS vulnerabilities detected in the file structure. Recommendation: Perform dependency audits, comprehensive code review of validation logic, and add formal security documentation.
- Medium · Missing Dependency File for Vulnerability Analysis —
Gemfile, reform.gemspec. The Gemfile and gemspec files are referenced in the file structure but their contents were not provided. This prevents comprehensive analysis of third-party dependencies for known vulnerabilities. Reform depends on external validation libraries (dry-validation, etc.) which should be regularly audited. Fix: Provide dependency files and regularly run 'bundle audit' or 'bundler-audit' to check for known vulnerabilities in gems. Keep dependencies updated to their latest secure versions. - Low · Potential Input Validation Coverage Gap —
lib/reform/contract/validate.rb, lib/reform/form/validate.rb, lib/reform/validation.rb. The codebase implements form validation through Reform, but the structure suggests validation is delegated to multiple validation libraries (dry-validation, custom validators). Without seeing the actual validation implementations, there's a risk of inconsistent validation logic or bypass opportunities. Fix: Ensure all user inputs are consistently validated. Review validation logic in validate.rb files to confirm proper sanitization. Use allowlist-based validation rather than blocklist approaches. Test validation edge cases. - Low · Potential Data Serialization Risks —
lib/reform/form/populator.rb, lib/reform/form/prepopulate.rb. The codebase handles form composition and populator logic which may deserialize or manipulate user-provided data. Files like lib/reform/form/populator.rb and lib/reform/form/deserialize_test.rb suggest data transformation operations that could be vulnerable to object injection if not properly handled. Fix: Ensure safe deserialization practices. Avoid using Marshal.load() or YAML.load() on untrusted input. Use safe deserialization methods like JSON.parse. Validate and sanitize all data transformations. - Low · Missing Security Configuration Documentation —
Repository root. No explicit security configuration files, security.md, or security policy documentation found in the repository structure. This makes it unclear what security best practices are recommended for users of this library. Fix: Create a SECURITY.md file documenting security best practices for Reform users, vulnerability reporting procedures, and known limitations. Include guidance on proper input validation and secure form handling.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.