### Migration Guide: String to Integer Column Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md A migration example for changing the state storage from a string column to an integer column, including updating the model to use integer values for states. ```ruby # Migration class MigrateVehicleStatesToInteger < ActiveRecord::Migration[7.2] def change # Add new integer column add_column :vehicles, :status_code, :integer, default: 0, null: false # Copy data with conversion reversible do |dir| dir.up do Vehicle.find_each do |vehicle| code = case vehicle.state when 'parked' then 0 when 'idling' then 1 when 'first_gear' then 2 else 0 end vehicle.update_column(:status_code, code) end end dir.down do # Reverse conversion if needed end end # Remove old column, rename new one remove_column :vehicles, :state rename_column :vehicles, :status_code, :state end end # Update model class Vehicle < ApplicationRecord state_machine :state do state :parked, value: 0 state :idling, value: 1 state :first_gear, value: 2 end end ``` -------------------------------- ### Callback Configuration Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Example demonstrating various callback configurations within a state machine definition. ```ruby class Vehicle < ApplicationRecord state_machine initial: :parked do before_transition :any => :idling do |vehicle| vehicle.put_on_seatbelt end after_transition :any => :parked do |vehicle| vehicle.remove_keys_from_ignition end around_transition do |block| puts "Before transition" block.call puts "After transition" end after_failure do |vehicle, transition| LogFailedTransition.create(vehicle_id: vehicle.id) end end end ``` -------------------------------- ### Gem Installation Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Instructions for installing the state_machines-activerecord gem using Bundler. ```ruby # Gemfile gem 'state_machines-activerecord' # Then: bundle install ``` -------------------------------- ### Automatic Configuration Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Provides an example of the configuration object generated when enum integration is automatically detected. ```ruby class Order < ApplicationRecord enum :status, { pending: 0, processing: 1, completed: 2 } state_machine :status do state :pending, :processing, :completed end end # Auto-detected configuration: # { # enabled: true, # prefix: true, # Use "status_" prefix # suffix: false, # No suffix # scopes: true, # Generate scope methods # enum_values: { "pending" => 0, "processing" => 1, ... }, # original_enum_methods: ["pending?", "processing?", ...], # state_machine_methods: ["status_pending?", "status_processing?", ...] # } ``` -------------------------------- ### Testing State Transitions Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md Provides an example of how to test state transitions in a Rails application using ActiveSupport::TestCase. ```ruby require 'test_helper' class VehicleTest < ActiveSupport::TestCase def setup @vehicle = Vehicle.new(state: 'parked') end test 'should transition from parked to idling' do assert @vehicle.ignite assert_equal 'idling', @vehicle.state end test 'should not transition from idling to idling' do @vehicle.state = 'idling' assert !@vehicle.ignite end test 'should persist state to database' do @vehicle.ignite @vehicle.save loaded = Vehicle.find(@vehicle.id) assert_equal 'idling', loaded.state end test 'should run before_transition callback' do called = false @vehicle.class.state_machine do before_transition { called = true } end @vehicle.ignite assert called end end ``` -------------------------------- ### Verify Integer Type Setup Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/machine-methods.md Example showing how to verify if the integer type is registered for compact storage in state machines. ```ruby class Vehicle < ApplicationRecord state_machine :status do state :pending, value: 0 state :active, value: 1 end end machine = Vehicle.state_machine(:status) if machine.integer_type_registered? puts "Integer type is registered" puts "Using compact storage" else puts "Using string storage" end ``` -------------------------------- ### Event Method Examples Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md Examples of generated instance methods for handling state machine events. ```ruby vehicle.ignite # => true if transition succeeds, false otherwise vehicle.ignite! # => true if succeeds, raises StateMachines::InvalidTransition if fails vehicle.can_ignite? # => true if transition is possible from current state vehicle.ignite_transition # => Returns the Transition object that would be performed ``` -------------------------------- ### before_transition Callback Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md Example of defining a `before_transition` callback to perform an action or log information before a state change. ```ruby class Vehicle < ApplicationRecord state_machine do before_transition :any => :idling do |vehicle| vehicle.put_on_seatbelt end before_transition do |vehicle, transition| puts "Transitioning from #{transition.from} to #{transition.to}" end end end ``` -------------------------------- ### Installation Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/README.md Instructions for installing the state_machines-activerecord gem using Bundler. ```ruby # Gemfile gem 'state_machines-activerecord' ``` ```bash bundle install ``` -------------------------------- ### Integer Column Storage Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md Shows how to use integer columns for compact state storage, including a migration example and model definition with explicit integer values for states. ```ruby # Migration class CreateMessages < ActiveRecord::Migration[7.2] def change create_table :messages do |t| t.string :content t.integer :status, null: false, default: 0 # Compact integer storage t.timestamps end end end # Model with explicit integer values class Message < ApplicationRecord state_machine :status do state :pending, value: 0 state :sending, value: 1 state :sent, value: 2 state :failed, value: 3 event :send do transition :pending => :sending end event :succeed do transition :sending => :sent end event :fail do transition [:pending, :sending] => :failed end end end # Usage message = Message.create(status: :pending) message.status # => "pending" (string, convenient) message.read_attribute_before_type_cast(:status) # => 0 (stored in DB) message.succeed # Database stores integer 2, less space than storing "sent" ``` -------------------------------- ### around_transition Callback Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md Example of an `around_transition` callback used for wrapping the transition process, such as for benchmarking. ```ruby class Vehicle < ApplicationRecord state_machine do around_transition do |block| start = Time.current block.call puts "Transition took #{Time.current - start} seconds" end end end ``` -------------------------------- ### Async Job Processing Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md An example of a state machine for background job states, including transitions, callbacks for triggering background workers, and the worker implementation. ```ruby class AnalysisJob < ApplicationRecord state_machine :state, initial: :queued do state :queued state :processing state :completed state :failed state :retrying event :start_processing do transition :queued => :processing end event :complete do transition :processing => :completed end event :fail do transition [:processing, :retrying] => :failed end event :retry do transition :failed => :retrying end after_transition :queued => :processing do |job| AnalysisWorker.perform_in(5.seconds, job.id) end after_transition :failed => :retrying do |job| AnalysisWorker.perform_in(1.hour, job.id) end end end # Worker class AnalysisWorker include Sidekiq::Worker def perform(job_id) job = AnalysisJob.find(job_id) return unless job.start_processing begin result = perform_analysis(job) job.complete rescue => e job.fail job.retry if job.retries < 3 end end private def perform_analysis(job) # Long-running analysis end end ``` -------------------------------- ### Basic State Machine Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md A simple state machine with states and events. ```ruby class Vehicle < ApplicationRecord state_machine :state, initial: :parked do state :parked state :idling state :first_gear state :second_gear event :ignite do transition :parked => :idling end event :shift_up do transition :idling => :first_gear transition :first_gear => :second_gear end event :park do transition [:idling, :first_gear, :second_gear] => :parked end end end # Usage vehicle = Vehicle.create(state: :parked) vehicle.parked? # => true vehicle.ignite # => true vehicle.state # => 'idling' vehicle.can_shift_up? # => true vehicle.shift_up # => true vehicle.can_ignite? # => false (already running) vehicle.park # => true ``` -------------------------------- ### after_transition Callback Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md Example of using an `after_transition` callback to update a record after a successful state change. ```ruby class Vehicle < ApplicationRecord state_machine do after_transition :any => :parked do |vehicle| vehicle.update_column(:last_parked_at, Time.current) end end end ``` -------------------------------- ### Translation Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md An example of how to define translations for states and events in YAML format. ```yaml en: activerecord: state_machines: vehicle: state: states: parked: "Parked" idling: "Idling" events: ignite: "Start engine" ``` -------------------------------- ### Default Scope Names Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Example showing the automatically generated state scopes and how they are used. ```ruby class Vehicle < ApplicationRecord state_machine :state do state :parked, :idling end end # Generated scopes: Vehicle.with_state(:parked) # Find all parked vehicles Vehicle.with_states(:parked, :idling) # Find parked or idling Vehicle.without_state(:parked) # Exclude parked vehicles Vehicle.without_states(:parked, :idling) # Exclude both # Transparent scopes (when nil is passed): Vehicle.with_state(nil) # All vehicles Vehicle.where(color: 'red').with_state(params[:state]) # Chainable ``` -------------------------------- ### Scopes in Search/Filter Interfaces Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md Shows how to use state scopes for filtering records in a search interface, along with an example of how to implement this in a Rails controller and view. ```ruby class JobsController < ApplicationController def index @jobs = Job.all @jobs = @jobs.with_state(params[:status]) if params[:status].present? @jobs = @jobs.where(category: params[:category]) if params[:category].present? @jobs = @jobs.order(created_at: :desc) end end # View <%= form_with method: :get, local: true do |form| <%= form.select :status, [ ['All', nil], ['Open', 'open'], ['Closed', 'closed'] ], {} %> <%= form.submit "Filter" %> <% end %> <%= link_to "All", jobs_path %> <%= link_to "Open", jobs_path(status: 'open') %> <%= link_to "Closed", jobs_path(status: 'closed') %> ``` -------------------------------- ### Example: Default Integration Settings Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Illustrates how the default integration settings for action and use_transactions are applied when defining a state machine. ```ruby class Vehicle < ApplicationRecord # action defaults to :save # use_transactions defaults to true state_machine initial: :parked do # ... end # Same as: state_machine initial: :parked, action: :save, use_transactions: true do # ... end end ``` -------------------------------- ### Multiple State Machines on One Model Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md Demonstrates how a single ActiveRecord model can manage multiple independent state machines, with an example of defining two state machines and using their states in validations. ```ruby class ProjectTask < ApplicationRecord # First state machine for task workflow state_machine :status, initial: :todo do state :todo state :in_progress state :completed state :cancelled event :start do transition :todo => :in_progress end event :finish do transition :in_progress => :completed end event :abandon do transition [:todo, :in_progress] => :cancelled end end # Second state machine for review process state_machine :review_status, initial: :unreviewed do state :unreviewed state :approved state :rejected state :changes_requested event :approve do transition :unreviewed => :approved end event :request_changes do transition [:unreviewed, :changes_requested] => :changes_requested end end # Can check either state validates :name, presence: true validates :assignee_id, presence: true, if: -> { in_progress? } validates :summary, presence: true, if: -> { approved? } end # Usage task = ProjectTask.create(name: "Fix bug", status: :todo, review_status: :unreviewed) task.start # => status: in_progress task.start # => false (can't start again) task.request_changes # => review_status: changes_requested task.status # => 'in_progress' task.review_status # => 'changes_requested' ``` -------------------------------- ### State Event Setter and Getter Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md Demonstrates setting and getting a state event, checking validity, saving, and observing attribute clearing. ```ruby vehicle = Vehicle.new(state: :parked) vehicle.state_event # => nil vehicle.state_event = 'ignite' vehicle.state_event # => 'ignite' vehicle.valid? # => true (transition is valid) vehicle.save # Performs the ignite event vehicle.state # => 'idling' vehicle.state_event # => nil (cleared after save) ``` -------------------------------- ### State Machine Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md An example demonstrating how to define a state machine on an ActiveRecord model, including states, events, transitions, validations, and callbacks. ```ruby class Vehicle < ApplicationRecord state_machine :state, initial: :parked do event :ignite do transition :parked => :idling end event :shift_up do transition :idling => :first_gear transition :first_gear => :second_gear end state :first_gear, :second_gear do validates :seatbelt_on, presence: true end before_transition :any => :idling do |vehicle| vehicle.put_on_seatbelt end after_transition any => :parked do |vehicle, transition| vehicle.seatbelt = 'off' end around_transition do |block| puts "Before transition" block.call puts "After transition" end end def put_on_seatbelt self.seatbelt_on = true end end # Usage vehicle = Vehicle.create(state: 'parked') vehicle.ignite # => true, saves to database vehicle.state # => 'idling' vehicle = Vehicle.new vehicle.state_event = 'ignite' # Set event to fire on save vehicle.save # Fires the event during save ``` -------------------------------- ### Bank Account with Transaction Rollback Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md Illustrates how state machine transitions can be transactional, with examples of `before_transition` callbacks that can cause a rollback if they fail, and `after_failure` callbacks that execute outside the transaction. ```ruby class BankAccount < ApplicationRecord state_machine :status, initial: :active use_transitions: true do state :active state :suspended state :closed event :suspend do transition :active => :suspended end event :close do transition [:active, :suspended] => :closed end before_transition :active => :suspended do |account| # If this fails, the entire transition is rolled back account.notify_owner("Account suspended") account.log_suspension_event end after_transition :any => :closed do |account| account.send_closure_letter end after_failure do |account, transition| # This runs OUTSIDE the transaction AuditLog.create( account_id: account.id, event: transition.trigger, status: 'failed' ) end end end class SuspensionNotification < ApplicationRecord # This would be rolled back if the transition fails end class AuditLog < ApplicationRecord # Use a separate database connection that won't be rolled back connects_to database: { writing: :analytics, reading: :analytics } end # Usage account = BankAccount.create(status: :active) account.suspend # => If notify_owner or log_suspension_event fails, # no changes are made and AuditLog is NOT created account.suspend # => If successful, SuspensionNotification is created, # AuditLog is created (persisted outside transaction) ``` -------------------------------- ### Testing Scopes Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md Examples of testing state machine scopes in ActiveRecord, including filtering by state, handling nil values, and chaining scopes. ```ruby test 'should filter by state with scope' do Vehicle.create(state: 'parked') Vehicle.create(state: 'parked') Vehicle.create(state: 'idling') assert_equal 2, Vehicle.with_state(:parked).count assert_equal 1, Vehicle.with_state(:idling).count end test 'with_state should handle nil transparently' do Vehicle.create(state: 'parked') Vehicle.create(state: 'idling') assert_equal 2, Vehicle.with_state(nil).count end test 'scopes should be chainable' do Vehicle.create(state: 'parked', color: 'red') Vehicle.create(state: 'parked', color: 'blue') Vehicle.create(state: 'idling', color: 'red') result = Vehicle.with_state(:parked).where(color: 'red') assert_equal 1, result.count end ``` -------------------------------- ### State Bang Method Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md Example of generated state bang methods for transitioning to a specific state and saving the model. ```ruby vehicle.parked! # Transitions to parked state and saves vehicle.idling! # Transitions to idling state and saves ``` -------------------------------- ### Event Attribute Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md Example of using the event attribute to queue a transition for the next save operation. ```ruby vehicle.state_event # => nil (no pending event) vehicle.state_event = 'ignite' vehicle.valid? # => Validates that the transition is valid vehicle.save # Performs the transition during save ``` -------------------------------- ### after_failure Callback Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md Example of an `after_failure` callback to log transition failures without rolling back the transaction. ```ruby class TransitionLog < ApplicationRecord connects_to database: { writing: :primary, reading: :primary } end class Vehicle < ApplicationRecord state_machine do after_failure do |vehicle, transition| TransitionLog.create(vehicle: vehicle, transition: transition) end end end ``` -------------------------------- ### State Predicate Method Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md Example of generated state predicate methods for checking the current state of a model. ```ruby vehicle.parked? # => true vehicle.idling? # => false ``` -------------------------------- ### Example: Automatic Type Registration Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Demonstrates automatic type registration when column is :integer and states have explicit integer values. ```ruby class Vehicle < ApplicationRecord # Assumes :status column is of type :integer state_machine :status do state :pending, value: 0 state :active, value: 1 state :archived, value: 2 end end # StateMachines::Type::Integer is automatically registered # No manual configuration needed vehicle = Vehicle.new(status: :pending) vehicle.save # Database stores: 0 # Attribute returns: "pending" ``` -------------------------------- ### Event Attributes in Forms Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md Using event attributes for form-based transitions. ```ruby # Model class Document < ApplicationRecord state_machine :approval_state, initial: :submitted do state :submitted state :approved state :rejected event :approve do transition :submitted => :approved end event :reject do transition :submitted => :rejected end end end # View <%= form_with(model: document, local: true) do |form| %><% if document.submitted? %>
<%= form.label :approval_state_event %> <%= form.select :approval_state_event, [ ['Approve', 'approve'], ['Reject', 'reject'] ], { include_blank: 'Choose action...' } %>
<% end %> <%= form.submit %> <% end %> # Controller class DocumentsController < ApplicationController def update @document = Document.find(params[:id]) if @document.update(document_params) redirect_to @document, notice: 'Document updated' else render :edit, alert: 'Update failed' end end private def document_params params.require(:document).permit(:approval_state_event) end end ``` -------------------------------- ### Transition Object Callback Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/types.md An example of using the transition object within `before_transition` and `after_transition` callbacks. ```ruby class Vehicle < ApplicationRecord state_machine do before_transition do |vehicle, transition| puts "From #{transition.from} to #{transition.to} via #{transition.trigger}" end after_transition do |vehicle, transition| TransitionLog.create( vehicle_id: vehicle.id, from_state: transition.from, to_state: transition.to, event: transition.trigger ) end end end ``` -------------------------------- ### Callbacks and Side Effects Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md State machines with callbacks that perform actions on transitions. ```ruby class Order < ApplicationRecord state_machine :status, initial: :pending do state :pending state :processing state :completed state :cancelled event :process do transition :pending => :processing end event :complete do transition :processing => :completed end event :cancel do transition [:pending, :processing] => :cancelled end before_transition :pending => :processing do |order| order.charge_customer end after_transition :processing => :completed do |order| order.send_confirmation_email order.trigger_fulfillment end after_failure do |order, transition| OrderFailureLog.create( order_id: order.id, event: transition.trigger, reason: order.errors.full_messages.join(', ') ) end end def charge_customer return if charged? # Payment logic self.charged_at = Time.current end def send_confirmation_email OrderMailer.confirmation_email(self).deliver_later end def trigger_fulfillment FulfillmentService.start(self) end end # Usage order = Order.create(status: :pending) order.process # => Charges customer, saves order.charged_at # => Time when charged order.complete # => Sends email, triggers fulfillment ``` -------------------------------- ### Event Transition Method Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md Example of using the event transition method to retrieve the transition object without performing it. ```Ruby vehicle = Vehicle.create(state: :parked) transition = vehicle.ignite_transition # => # transition.from # => :parked transition.to # => :idling transition.trigger # => :ignite transition.record # => vehicle ``` -------------------------------- ### State Bang Method Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md Example showing how to use bang methods to transition a record to a new state and save it immediately. ```Ruby class Vehicle < ApplicationRecord state_machine :state, initial: :parked do state :parked, :idling, :first_gear event :ignite do transition :parked => :idling end end end vehicle = Vehicle.create vehicle.idling! # Transitions to idling state vehicle.state # => 'idling' vehicle.reload # Verify saved to database vehicle.state # => 'idling' ``` -------------------------------- ### Event Bang Method Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md Example demonstrating the event bang method, which performs a transition and raises an error on failure. ```Ruby vehicle = Vehicle.create vehicle.ignite! # => true vehicle.ignite! # Raises: StateMachines::InvalidTransition # Message: "Cannot transition state via :ignite from :idling" ``` -------------------------------- ### State Driven Validations Example 1 Source: https://github.com/state-machines/state_machines-activerecord/blob/master/README.md Demonstrates defining validations that should execute for specific states. This example shows a potential issue with custom validators in multiple states. ```ruby class Vehicle < ApplicationRecord state_machine do state :first_gear, :second_gear do validate :speed_is_legal end end end ``` -------------------------------- ### Event Predicate Method Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md Example showing how to use the 'can_event_name?' predicate method to check if an event can be triggered. ```Ruby vehicle = Vehicle.create(state: :parked) vehicle.can_ignite? # => true vehicle.ignite vehicle.can_ignite? # => false (can't ignite from idling) ``` -------------------------------- ### Custom Plural Names Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Example of configuring custom plural names for state scopes. ```ruby class Vehicle < ApplicationRecord state_machine :vehicle_state, plural: { vehicle_state: 'vehicle_states' } do state :parked, :idling end end # Plural name is used internally in scope implementation # but scope methods still use standard names ``` -------------------------------- ### Transition Syntax Examples Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/types.md Provides various syntaxes for defining transitions between states. ```ruby transition :from => :to # Single source and target transition [:from1, :from2] => :to # Multiple sources, single target transition :any => :to # Any state to target transition :from => [:to1, :to2] # Single source, multiple targets (less common) ``` -------------------------------- ### Default Configuration Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Example of using default options when defining a state machine on an ActiveRecord model. ```ruby class Vehicle < ApplicationRecord # Uses default options state_machine :state, initial: :parked do # ... end end # Equivalent to: state_machine :state, initial: :parked, action: :save, use_transactions: true do # ... end ``` -------------------------------- ### Manual Type Registration Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Example of manually registering the StateMachines::Type::Integer when automatic detection doesn't work. ```ruby class Vehicle < ApplicationRecord attribute :status, StateMachines::Type::Integer.new(state_machine(:status).states) state_machine :status do state :pending, value: 0 state :active, value: 1 end end ``` -------------------------------- ### State Predicate Method Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md Example demonstrating the usage of state predicate methods to check the current state of a record. ```Ruby class Vehicle < ApplicationRecord state_machine :state, initial: :parked do state :parked, :idling, :first_gear end end vehicle = Vehicle.create vehicle.parked? # => true vehicle.idling? # => false vehicle.first_gear? # => false vehicle.ignite vehicle.parked? # => false vehicle.idling? # => true ``` -------------------------------- ### Event Trigger Method Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md Example illustrating the use of event trigger methods to perform state transitions. ```Ruby class Vehicle < ApplicationRecord state_machine :state, initial: :parked do event :ignite do transition :parked => :idling end end end vehicle = Vehicle.create vehicle.ignite # => true vehicle.state # => 'idling' vehicle.ignite # => false (can't ignite from idling) ``` -------------------------------- ### Model Definition Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/README.md An example of how to define a state machine within an Active Record model, including event transitions, callbacks, and state-specific validations. ```ruby class Vehicle < ApplicationRecord state_machine :initial => :parked do before_transition :parked => any - :parked, :do => :put_on_seatbelt after_transition any => :parked do |vehicle, transition| vehicle.seatbelt = 'off' end around_transition :benchmark event :ignite do transition :parked => :idling end state :first_gear, :second_gear do validates :seatbelt_on, presence: true end end def put_on_seatbelt ... end def benchmark ... yield ... end end ``` -------------------------------- ### Example: Instance Method `deserialize` Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/integer-type.md Illustrates how the `deserialize` method converts an integer value from the database into a state name string. ```ruby type = StateMachines::Type::Integer.new(states) type.deserialize(0) # => "pending" type.deserialize(1) # => "active" type.deserialize(999) # => "999" (unknown value) type.deserialize(nil) # => nil ``` -------------------------------- ### Testing Event Attributes Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md Demonstrates testing the use of event attributes for triggering state transitions and validating invalid events. ```ruby test 'should queue event via event attribute' do vehicle = Vehicle.new vehicle.state_event = 'ignite' assert vehicle.valid? assert vehicle.save assert_equal 'idling', vehicle.state end test 'should validate invalid event' do vehicle = Vehicle.new vehicle.state_event = 'invalid_event' assert !vehicle.valid? assert vehicle.errors[:state_event].any? end ``` -------------------------------- ### Optimizing Storage Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/integer-type.md Illustrates the storage space difference between string and integer representations of state names. ```ruby # String storage state = "very_long_state_name" # => ~20 bytes per record # Integer storage state = 5 # => 4 bytes per record ``` -------------------------------- ### Custom Action Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Example of configuring a custom action for state transitions, such as updating a specific column instead of saving the entire record. ```ruby class Vehicle < ApplicationRecord # Transition to idle state without saving state_machine :state, initial: :parked, action: :idle_transition do # ... end def idle_transition # Custom action - not the standard save update_column(:last_transition_at, Time.current) end end ``` -------------------------------- ### Enum Integration Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/integer-type.md Demonstrates seamless integration with Rails enums on integer columns. ```ruby class Order < ApplicationRecord enum :status, { pending: 0, processing: 1, completed: 2 } state_machine :status do state :pending, value: 0 state :processing, value: 1 state :completed, value: 2 end end order = Order.new(status: :pending) order.pending? # => true (works with enum) ``` -------------------------------- ### State-Scoped Validations Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md Validations that only apply in specific states. ```ruby class BlogPost < ApplicationRecord state_machine :state, initial: :draft do state :draft state :published state :archived state :published do validates :title, presence: true validates :content, presence: true, length: { minimum: 100 } validates :published_at, presence: true # Inline validator for multiple states validate { |post| post.check_post_quality } end event :publish do transition :draft => :published end event :archive do transition [:draft, :published] => :archived end end def check_post_quality return if content.blank? if content.length < 500 errors.add(:content, "should be substantial (500+ chars recommended)") end end end # Usage post = BlogPost.new(state: :draft) post.valid? # => true (validations don't apply in draft) post.state = :published post.title = "" post.valid? # => false (title is required in published) # errors.full_messages => ["Title can't be blank", ...] ``` -------------------------------- ### Auto-Detection Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Shows how enum integration is automatically enabled when a Rails enum exists on the same attribute and no explicit configuration is provided. ```ruby class Order < ApplicationRecord enum :status, { pending: 0, processing: 1, completed: 2 } state_machine :status do # Auto-detection: enum integration is enabled state :pending, :processing, :completed end end # Check if enum integration is active Order.state_machine(:status).enum_integrated? # => true ``` -------------------------------- ### InvalidTransition Exception Handling Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md Example of how to rescue and handle the StateMachines::InvalidTransition exception. ```ruby begin vehicle.ignite! rescue StateMachines::InvalidTransition => e puts e.message # "Cannot transition state via :ignite from :parked" puts e.reason # "Name cannot be blank" end ``` -------------------------------- ### State-Scoped Validations Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Example of state-scoped validations where a validation only runs in specific states. ```ruby class Vehicle < ApplicationRecord state_machine do state :first_gear, :second_gear do validates :seatbelt_on, presence: true end end end # The validates callback runs only in first_gear and second_gear states ``` -------------------------------- ### i18n Integration Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Example of how the state machine gem automatically registers its locale with Rails' i18n on load. ```ruby # In state_machines-activerecord.rb ActiveSupport.on_load(:i18n) do I18n.load_path << File.expand_path('state_machines/integrations/active_record/locale.rb', __dir__) end ``` -------------------------------- ### Example Translation File Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/types.md A sample YAML file demonstrating translations for state machine elements. ```yaml en: activerecord: errors: messages: invalid: "is invalid" invalid_event: "cannot transition when %{state}" invalid_transition: "cannot transition via %{event}" state_machines: vehicle: state: states: parked: "Parked" idling: "Idling" first_gear: "1st Gear" events: ignite: "Start engine" shift_up: "Shift up" ``` -------------------------------- ### Use Transition Introspection Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/errors.md Example of using state machine introspection to check if a transition is possible and get details about it. ```ruby machine = Vehicle.state_machine(:state) transition = machine.transitions(vehicle, :ignite) if transition puts "Transition possible: #{transition.from} => #{transition.to}" else puts "No transition found" end ``` -------------------------------- ### State Scope Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md Demonstrates using `with_state` and `with_states` scopes with different parameter types and chaining. ```ruby class Vehicle < ApplicationRecord state_machine :state do state :parked, :idling, :first_gear end end # Singular form Vehicle.with_state(:parked) # Records in parked state Vehicle.with_state('parked') # String form (converted internally) # Plural form Vehicle.with_states(:parked, :idling) # Records in either state Vehicle.with_states([:parked, :idling]) # Array form # Transparent scope (nil returns all) Vehicle.with_state(nil) # All records # Chainable Vehicle.where(color: 'red').with_state(:parked).order(created_at: :asc) # In search interface class SearchService def vehicles_by_state(state = nil) Vehicle.with_state(state) # Returns all if state is nil end end ``` -------------------------------- ### Custom Plural Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Example of using custom plural names for state scopes, which affects the naming of generated scope methods. ```ruby class Person < ApplicationRecord state_machine :state, plural: { state: 'states' } do state :active, :inactive end end # Generated scope: Person.with_state(:active) # The plural name is used internally ``` -------------------------------- ### State-Driven Validations Workaround Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md Example showing how to use inline validators to ensure custom validators run on multiple states. ```ruby class Vehicle < ApplicationRecord state_machine do state :first_gear, :second_gear do # This will only run in second_gear validate :speed_is_legal # This will run in both states validate { |vehicle| vehicle.speed_is_legal } end end def speed_is_legal errors.add(:speed, "is too high") if speed > 100 end end ``` -------------------------------- ### Example: Instance Method `type` Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/integer-type.md Shows how the `type` method returns the ActiveRecord attribute type identifier. ```ruby type = StateMachines::Type::Integer.new(states) type.type # => :integer ``` -------------------------------- ### State-based Scopes: with_state / with_states Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md Examples of using the generated `with_state` and `with_states` scopes to query records based on their state. ```ruby Vehicle.with_state(:parked) # Records in parked state Vehicle.with_states(:parked, :idling) # Records in either state Vehicle.with_state(nil) # All records (transparent scope) Vehicle.where(color: 'red').with_state(params[:state]) # Chainable ``` -------------------------------- ### State Machine Accessor Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md Demonstrates retrieving state machine instances and performing introspection on them. ```ruby class Vehicle < ApplicationRecord state_machine :state do state :parked, :idling event :ignite do transition :parked => :idling end end state_machine :status do state :active, :inactive end end # Get default state machine machine = Vehicle.state_machine machine.attribute # => :state # Get specific state machine state_machine = Vehicle.state_machine(:state) status_machine = Vehicle.state_machine(:status) # Introspection state_machine.states.map(&:name) # => [:parked, :idling] state_machine.events.map(&:name) # => [:ignite, ...] ``` -------------------------------- ### generate_state_method_name Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/machine-methods.md Examples of using generate_state_method_name with and without enum integration. ```ruby # With enum integration (prefix applied) machine.generate_state_method_name(:pending, :predicate) # => "status_pending?" machine.generate_state_method_name(:pending, :bang) # => "status_pending!" machine.generate_state_method_name(:pending, :scope) # => "status_pending" # Without enum integration (no prefix) machine.generate_state_method_name(:parked, :predicate) # => "parked?" ``` -------------------------------- ### Validation Errors Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md Demonstrates how validation errors are added to the state attribute when a transition is invalid or a save fails. ```ruby vehicle = Vehicle.create(state: 'idling') vehicle.ignite # => false vehicle.errors.full_messages # => ["State cannot transition via \"ignite\""] vehicle = Vehicle.new vehicle.ignite! # Raises StateMachines::InvalidTransition ``` -------------------------------- ### Callback Block Arguments - Two Arguments Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/types.md Example of a callback receiving both the record instance and the transition object. ```ruby before_transition do |vehicle, transition| # vehicle is the model instance # transition is the Transition object puts "From #{transition.from} to #{transition.to}" end ``` -------------------------------- ### Introspect Configuration Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/machine-methods.md Example of how to introspect the configuration of a state machine, including enum integration and storage type. ```ruby class Product < ApplicationRecord enum :status, { draft: 0, published: 1 } state_machine :status do state :draft, :published end end machine = Product.state_machine(:status) config = { enum_integrated: machine.enum_integrated?, enum_mapping: machine.enum_mapping, original_enum_methods: machine.original_enum_methods, state_machine_methods: machine.state_machine_methods, integer_type_registered: machine.integer_type_registered? } puts config.inspect ``` -------------------------------- ### Without State Scope Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/generated-methods.md Demonstrates using `without_state` and `without_states` scopes with different parameter types and chaining. ```ruby # Singular form Vehicle.without_state(:parked) # Excludes parked records # Plural form Vehicle.without_states(:parked, :idling) # Excludes both states # Transparent scope Vehicle.without_state(nil) # All records # Chainable Vehicle.where(color: 'red').without_state(:parked).limit(10) # Combining with and without Vehicle.with_state(:parked, :idling).without_state(:broken) ``` -------------------------------- ### Use Transactions (Default: true) Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Demonstrates how state machine transitions are wrapped in database transactions by default, and how a `before_transition` hook can halt the transition and cause a rollback. ```ruby class Vehicle < ApplicationRecord # Transactions enabled (default) state_machine :state, use_transactions: true do before_transition do |vehicle| Message.create(content: "Transitioning...") false # Halt - transaction rolls back end end end vehicle = Vehicle.create vehicle.ignite # => false Message.count # => 0 (rolled back) ``` -------------------------------- ### Index-Based Fallback Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/integer-type.md Illustrates how the type uses the index position of a state as its integer value when no explicit value is defined. ```ruby states = [ State.new(:pending), # index 0 State.new(:processing), # index 1 State.new(:cancelled, value: 999), # explicit value State.new(:archived) # index 2 (skips cancelled's explicit value) ] type.serialize("pending") # => 0 (index) type.serialize("archived") # => 2 (index) type.serialize("cancelled") # => 999 (explicit) ``` -------------------------------- ### State Translation Keys Example Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/types.md Specific example of state translation keys for a Vehicle model. ```text activerecord.state_machines.vehicle.state.states.parked activerecord.state_machines.vehicle.states.parked activerecord.state_machines.state.states.parked activerecord.state_machines.states.parked ``` -------------------------------- ### State-based Scopes: without_state / without_states Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/activerecord-integration.md Examples of using the generated `without_state` and `without_states` scopes to query records that are not in specific states. ```ruby Vehicle.without_state(:parked) # Excludes parked records Vehicle.without_states(:parked, :idling) # Excludes both states Vehicle.without_state(nil) # All records (transparent scope) ``` -------------------------------- ### Example: Instance Method `serialize` Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/integer-type.md Demonstrates how the `serialize` method converts a state name (string or symbol) into its corresponding integer value for database storage. ```ruby type = StateMachines::Type::Integer.new(states) type.serialize("pending") # => 0 type.serialize(:active) # => 1 type.serialize("archived") # => 2 type.serialize(nil) # => nil ``` -------------------------------- ### detect_existing_enum_methods Example Return Value Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/api-reference/machine-methods.md Example return value for detect_existing_enum_methods, showing detected Rails enum methods. ```ruby [ "pending?", # Predicate method "processing?", "pending!", # Bang method "processing!", "pending", # Scope method "processing", "not_pending", # Negated scope "not_processing" ] ``` -------------------------------- ### API with Exception-Based Error Handling Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/examples-and-patterns.md Demonstrates how to use 'bang' methods (methods ending with '!') for state machine transitions in an API context, and how to handle `StateMachines::InvalidTransition` exceptions for robust error reporting. ```ruby class PaymentsController < ApplicationController rescue_from StateMachines::InvalidTransition, with: :handle_invalid_transition def capture @payment = Payment.find(params[:id]) begin @payment.capture! render json: { status: 'captured', payment: @payment } rescue StateMachines::InvalidTransition => e render json: { error: e.reason }, status: :unprocessable_entity end end def refund @payment = Payment.find(params[:id]) begin @payment.refund! render json: { status: 'refunded', payment: @payment } rescue StateMachines::InvalidTransition => e render json: { error: e.reason }, status: :conflict end end private def handle_invalid_transition(exception) render json: { error: 'Invalid state transition' }, status: :conflict end end ``` -------------------------------- ### Integration Defaults Source: https://github.com/state-machines/state_machines-activerecord/blob/master/_autodocs/configuration.md Demonstrates how to inspect the default integration settings for ActiveRecord state machines. ```ruby StateMachines::Integrations::ActiveRecord.defaults # => { action: :save, use_transactions: true } ```