Skip to content

Latest commit

 

History

History
191 lines (147 loc) · 4.62 KB

README.md

File metadata and controls

191 lines (147 loc) · 4.62 KB

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: '[email protected]'
    },
    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

Class Inheritance

HasStates lets you inherit from the HasStates::Base class to create custom state classes. This makes validations and custom methods on specific state types easy.

class MyState < HasStates::Base
  # Add validations or methods
end

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.