0.0
The project is in a healthy, maintained state
HasStates provides state management and event system capabilities for Ruby objects. It allows tracking states, state transitions, and triggering callbacks on state changes.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

HasStates

HasStates is a flexible state management gem for Ruby on Rails that allows you to add multiple state machines to your models. It provides a simple way to track state transitions, add metadata, and execute callbacks.

Features

  • Multiple state types per model
  • Model-specific state configurations
  • JSON metadata storage for each state
  • Configurable callbacks with conditions
  • Limited execution callbacks
  • Automatic scope generation
  • Simple state transition tracking

Installation

Add this line to your application's Gemfile:

gem 'stateful_models'

Then execute:

$ bundle install

Generate the required migration and initializer:

$ rails generate has_states:install

Finally, run the migration:

$ rails db:migrate

Configuration

Configure your models and their state types in config/initializers/has_states.rb:

HasStates.configure do |config|
  # Configure states on any model
  config.configure_model User do |model|
    # Define state type and its allowed statuses
    model.state_type :kyc do |type|
      type.statuses = [
        'pending',              # Initial state
        'documents_required',   # Waiting for documents
        'under_review',        # Documents being reviewed
        'approved',            # KYC completed successfully
        'rejected'             # KYC failed
      ]
    end

    # Define multiple state types per model with different statuses
    model.state_type :onboarding do |type|
      type.statuses = [
        'pending',          # Just started
        'email_verified',   # Email verification complete
        'completed'         # Onboarding finished
      ]
    end
  end

  # Configure multiple models
  config.configure_model Company do |model|
    model.state_type :verification do |type|
      type.statuses = ['pending', 'verified', 'rejected']
    end
  end
end

Usage

Basic State Management

user = User.create!(name: 'John')
# Add a new state
state = user.add_state('kyc', status: 'pending', metadata: {
  documents: ['passport', 'utility_bill'],
  notes: 'Awaiting document submission'
})

# Check current state
current_kyc = user.current_state('kyc')

# Predicate methods are generated for every status.
current_kyc.pending?  # => true
current_kyc.approved? # => false

# Update state
current_kyc.update!(status: 'under_review')

# Check state for record 
user.kyc_pending? # => true
user.kyc_completed? # => false

# See all states for record
user.states # => [#<HasStates::State...>]

Working with Metadata

Each state can store arbitrary metadata as JSON:

# Store complex metadata
state = user.add_state('kyc', metadata: {
  documents: {
    passport: { 
      status: 'verified',
      verified_at: Time.current,
      verified_by: 'admin@example.com'
    },
    utility_bill: { 
      status: 'rejected',
      reason: 'Document expired'
    }
  },
  risk_score: 85,
  notes: ['Requires additional verification', 'High-risk jurisdiction']
})

# Access metadata
state.metadata['documents']['passport']['status'] # => "verified"
state.metadata['risk_score'] # => 85

Callbacks

Register callbacks that execute when states change:

HasStates.configure do |config|
  # Basic callback
  config.on(:kyc, to: 'completed') do |state|
    UserMailer.kyc_completed(state.stateable).deliver_later
  end

  # Callback with custom ID for easy removal
  config.on(:kyc, id: :notify_admin, to: 'rejected') do |state|
    AdminNotifier.kyc_rejected(state)
  end

  # Callback that runs only once
  config.on(:onboarding, to: 'completed', times: 1) do |state|
    WelcomeMailer.send_welcome(state.stateable)
  end

  # Callback with from/to conditions
  config.on(:kyc, from: 'pending', to: 'under_review') do |state|
    NotificationService.notify_review_started(state)
  end
end

# Remove callbacks
HasStates.configuration.off(:notify_admin)  # Remove by ID
HasStates.configuration.off(callback)       # Remove by callback object

Scopes

HasStates automatically generates scopes for your state types:

HasStates::State.kyc              # All KYC states
HasStates::State.onboarding      # All onboarding states

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/has_states.

License

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