RepoPilotOpen in app →

aasm/aasm

AASM - State machines for Ruby classes (plain Ruby, ActiveRecord, Mongoid, NoBrainer, Dynamoid)

Healthy

Healthy across the board

Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 2w ago
  • 21+ active contributors
  • MIT licensed
Show 3 more →
  • CI configured
  • Tests present
  • Concentrated ownership — top contributor handles 64% of recent commits

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.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/aasm/aasm)](https://repopilot.app/r/aasm/aasm)

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/aasm/aasm on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: aasm/aasm

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:

  1. 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.
  2. 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.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/aasm/aasm 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 the board

  • Last commit 2w ago
  • 21+ active contributors
  • MIT licensed
  • CI configured
  • Tests present
  • ⚠ Concentrated ownership — top contributor handles 64% of recent commits

<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 aasm/aasm repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/aasm/aasm.

What it runs against: a local clone of aasm/aasm — 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 aasm/aasm | 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 ≤ 44 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>aasm/aasm</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of aasm/aasm. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/aasm/aasm.git
#   cd aasm
#
# 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 aasm/aasm and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "aasm/aasm(\\.git)?\\b" \\
  && ok "origin remote is aasm/aasm" \\
  || miss "origin remote is not aasm/aasm (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/aasm.rb" \\
  && ok "lib/aasm.rb" \\
  || miss "missing critical file: lib/aasm.rb"
test -f "lib/aasm/state_machine.rb" \\
  && ok "lib/aasm/state_machine.rb" \\
  || miss "missing critical file: lib/aasm/state_machine.rb"
test -f "lib/aasm/core/event.rb" \\
  && ok "lib/aasm/core/event.rb" \\
  || miss "missing critical file: lib/aasm/core/event.rb"
test -f "lib/aasm/core/transition.rb" \\
  && ok "lib/aasm/core/transition.rb" \\
  || miss "missing critical file: lib/aasm/core/transition.rb"
test -f "lib/aasm/persistence/base.rb" \\
  && ok "lib/aasm/persistence/base.rb" \\
  || miss "missing critical file: lib/aasm/persistence/base.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 44 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~14d)"
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/aasm/aasm"
  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).

</details>

TL;DR

AASM is a Ruby gem that adds finite state machine (FSM) capabilities to any Ruby class, enabling declarative definition of states, transitions, and lifecycle callbacks. It provides adapters for ActiveRecord, Mongoid, Dynamoid, NoBrainer, and plain Ruby objects, allowing developers to model complex workflows (e.g., order processing, user onboarding) with guards, callbacks, and bang events for state transitions. Monolithic gem with core FSM engine in lib/aasm/core/ (state.rb, event.rb, transition.rb, invoker.rb), ORM adapters in lib/aasm/ (implicit via require strategy), and testing helpers in lib/aasm/minitest/. State machine DSL defined in lib/aasm/aasm.rb and instance bindings in lib/aasm/instance_base.rb. Gemfiles/ directory isolates Rails version compatibility testing.

👥Who it's for

Ruby on Rails developers and Ruby library authors who need to model stateful domain objects (orders, users, jobs) with explicit state transitions, validation guards, and side-effect callbacks. Contributors typically work on ORMs (ActiveRecord, Mongoid) or need FSM support across multiple persistence layers.

🌱Maturity & risk

Production-ready and actively maintained. The gem has substantial version history (v3→v4 migration documented), comprehensive test coverage across Rails 6.0–8.0 and multiple ORMs, CI/CD via GitHub Actions, and clear changelog. It's a well-established library with regular updates (Gemfiles for multiple Rails versions present).

Low risk for established Rails projects; moderate risk if integrating with bleeding-edge ORMs. Single gem author suggests potential maintainability bottleneck (check GitHub releases for commit recency). Heavy reliance on ORM-specific adapters means breaking changes in Mongoid or Dynamoid APIs could ripple through. No visible security audit documentation.

Active areas of work

Active maintenance with Rails 8.0 support added (gemfiles/rails_8.0.gemfile present). Recent work includes version 3→4 migration guides (README_FROM_VERSION_3_TO_4.md) and expanded testing infrastructure. Multiple ORM adapters being maintained. PLANNED_CHANGES.md suggests feature roadmap exists but specific PRs/milestones not visible from file list.

🚀Get running

git clone https://github.com/aasm/aasm.git
cd aasm
bundle install
bundle exec rake

Run tests via bundle exec rspec (default) or bundle exec minitest for the minitest suite. Test against specific Rails versions using bundle exec appraisal GEMFILE_NAME install && bundle exec appraisal GEMFILE_NAME rake.

Daily commands: No server to start. This is a library gem. Install via bundle install and require aasm in your Ruby code. For development: bundle exec rake runs the full test suite; bundle exec guard watches for changes (if Guard installed). Docker testing: docker-compose up runs all tests across Rails versions.

🗺️Map of the codebase

  • lib/aasm.rb — Entry point that requires and integrates all core AASM modules; essential for understanding how the gem is loaded and initialized.
  • lib/aasm/state_machine.rb — Core state machine definition and DSL handler; defines how states, events, and transitions are declared and managed.
  • lib/aasm/core/event.rb — Event abstraction layer that triggers state transitions and manages callbacks; critical for understanding how state changes occur.
  • lib/aasm/core/transition.rb — Transition logic including guards, callbacks, and state movement; handles the core business logic of moving between states.
  • lib/aasm/persistence/base.rb — Abstract base for persistence strategies across different ORMs; required reading to understand how state is persisted across frameworks.
  • lib/aasm/instance_base.rb — Instance-level state machine behavior and runtime state management; bridges DSL definitions with runtime instance execution.
  • lib/aasm/core/invoker.rb — Callback invocation abstraction that handles different callback types (procs, methods, class methods); central to understanding callback execution.

🛠️How to make changes

Add a New State Machine to a Class

  1. Include AASM in your class by adding include AASM at the top (lib/aasm.rb)
  2. Define the state machine using the aasm DSL block, declaring states and initial state (lib/aasm/state_machine.rb)
  3. Add event blocks within the state machine definition using event :event_name syntax (lib/aasm/core/event.rb)
  4. Define transitions using transitions from: :state1, to: :state2 with optional guards and callbacks (lib/aasm/core/transition.rb)
  5. For persistence, choose the appropriate adapter in lib/aasm/persistence/ (e.g., active_record_persistence.rb for Rails) (lib/aasm/persistence/orm.rb)

Add Guard Logic to a Transition

  1. Within a transition definition, add guard: :method_name or guard: proc { |obj| ... } to conditionally allow state changes (lib/aasm/core/transition.rb)
  2. Define the guard method on your class that returns true/false (lib/aasm/instance_base.rb)
  3. Guards are evaluated by invokers; use ProcInvoker for lambdas/procs or method name strings (lib/aasm/core/invoker.rb)

Add Lifecycle Callbacks to State Changes

  1. Within a transition, use before:, after:, on_transition: to specify callbacks (lib/aasm/core/transition.rb)
  2. Callbacks can reference method names (strings), procs, or class methods; they're executed via invoker classes (lib/aasm/core/invoker.rb)
  3. For state-level callbacks, use before_enter:, after_enter:, before_exit:, after_exit: in state definitions (lib/aasm/core/state.rb)
  4. Invoker classes in lib/aasm/core/invokers/ handle execution; extend or add new invoker types as needed (lib/aasm/core/invokers/base_invoker.rb)

Support a New ORM/Persistence Backend

  1. Create a new persistence adapter in lib/aasm/persistence/ inheriting from base.rb (lib/aasm/persistence/base.rb)
  2. Implement required methods: read_state, write_state, and supports_requires_new? (lib/aasm/persistence/active_record_persistence.rb)
  3. Register your adapter in the persistence factory by adding a case condition in lib/aasm/persistence/orm.rb (lib/aasm/persistence/orm.rb)
  4. Test via plain Ruby classes or model specs using your ORM adapter (spec/models)

🔧Why these technologies

  • Plain Ruby with mixin pattern (AASM module) — Allows state machines to be added to any Ruby class without inheritance, maximizing flexibility and reusability across frameworks
  • Multiple ORM adapters (ActiveRecord, Mongoid, Dynamoid, etc.) — AASM decouples state machine logic from persistence, supporting diverse backends through a common interface
  • DSL via block evaluation (state_machine.rb) — Provides a readable, declarative syntax for defining states, events, and transitions that feels natural in Ruby
  • Invoker pattern for callbacks (base_invoker, proc_invoker, etc.) — Abstracts different callback types (procs, method names, class methods) into a common invocation interface for consistency and extensibility
  • RSpec and Minitest matchers — Provides first-class testing support allowing developers to write state machine assertions idiomatically in their test framework of choice

⚖️Trade-offs already made

  • Single state machine vs. multiple state machines per class

    • Why: Supporting multiple state machines adds complexity but is required for models with independent state concepts
    • Consequence: Requires namespacing methods (e.g., aasm(:workflow)) and careful state machine store management to avoid conflicts
  • Guard evaluation before or after transition setup

    • Why:
    • Consequence: undefined

🪤Traps & gotchas

  1. Multiple state machines per class: Requires naming (aasm(:column_name)) to avoid conflicts; see README section. 2. Callback execution context: Callbacks run in instance context but guard procs receive no args by default—check lib/aasm/core/transition.rb for invoke patterns. 3. ORM persistence: State changes don't auto-save in AR; bang events (e.g., submit!) required for persistence; absence of bang events means no DB write. 4. Enum storage: ActiveRecord enums vs. string columns behave differently; config in lib/aasm/configuration.rb. 5. Rails version compatibility: Must match Gemfiles/* for testing; newer Rails versions may need custom adapters. 6. Transaction handling: See CHANGELOG and spec/models/active_record* for transaction/locking gotchas with pessimistic locking.

🏗️Architecture

💡Concepts to learn

  • Finite State Machine (FSM) — Core concept of AASM; understanding state diagrams, transitions, and guards is essential to use the library effectively
  • Guard conditions (predicates) — AASM uses guards to conditionally allow or deny transitions; they're callbacks that return boolean and must be understood for complex workflows
  • Lifecycle callbacks (hooks) — before_enter, after_enter, before_exit, on_transition callbacks are how AASM integrates side effects; critical for ORM persistence and audit logging
  • Polymorphic dispatch (invoker pattern) — AASM's invoker system (BaseInvoker, ClassInvoker, ProcInvoker, LiteralInvoker) shows how callbacks are routed; understanding this is key to modifying callback behavior
  • ORM adapter pattern — AASM supports multiple ORMs (AR, Mongoid, Dynamoid) via adapter strategy; understanding how state is persisted differently per ORM is crucial for cross-DB support
  • DSL (Domain-Specific Language) — AASM's aasm { states :a, :b; event(:go) { transitions from: :a, to: :b } } syntax is a Ruby DSL; understanding eval-based DSL construction helps modify or extend the grammar
  • Pessimistic locking (database-level) — AASM supports pessimistic locking for AR to prevent race conditions on concurrent state changes; covered in README and visible in transition invoke logic
  • statesman/statesman — Alternative Ruby state machine gem with focus on immutable state history and Rails integration; direct competitor
  • workflow/workflow — Older Ruby FSM library; predecessor design pattern that influenced AASM's DSL-based approach
  • rails/rails — Primary ecosystem dependency; AASM adapts to ActiveRecord API changes and uses its callback hooks
  • mongodb/mongoid — Supported ORM adapter; AASM must track Mongoid's persistence API to maintain MongoDB integration
  • Dynamoid/dynamoid — DynamoDB adapter for AASM; required for AWS DynamoDB state persistence support in the 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 test coverage for Redis persistence layer

The repository includes lib/aasm/persistence/redis_persistence.rb but there's no evidence of dedicated test suite for Redis persistence in the test structure. Given that AASM supports multiple ORMs (ActiveRecord, Mongoid, Dynamoid, NoBrainer, Sequel), Redis persistence deserves parity in test coverage. This is a concrete gap since other persistence adapters clearly have tests but Redis is missing explicit coverage.

  • [ ] Create spec/persistence/redis_persistence_spec.rb following the pattern of existing persistence tests
  • [ ] Add tests for state persistence, retrieval, and transitions in Redis backend
  • [ ] Test edge cases like TTL expiration, missing keys, and concurrent access
  • [ ] Verify integration with the Redis client initialization and configuration
  • [ ] Add Redis service to docker-compose.yml if not already present for test environment

Add Sequel ORM persistence tests and CI matrix coverage

The codebase includes lib/aasm/persistence/sequel_persistence.rb but Sequel is notably absent from the gemfiles/ directory (which contains Rails 6.0-8.0 appraisals). Sequel is a mature Ruby ORM that deserves first-class support. Adding a Sequel gemfile and CI workflow will ensure the persistence layer works reliably and prevent future regressions.

  • [ ] Create gemfiles/sequel.gemfile with appropriate Sequel version and dependencies
  • [ ] Add Sequel ORM to .github/workflows/build.yml matrix testing
  • [ ] Create or enhance spec/persistence/sequel_persistence_spec.rb with comprehensive tests
  • [ ] Verify transition callbacks, guards, and state persistence work correctly with Sequel
  • [ ] Update README.md to document Sequel as an officially supported ORM

Implement missing RSpec/Minitest matchers for event guards and callbacks

The repository has well-structured RSpec and Minitest helper modules (lib/aasm/rspec/ and lib/aasm/minitest/) with matchers like allow_event, allow_transition_to, and have_state. However, there are no matchers for verifying guard conditions (guards that should prevent transitions) or callback execution. This is a concrete feature gap that would improve test expressiveness for state machine validation.

  • [ ] Create lib/aasm/rspec/have_guard.rb to test that guards are properly defined on transitions
  • [ ] Create lib/aasm/minitest/have_guard.rb with equivalent Minitest implementation
  • [ ] Add matchers to verify callback hooks (before_enter, after_exit, etc.) are defined
  • [ ] Add comprehensive specs in spec/unit/rspec_matchers_spec.rb and spec/unit/minitest_matchers_spec.rb
  • [ ] Document new matchers in the API documentation or HOWTO guide

🌿Good first issues

  • Add missing AASM state machine examples to spec/models/ for a newer ORM (e.g., Sequel full integration test with all callback types) or fill gaps in Dynamoid/NoBrainer coverage
  • Extend lib/aasm/minitest/ with new assertion helper (e.g., assert_transition_with_guards to test guard conditions) and corresponding spec tests, since only 4 helpers exist currently
  • Document callback execution order and context in HOWTO or README with a concrete example (e.g., when does before_exit run relative to on_enter?) since subtle edge cases exist in lib/aasm/core/transition.rb invoke logic

Top contributors

Click to expand

📝Recent commits

Click to expand
  • c517362 — Merge pull request #882 from aasm/fix/minitest_compatibility (alto)
  • 0a07b1f — Use Minitest v5 for now (alto)
  • 404fa98 — Fix Minitest compatibility (alto)
  • 726a578 — Version bump to 5.5.2 (alto)
  • 3c10a03 — Merge pull request #873 from segiddins/segiddins/proc-invoker-kwargs-support (alto)
  • 35e060c — Add keyword arguments support to ProcInvoker (segiddins)
  • 18de75f — Correctly use the twiddle-wakka (alto)
  • 7d09045 — Update changelog for https://github.com/aasm/aasm/pull/852 (alto)
  • f63d93e — Merge pull request #852 from allcentury/rails-tests-upgrades (alto)
  • 0b40827 — Update changelog for https://github.com/aasm/aasm/pull/759 (alto)

🔒Security observations

  • High · Outdated Ruby Version in Dockerfile — Dockerfile (line 1: FROM ruby:2.3.4-slim). The Dockerfile uses Ruby 2.3.4, which reached end-of-life on March 21, 2019. This version no longer receives security patches and contains known vulnerabilities. Ruby 2.3.4 is extremely outdated and poses significant security risks. Fix: Update to a currently supported Ruby version (3.2+). Use 'FROM ruby:3.2-slim' or newer. Ensure compatibility with the application and dependencies.
  • High · Missing Security Headers and Hardening in Docker — Dockerfile. The Dockerfile does not implement security best practices such as: running as non-root user, using multi-stage builds to minimize image size, or implementing USER directive to drop privileges. The container runs as root by default. Fix: Add a non-root user (e.g., 'RUN useradd -m aasm' and 'USER aasm'), use multi-stage builds, minimize layers, and consider using Alpine Linux for a smaller attack surface.
  • Medium · Exposed Service Ports in Docker Compose — docker-compose.yml (services: mongo, redis, rethinkdb, dynamodb). The docker-compose.yml configuration exposes multiple service ports (MongoDB 27017, Redis 6379, RethinkDB 28015, DynamoDB 8000) without explicit port bindings or network isolation. These could potentially be accessible from outside the container network in misconfigured environments. Fix: Use internal Docker networks (create a dedicated network and remove port exposure), or explicitly bind ports only to localhost (127.0.0.1:PORT:PORT). Implement network policies and firewall rules.
  • Medium · Unbounded Bundle Installation — Dockerfile (RUN bundle install). The Dockerfile uses 'RUN bundle install' without specifying a lockfile version or using '--frozen' flag. This allows Bundler to resolve dependencies at build time, potentially introducing vulnerable dependency versions if Gemfile.lock is not properly maintained. Fix: Use 'RUN bundle install --frozen' to ensure dependencies match Gemfile.lock exactly. Ensure Gemfile.lock is committed to version control and regularly audited with 'bundle audit'.
  • Medium · Missing Dependency Audit Information — Gemfile, Gemfile.lock (not provided for analysis). No Gemfile or Gemfile.lock content was provided for analysis. The presence of a 'Gemfile.lock_old' suggests version control issues. Dependencies cannot be verified for known vulnerabilities. Fix: Run 'bundle audit' regularly to check for vulnerable gems. Implement automated dependency scanning in CI/CD pipeline using tools like Dependabot or Bundler Audit.
  • Medium · Insufficient Input Validation Framework — lib/aasm/core/invoker.rb, lib/aasm/core/transition.rb, lib/aasm/persistence/. The AASM state machine library deals with state transitions and event invocations. Potential for improper validation of state machine inputs if users don't validate transition parameters, especially with database persistence layers (ActiveRecord, Mongoid, etc.). Fix: Document and enforce input validation requirements for custom callbacks and guards. Ensure all user-supplied data is validated before state transitions. Use allowlist validation for state values.
  • Low · Multiple ORM Integrations Increase Attack Surface — lib/aasm/persistence/ (multiple implementations). The library supports multiple persistence backends (ActiveRecord, Mongoid, NoBrainer, Dynamoid, Sequel, Redis, CoreDataQuery). Each integration represents a potential attack surface if any ORM-specific vulnerabilities exist or are misused. Fix: Only enable and use required persistence adapters. Review security practices specific to each ORM being used. Test and validate the persistence layer configuration against injection attacks.
  • Low · Development Dependencies in Production Image — Dockerfile (RUN apt-get install -y libsqlite3-dev build-essential git). The Dockerfile includes build tools (build-essential, git) and development utilities in the final image. These are not needed at runtime and increase the attack surface and image size. Fix: Use multi-stage Docker builds to exclude development dependencies from the final image

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · aasm/aasm — RepoPilot