RepoPilotOpen in app →

pluginaweek/state_machine

Adds support for creating state machines for attributes on any Ruby class

Mixed

Stale — last commit 2y ago

worst of 4 axes
Use as dependencyMixed

last commit was 2y ago; top contributor handles 91% of recent commits

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.

  • 10 active contributors
  • MIT licensed
  • CI configured
Show 3 more →
  • Tests present
  • Stale — last commit 2y ago
  • Single-maintainer risk — top contributor 91% of recent commits
What would change the summary?
  • Use as dependency MixedHealthy if: 1 commit in the last 365 days

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 "Forkable" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Forkable
[![RepoPilot: Forkable](https://repopilot.app/api/badge/pluginaweek/state_machine?axis=fork)](https://repopilot.app/r/pluginaweek/state_machine)

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

Onboarding doc

Onboarding: pluginaweek/state_machine

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/pluginaweek/state_machine 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

WAIT — Stale — last commit 2y ago

  • 10 active contributors
  • MIT licensed
  • CI configured
  • Tests present
  • ⚠ Stale — last commit 2y ago
  • ⚠ Single-maintainer risk — top contributor 91% 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 pluginaweek/state_machine repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/pluginaweek/state_machine.

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

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

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "pluginaweek/state_machine(\\.git)?\\b" \\
  && ok "origin remote is pluginaweek/state_machine" \\
  || miss "origin remote is not pluginaweek/state_machine (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/state_machine.rb" \\
  && ok "lib/state_machine.rb" \\
  || miss "missing critical file: lib/state_machine.rb"
test -f "lib/state_machine/machine.rb" \\
  && ok "lib/state_machine/machine.rb" \\
  || miss "missing critical file: lib/state_machine/machine.rb"
test -f "lib/state_machine/state.rb" \\
  && ok "lib/state_machine/state.rb" \\
  || miss "missing critical file: lib/state_machine/state.rb"
test -f "lib/state_machine/transition.rb" \\
  && ok "lib/state_machine/transition.rb" \\
  || miss "missing critical file: lib/state_machine/transition.rb"
test -f "lib/state_machine/integrations" \\
  && ok "lib/state_machine/integrations" \\
  || miss "missing critical file: lib/state_machine/integrations"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 819 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~789d)"
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/pluginaweek/state_machine"
  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

state_machine is a Ruby gem that adds declarative state machine support to any Ruby class, enabling clean management of object state via states, events, transitions, and callbacks. It eliminates the need for scattered boolean flags by providing a DSL to define state flows (e.g., parked → ignited → idling → driving) with before/after/around hooks, conditional transitions, and integration with ORMs like ActiveRecord, DataMapper, Mongoid, and Sequel. Monolithic gem structure: lib/ contains the core state_machine DSL engine and ORM integrations (ActiveModel, ActiveRecord, DataMapper, etc.), examples/ provides runnable standalone demos (car.rb, vehicle.rb) with generated YARD docs in examples/doc/, and test suite (likely in test/ or spec/) validates the DSL and integrations. The Rakefile orchestrates builds and documentation generation.

👥Who it's for

Ruby developers building domain models (especially with Rails/ActiveRecord, DataMapper, or other ORMs) who need to manage complex state workflows—common in e-commerce (order states), payment processing, user provisioning, or vehicle/asset tracking systems. Contributors are typically Rails developers or state machine enthusiasts maintaining a foundational gem in the Ruby ecosystem.

🌱Maturity & risk

Production-ready and mature. The project has Travis CI integration (.travis.yml), comprehensive examples (examples/ with car.rb, vehicle.rb, traffic_light.rb, auto_shop.rb), YARD documentation config (.yardopts), an Appraisals file for multi-version testing, and a CHANGELOG. However, the last visible activity is not recent enough to confirm ongoing maintenance—this warrants checking commit history and last tag date before new deployments.

Low-to-moderate risk. It is a well-established gem with minimal external dependencies (typical for state machine logic), but single-maintainer risk is real for older gems in this space. Key concern: compatibility with modern Ruby versions (3.0+) and current ORM versions (Rails 6+, Rails 7+) should be verified. Check that the Gemfile and Appraisals cover your target Ruby and ORM versions.

Active areas of work

Based on the file structure, no active development is visible in the provided snapshot. The presence of Appraisals, .travis.yml, and organized examples suggests the project was actively maintained but may be in maintenance-only mode. Check the GitHub issues page and recent commit log to confirm current activity.

🚀Get running

git clone git://github.com/pluginaweek/state_machine.git
cd state_machine
bundle install
bundle exec rake test

Daily commands: This is a library gem, not an application. To use it: add gem 'state_machine' to your Gemfile and bundle install. To test locally: bundle exec rake test. To see examples: cd examples && ruby car.rb or ruby vehicle.rb. To generate documentation: bundle exec yardoc (Ruby 1.9+).

🗺️Map of the codebase

  • lib/state_machine.rb — Main entry point and core namespace—defines the StateMachine class and registers with integrations
  • lib/state_machine/machine.rb — Central abstraction for defining states, events, and transitions—every state machine configuration flows through this
  • lib/state_machine/state.rb — State definition and lifecycle—manages state callbacks and guards that control behavior
  • lib/state_machine/transition.rb — Transition logic and event firing—executes state changes and associated callbacks
  • lib/state_machine/integrations — Integration layer for ActiveRecord, DataMapper, and other ORMs—enables persistence without tight coupling
  • Rakefile — Build and test harness—manages multi-version testing across 30+ Rails/ORM versions
  • README.md — High-level API overview and philosophy—explains state machine concept and typical usage patterns

🧩Components & responsibilities

  • Machine (Ruby metaprogramming, eval-based DSL) — Registers and manages all states, events, and transitions for a single state attribute; orchestrates event firing
    • Failure mode: Infinite loop if callback triggers same

🛠️How to make changes

Add a new state machine to a class

  1. Include StateMachine in your class or use state_machine DSL at class level (examples/vehicle.rb (line ~1–10 for reference pattern))
  2. Define the state machine with state_machine :attribute_name do block (lib/state_machine/machine.rb (machine initialization))
  3. Declare initial state with initial :state_name (lib/state_machine/machine.rb (initial_state handling))
  4. Define states using state :name with optional callbacks (lib/state_machine/state.rb)
  5. Define events using event :name containing transitions (lib/state_machine/event.rb)
  6. Add transitions with transition :from => ..., :to => ..., :guard => ... (lib/state_machine/transition.rb)

Add a callback to a transition or state

  1. In the state block, use before, after, or around hooks (lib/state_machine/state.rb (callback registration))
  2. Alternatively, add callback to transition with :before/:after/:around option (lib/state_machine/transition.rb (callback execution))
  3. Callback receives the object and optional transition/event args (lib/state_machine/callback.rb (invoke method))
  4. For ActiveRecord, use :save/:persist callbacks to update DB (lib/state_machine/integrations/active_record.rb (callback hooks))

Add a guard condition to prevent invalid transitions

  1. Use :guard => :method_name or :if/:unless option on transition (lib/state_machine/transition.rb (transitionable? check))
  2. Define the guard method on your class returning true/false (lib/state_machine/guard.rb (evaluate logic))
  3. Multiple guards can be combined; all must pass for transition to proceed (lib/state_machine/guard.rb (matches? method))

Integrate state_machine with a custom ORM

  1. Create new file in lib/state_machine/integrations/my_orm.rb (lib/state_machine/integrations/active_record.rb (as template))
  2. Extend StateMachine::Integrations::Base with ORM-specific hooks (lib/state_machine/integrations/base.rb)
  3. Override after_initialize, before_save, after_save as needed for persistence (lib/state_machine/integrations/active_record.rb (method overrides))
  4. Register integration by adding case statement in lib/state_machine.rb (lib/state_machine.rb (integration detection))

🔧Why these technologies

  • Ruby DSL (eval-based) — Enables readable, declarative state machine definitions without verbose setup; leverages Ruby's metaprogramming for dynamic method generation
  • ORM Integrations (ActiveRecord, DataMapper, Sequel, Mongoid) — Allows state_machine to work with any persistence layer without core coupling; persistence is optional for plain Ruby classes
  • Guard/Callback pattern — Separates conditional logic (guards) from side effects (callbacks), following single-responsibility and enabling testable, composable transitions
  • Class-level DSL with dynamic method generation — Removes boilerplate: event methods and state predicates are auto-generated, reducing manual method definitions

⚖️Trade-offs already made

  • DSL evaluation via instance_eval instead of class composition

    • Why: Provides cleaner syntax and implicit self binding; trade-off is less explicit dependency injection
    • Consequence: Slightly harder to test DSL blocks in isolation; easier learning curve for Rails developers
  • Support for multiple state attributes per class

    • Why: Flexibility to model complex objects with independent state machines
    • Consequence: Increased complexity in method name collision detection and state introspection
  • Guard conditions evaluated at runtime, not at compile-time

    • Why: Allows dynamic business logic based on object state without predefinition
    • Consequence: Invalid transitions only caught at runtime; no static analysis of reachable states
  • Tight integration with ORM validation/persistence hooks

    • Why: Enables atomic state + associated data updates within ORM transactions
    • Consequence: Heavy coupling to ORM lifecycle; harder to use pure state logic independent of persistence

🚫Non-goals (don't propose these)

  • Hierarchical/nested state machines (orthogonal states not supported)
  • Visual state diagram auto-generation (examples include pre-rendered PNGs only)
  • Real-time event streaming or event sourcing
  • Distributed/multi-process state coordination

🪤Traps & gotchas

  1. ORM version compatibility: Appraisals file indicates the gem supports multiple ORM versions; ensure your Gemfile and bundle match the Appraisals matrix to avoid adapter load errors. 2) Ruby version: .yardopts mentions YARD documentation requires Ruby 1.9+; very old Ruby versions may not work. 3) State value types: The gem supports any state value type (not just symbols), but persistence in databases requires serialization—confirm your ORM handles custom state types. 4) Callback argument order: before/after/around transitions pass different arguments; review the Vehicle example carefully to match your callback signatures. 5) Event parallelization: The README mentions parallel events; this may require careful handling in multi-threaded environments.

🏗️Architecture

💡Concepts to learn

  • Finite State Machine (FSM) — state_machine is a direct implementation of FSM theory; understanding states as mutually-exclusive, transitions as allowed paths, and events as triggers is foundational to using the gem effectively
  • Guard conditions (or preconditions) — state_machine supports conditional transitions via :if/:unless blocks; these are guard conditions that prevent invalid state transitions and are critical to correctness
  • Callback hooks (before/after/around) — state_machine's callback system (before_transition, after_transition, around_transition) mirrors Rails' callback pattern and enables side effects (logging, validation, notifications) tied to state changes
  • ORM adapter pattern — state_machine's integrations/ directory shows the adapter pattern for abstracting across multiple ORMs (ActiveRecord, DataMapper, Mongoid); critical for understanding how the gem persists state across different databases
  • Namespace isolation — state_machine supports multiple independent state machines per class via namespacing (e.g., state_machine :workflow and state_machine :approval); prevents attribute and callback collisions
  • GraphViz visualization — state_machine can auto-generate .png state diagrams (examples/ contains AutoShop_state.png, Vehicle_state.png); crucial for documenting complex workflows and validating transitions visually
  • Internationalization (I18n) — state_machine integrates with Rails I18n for translating state and event names; important for multi-language applications
  • aasm/aasm — Direct competitor in Ruby state machine space; more opinionated API but also widely used in Rails projects—compare DSL and features if considering alternatives
  • facebookarchive/Workflow — Early inspiration for declarative state machines in Ruby; less active but influenced the genre (state_machine is a more feature-complete successor)
  • rails/rails — Primary consumer ecosystem; state_machine integrates deeply with ActiveRecord and ActiveModel—understanding Rails conventions is essential
  • pluginaweek/enumeration — Companion gem from the same author for managing enumerated types; often paired with state_machine for state value definitions
  • sparklemotion/nokogiri — Unrelated to state machines but shows multi-integration pattern (C extensions + multiple ORM bindings) that mirrors state_machine's adapter architecture

🪄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.

Migrate from Travis CI to GitHub Actions workflow

The repo uses .travis.yml for CI/CD, which is outdated. GitHub Actions is now the standard for GitHub-hosted projects. This would modernize the CI pipeline, improve build reliability, and leverage native GitHub integration. The current .travis.yml tests against multiple Rails/ActiveModel versions (2.0.0 through 4.0.0) which should be formalized in a matrix strategy.

  • [ ] Create .github/workflows/ci.yml with matrix testing for ActiveModel versions defined in gemfiles/ directory
  • [ ] Include Ruby version matrix (likely 1.9.3 through 2.x based on gem versions)
  • [ ] Add status badge to README.md pointing to GitHub Actions instead of Travis CI
  • [ ] Verify all gemfile configurations run successfully
  • [ ] Archive or remove .travis.yml once validated

Add comprehensive integration tests for Rails REST example

The examples/rails-rest/ directory contains a complete Rails integration example (controller, model, migration, and views) but there are no corresponding integration test files validating this workflow. This would ensure the state machine works correctly with Rails conventions and catch regressions when Rails versions change.

  • [ ] Create test/integration/rails_rest_test.rb with tests for the examples/rails-rest/ scaffold
  • [ ] Test state transitions via controller actions matching the view names (_new, _edit, _show)
  • [ ] Validate database persistence of state changes across Rails CRUD operations
  • [ ] Test validations and error handling when state transitions are invalid
  • [ ] Document how to run these tests in CONTRIBUTING.md (if it exists) or README.md

Add type safety and transition validation tests for complex state machines

The examples include Vehicle, Car, TrafficLight, and AutoShop state machines but lack comprehensive test coverage validating edge cases like invalid transitions, callback execution order, and guard clause interactions. This is critical for a library where correctness of state transitions directly impacts application behavior.

  • [ ] Create test/unit/vehicle_state_machine_test.rb with guards, callbacks, and nested states validation
  • [ ] Add tests for AutoShop example covering complex transitions and callback ordering
  • [ ] Test guard clause scenarios (e.g., what happens when a guard returns false mid-transition)
  • [ ] Validate that all example state diagrams (PNG files in examples/) have corresponding test coverage
  • [ ] Add tests for edge cases like transitioning to same state, concurrent state machine attributes, and inheritance

🌿Good first issues

  • Add integration tests for Mongoid and MongoMapper (lib/state_machine/integrations/) to match the ActiveRecord and DataMapper coverage shown in the codebase—these adapters exist but test coverage is sparse.
  • Expand examples/ with a Sinatra or Hanami example (only Rails and Merb REST examples are present) to demonstrate state_machine usage outside Rails contexts.
  • Document the GraphViz visualization feature with a how-to guide and example PNG output in the README—the feature is mentioned but no usage instructions or generated diagrams are linked.

Top contributors

Click to expand
  • @obrie — 91 commits
  • [@Peter Lampesberger](https://github.com/Peter Lampesberger) — 1 commits
  • @darkhelmet — 1 commits
  • [@Brad Heller](https://github.com/Brad Heller) — 1 commits
  • @nathanl — 1 commits

📝Recent commits

Click to expand
  • 8a3ba81 — Merge pull request #247 from lichtamberg/master (obrie)
  • e6ea8d1 — Fixed typo, there is no alarm_state in this example (Peter Lampesberger)
  • 5bbf99e — Update to latest version of appraisal, renaming gemfiles according to new spec (obrie)
  • 25ba9a8 — Fix missing dependency require in EventCollection (obrie)
  • feff2a5 — Raise InvalidContext error when the current state does not define a state-driven behavior. Closes #196 (obrie)
  • 95b1e96 — Update State tests to only validate usage of super in state-driven behaviors in Ruby 1.9.1+ (obrie)
  • b3914bc — Fix target state being indeterminate for transitions that use blacklists (obrie)
  • c3b05c3 — Allow super to be called within state-driven behaviors (obrie)
  • 55b3b04 — Tag v1.2.0 release (obrie)
  • c80508b — Allow multiple whitelisted / blacklisted :to states when definining transitions. Closes #197 (obrie)

🔒Security observations

The state_machine gem shows moderate security concerns primarily due to support for very old Ruby on Rails versions with known vulnerabilities. The core library structure appears reasonable, but lacks visible security documentation and best practices guidance. The main risk is downstream when used with outdated dependencies. The absence of recent security updates and policy documentation is also a concern. Recommend updating dependency versions and adding security guidelines.

  • Medium · Outdated Ruby on Rails Dependencies — gemfiles/ directory - Multiple gemfile versions (active_record_2.x.x, active_model versions < 4.1). The codebase contains gemfiles referencing very old versions of ActiveRecord and ActiveModel (2.0.0 through 4.0.0). These versions contain known security vulnerabilities including SQL injection and XSS issues that were patched in later releases. Fix: Update to the latest supported versions of Rails, ActiveRecord, and ActiveModel. At minimum, use Rails 5.2+ or later. Review and update all dependencies to address known CVEs.
  • Low · Missing Security Policy Documentation — Repository root. No SECURITY.md file or security policy is visible in the repository structure. This makes it difficult for security researchers to report vulnerabilities responsibly. Fix: Add a SECURITY.md file with vulnerability disclosure guidelines and contact information for reporting security issues.
  • Low · No Input Validation Guidance in Documentation — README.md, Documentation. The README and available documentation do not appear to include security best practices or warnings about input validation when using state machines with user-controlled data. Fix: Add security considerations section to the README covering proper input validation, especially if state machines are used with web frameworks or user input.
  • Low · Potential Code Generation Security — lib/ directory (not provided in structure). As a state machine DSL, the library likely generates code dynamically. Without seeing the full codebase, dynamic code generation could pose security risks if not handled carefully (eval, method_missing, etc). Fix: Review all dynamic code generation to ensure it doesn't evaluate untrusted input. Avoid eval() and use safer alternatives like define_method with proper validation.

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.

Mixed signals · pluginaweek/state_machine — RepoPilot