Represents a state machine for a particular attribute. State machines consist of states, events and a set of transitions that define how the state changes after a particular event is fired.

A state machine will not know all of the possible states for an object unless they are referenced somewhere in the state machine definition. As a result, any unused states should be defined with the other_states or state helper.

Actions

When an action is configured for a state machine, it is invoked when an object transitions via an event. The success of the event becomes dependent on the success of the action. If the action is successful, then the transitioned state remains persisted. However, if the action fails (by returning false), the transitioned state will be rolled back.

For example,

  class Vehicle
    attr_accessor :fail, :saving_state

    state_machine :initial => :parked, :action => :save do
      event :ignite do
        transition :parked => :idling
      end

      event :park do
        transition :idling => :parked
      end
    end

    def save
      @saving_state = state
      fail != true
    end
  end

  vehicle = Vehicle.new     # => #<Vehicle:0xb7c27024 @state="parked">
  vehicle.save              # => true
  vehicle.saving_state      # => "parked" # The state was "parked" was save was called

  # Successful event
  vehicle.ignite            # => true
  vehicle.saving_state      # => "idling" # The state was "idling" when save was called
  vehicle.state             # => "idling"

  # Failed event
  vehicle.fail = true
  vehicle.park              # => false
  vehicle.saving_state      # => "parked"
  vehicle.state             # => "idling"

As shown, even though the state is set prior to calling the save action on the object, it will be rolled back to the original state if the action fails. Note that this will also be the case if an exception is raised while calling the action.

Indirect transitions

In addition to the action being run as the result of an event, the action can also be used to run events itself. For example, using the above as an example:

  vehicle = Vehicle.new           # => #<Vehicle:0xb7c27024 @state="parked">

  vehicle.state_event = 'ignite'
  vehicle.save                    # => true
  vehicle.state                   # => "idling"
  vehicle.state_event             # => nil

As can be seen, the save action automatically invokes the event stored in the state_event attribute (:ignite in this case).

One important note about using this technique for running transitions is that if the class in which the state machine is defined also defines the action being invoked (and not a superclass), then it must manually run the StateMachine hook that checks for event attributes.

For example, in ActiveRecord, DataMapper, and Sequel, the default action (save) is already defined in a base class. As a result, when a state machine is defined in a model / resource, StateMachine can automatically hook into the save action.

On the other hand, the Vehicle class from above defined its own save method (and there is no save method in its superclass). As a result, it must be modified like so:

    def save
      self.class.state_machines.fire_event_attributes(self, :save) do
        @saving_state = state
        fail != true
      end
    end

This will add in the functionality for firing the event stored in the state_event attribute.

Callbacks

Callbacks are supported for hooking before and after every possible transition in the machine. Each callback is invoked in the order in which it was defined. See StateMachine::Machine#before_transition and StateMachine::Machine#after_transition for documentation on how to define new callbacks.

Note that callbacks only get executed within the context of an event. As a result, if a class has an initial state when it‘s created, any callbacks that would normally get executed when the object enters that state will not get triggered.

For example,

  class Vehicle
    state_machine :initial => :parked do
      after_transition all => :parked do
        raise ArgumentError
      end
      ...
    end
  end

  vehicle = Vehicle.new   # => #<Vehicle id: 1, state: "parked">
  vehicle.save            # => true (no exception raised)

If you need callbacks to get triggered when an object is created, this should be done by either:

  • Use a before :save or equivalent hook, or
  • Set an initial state of nil and use the correct event to create the object with the proper state, resulting in callbacks being triggered and the object getting persisted

Canceling callbacks

Callbacks can be canceled by throwing :halt at any point during the callback. For example,

  ...
  throw :halt
  ...

If a before callback halts the chain, the associated transition and all later callbacks are canceled. If an after callback halts the chain, the later callbacks are canceled, but the transition is still successful.

Note that if a before callback fails and the bang version of an event was invoked, an exception will be raised instead of returning false. For example,

  class Vehicle
    state_machine :initial => :parked do
      before_transition any => :idling, :do => lambda {|vehicle| throw :halt}
      ...
    end
  end

  vehicle = Vehicle.new
  vehicle.park        # => false
  vehicle.park!       # => StateMachine::InvalidTransition: Cannot transition state via :park from "idling"

Observers

Observers, in the sense of external classes and not Ruby‘s Observable mechanism, can hook into state machines as well. Such observers use the same callback api that‘s used internally.

Below are examples of defining observers for the following state machine:

  class Vehicle
    state_machine do
      event :park do
        transition :idling => :parked
      end
      ...
    end
    ...
  end

Event/Transition behaviors:

  class VehicleObserver
    def self.before_park(vehicle, transition)
      logger.info "#{vehicle} instructed to park... state is: #{transition.from}, state will be: #{transition.to}"
    end

    def self.after_park(vehicle, transition, result)
      logger.info "#{vehicle} instructed to park... state was: #{transition.from}, state is: #{transition.to}"
    end

    def self.before_transition(vehicle, transition)
      logger.info "#{vehicle} instructed to #{transition.event}... #{transition.attribute} is: #{transition.from}, #{transition.attribute} will be: #{transition.to}"
    end

    def self.after_transition(vehicle, transition)
      logger.info "#{vehicle} instructed to #{transition.event}... #{transition.attribute} was: #{transition.from}, #{transition.attribute} is: #{transition.to}"
    end
  end

  Vehicle.state_machine do
    before_transition :on => :park, :do => VehicleObserver.method(:before_park)
    before_transition VehicleObserver.method(:before_transition)

    after_transition :on => :park, :do => VehicleObserver.method(:after_park)
    after_transition VehicleObserver.method(:after_transition)
  end

One common callback is to record transitions for all models in the system for auditing/debugging purposes. Below is an example of an observer that can easily automate this process for all models:

  class StateMachineObserver
    def self.before_transition(object, transition)
      Audit.log_transition(object.attributes)
    end
  end

  [Vehicle, Switch, Project].each do |klass|
    klass.state_machines.each do |attribute, machine|
      machine.before_transition StateMachineObserver.method(:before_transition)
    end
  end

Additional observer-like behavior may be exposed by the various integrations available. See below for more information on integrations.

Overriding instance / class methods

Hooking in behavior to the generated instance / class methods from the state machine, events, and states is very simple because of the way these methods are generated on the class. Using the class‘s ancestors, the original generated method can be referred to via super. For example,

  class Vehicle
    state_machine do
      event :park do
        ...
      end
    end

    def park(*args)
      logger.info "..."
      super
    end
  end

In the above example, the park instance method that‘s generated on the Vehicle class (by the associated event) is overridden with custom behavior. Once this behavior is complete, the original method from the state machine is invoked by simply calling super.

The same technique can be used for state, state_name, and all other instance and class methods on the Vehicle class.

Integrations

By default, state machines are library-agnostic, meaning that they work on any Ruby class and have no external dependencies. However, there are certain libraries which expose additional behavior that can be taken advantage of by state machines.

This library is built to work out of the box with a few popular Ruby libraries that allow for additional behavior to provide a cleaner and smoother experience. This is especially the case for objects backed by a database that may allow for transactions, persistent storage, search/filters, callbacks, etc.

When a state machine is defined for classes using any of the above libraries, it will try to automatically determine the integration to use (Agnostic, ActiveRecord, DataMapper, or Sequel) based on the class definition. To see how each integration affects the machine‘s behavior, refer to all constants defined under the StateMachine::Integrations namespace.

Methods
Included Modules
Attributes
[R] action The action to invoke when an object transitions
[R] callbacks The callbacks to invoke before/after a transition is performed

Maps :before => callbacks and :after => callbacks

[RW] default_messages
[R] events The events that trigger transitions. These are sorted, by default, in the order in which they were defined.
[R] instance_helper_module
[R] name The name of the machine, used for scoping methods generated for the machine as a whole (not states or events)
[R] namespace An identifier that forces all methods (including state predicates and event methods) to be generated with the value prefixed or suffixed, depending on the context.
[RW] owner_class The class that the machine is defined in
[R] states A list of all of the states known to this state machine. This will pull states from the following sources:
  • Initial state
  • State behaviors
  • Event transitions (:to, :from, and :except_from options)
  • Transition callbacks (:to, :from, :except_to, and :except_from options)
  • Unreferenced states (using other_states helper)

These are sorted, by default, in the order in which they were referenced.

[R] use_transactions Whether the machine will use transactions when firing events
Public Class methods
draw(class_names, options = {})

Draws the state machines defined in the given classes using GraphViz. The given classes must be a comma-delimited string of class names.

Configuration options:

  • :file - A comma-delimited string of files to load that contain the state machine definitions to draw
  • :path - The path to write the graph file to
  • :format - The image format to generate the graph in
  • :font - The name of the font to draw state names in
     # File lib/state_machine/machine.rb, line 334
334:       def draw(class_names, options = {})
335:         raise ArgumentError, 'At least one class must be specified' unless class_names && class_names.split(',').any?
336:         
337:         # Load any files
338:         if files = options.delete(:file)
339:           files.split(',').each {|file| require file}
340:         end
341:         
342:         class_names.split(',').each do |class_name|
343:           # Navigate through the namespace structure to get to the class
344:           klass = Object
345:           class_name.split('::').each do |name|
346:             klass = klass.const_defined?(name) ? klass.const_get(name) : klass.const_missing(name)
347:           end
348:           
349:           # Draw each of the class's state machines
350:           klass.state_machines.each_value do |machine|
351:             machine.draw(options)
352:           end
353:         end
354:       end
find_or_create(owner_class, *args, &block)

Attempts to find or create a state machine for the given class. For example,

  StateMachine::Machine.find_or_create(Vehicle)
  StateMachine::Machine.find_or_create(Vehicle, :initial => :parked)
  StateMachine::Machine.find_or_create(Vehicle, :status)
  StateMachine::Machine.find_or_create(Vehicle, :status, :initial => :parked)

If a machine of the given name already exists in one of the class‘s superclasses, then a copy of that machine will be created and stored in the new owner class (the original will remain unchanged).

     # File lib/state_machine/machine.rb, line 301
301:       def find_or_create(owner_class, *args, &block)
302:         options = args.last.is_a?(Hash) ? args.pop : {}
303:         name = args.first || :state
304:         
305:         # Find an existing machine
306:         if owner_class.respond_to?(:state_machines) && machine = owner_class.state_machines[name]
307:           # Only create a new copy if changes are being made to the machine in
308:           # a subclass
309:           if machine.owner_class != owner_class && (options.any? || block_given?)
310:             machine = machine.clone
311:             machine.initial_state = options[:initial] if options.include?(:initial)
312:             machine.owner_class = owner_class
313:           end
314:           
315:           # Evaluate DSL
316:           machine.instance_eval(&block) if block_given?
317:         else
318:           # No existing machine: create a new one
319:           machine = new(owner_class, name, options, &block)
320:         end
321:         
322:         machine
323:       end
new(owner_class, *args, &block)

Creates a new state machine for the given attribute

     # File lib/state_machine/machine.rb, line 404
404:     def initialize(owner_class, *args, &block)
405:       options = args.last.is_a?(Hash) ? args.pop : {}
406:       assert_valid_keys(options, :attribute, :initial, :action, :plural, :namespace, :integration, :messages, :use_transactions)
407:       
408:       # Find an integration that matches this machine's owner class
409:       if integration = options[:integration] ? StateMachine::Integrations.find(options[:integration]) : StateMachine::Integrations.match(owner_class)
410:         extend integration
411:         options = integration.defaults.merge(options) if integration.respond_to?(:defaults)
412:       end
413:       
414:       # Add machine-wide defaults
415:       options = {:use_transactions => true}.merge(options)
416:       
417:       # Set machine configuration
418:       @name = args.first || :state
419:       @attribute = options[:attribute] || @name
420:       @events = EventCollection.new(self)
421:       @states = StateCollection.new(self)
422:       @callbacks = {:before => [], :after => []}
423:       @namespace = options[:namespace]
424:       @messages = options[:messages] || {}
425:       @action = options[:action]
426:       @use_transactions = options[:use_transactions]
427:       self.owner_class = owner_class
428:       self.initial_state = options[:initial]
429:       
430:       # Define class integration
431:       define_helpers
432:       define_scopes(options[:plural])
433:       after_initialize
434:       
435:       # Evaluate DSL
436:       instance_eval(&block) if block_given?
437:     end
Public Instance methods
after_transition(*args, &block)

Creates a callback that will be invoked after a transition is performed so long as the given requirements match the transition.

See before_transition for a description of the possible configurations for defining callbacks.

      # File lib/state_machine/machine.rb, line 1200
1200:     def after_transition(*args, &block)
1201:       options = (args.last.is_a?(Hash) ? args.pop : {})
1202:       options[:do] = args if args.any?
1203:       add_callback(:after, options, &block)
1204:     end
attribute(name = :state)

Gets the actual name of the attribute on the machine‘s owner class that stores data with the given name.

     # File lib/state_machine/machine.rb, line 494
494:     def attribute(name = :state)
495:       name == :state ? @attribute : "#{self.name}_#{name}""#{self.name}_#{name}"
496:     end
before_transition(*args, &block)

Creates a callback that will be invoked before a transition is performed so long as the given requirements match the transition.

The callback

Callbacks must be defined as either an argument, in the :do option, or as a block. For example,

  class Vehicle
    state_machine do
      before_transition :set_alarm
      before_transition :set_alarm, all => :parked
      before_transition all => :parked, :do => :set_alarm
      before_transition all => :parked do |vehicle, transition|
        vehicle.set_alarm
      end
      ...
    end
  end

Notice that the first three callbacks are the same in terms of how the methods to invoke are defined. However, using the :do can provide for a more fluid DSL.

In addition, multiple callbacks can be defined like so:

  class Vehicle
    state_machine do
      before_transition :set_alarm, :lock_doors, all => :parked
      before_transition all => :parked, :do => [:set_alarm, :lock_doors]
      before_transition :set_alarm do |vehicle, transition|
        vehicle.lock_doors
      end
    end
  end

Notice that the different ways of configuring methods can be mixed.

State requirements

Callbacks can require that the machine be transitioning from and to specific states. These requirements use a Hash syntax to map beginning states to ending states. For example,

  before_transition :parked => :idling, :idling => :first_gear, :do => :set_alarm

In this case, the set_alarm callback will only be called if the machine is transitioning from parked to idling or from idling to parked.

To help define state requirements, a set of helpers are available for slightly more complex matching:

  • all - Matches every state/event in the machine
  • all - [:parked, :idling, …] - Matches every state/event except those specified
  • any - An alias for all (matches every state/event in the machine)
  • same - Matches the same state being transitioned from

See StateMachine::MatcherHelpers for more information.

Examples:

  before_transition :parked => [:idling, :first_gear], :do => ...     # Matches from parked to idling or first_gear
  before_transition all - [:parked, :idling] => :idling, :do => ...   # Matches from every state except parked and idling to idling
  before_transition all => :parked, :do => ...                        # Matches all states to parked
  before_transition any => same, :do => ...                           # Matches every loopback

Event requirements

In addition to state requirements, an event requirement can be defined so that the callback is only invoked on specific events using the on option. This can also use the same matcher helpers as the state requirements.

Examples:

  before_transition :on => :ignite, :do => ...                        # Matches only on ignite
  before_transition :on => all - :ignite, :do => ...                  # Matches on every event except ignite
  before_transition :parked => :idling, :on => :ignite, :do => ...    # Matches from parked to idling on ignite

Result requirements

By default, after_transition callbacks will only be run if the transition was performed successfully. A transition is successful if the machine‘s action is not configured or does not return false when it is invoked. In order to include failed attempts when running an after_transition callback, the :include_failures option can be specified like so:

  after_transition :include_failures => true, :do => ...  # Runs on all attempts to transition, including failures
  after_transition :do => ...                             # Runs only on successful attempts to transition

Verbose Requirements

Requirements can also be defined using verbose options rather than the implicit Hash syntax and helper methods described above.

Configuration options:

  • :from - One or more states being transitioned from. If none are specified, then all states will match.
  • :to - One or more states being transitioned to. If none are specified, then all states will match.
  • :on - One or more events that fired the transition. If none are specified, then all events will match.
  • :except_from - One or more states not being transitioned from
  • :except_to - One more states not being transitioned to
  • :except_on - One or more events that *did not* fire the transition

Examples:

  before_transition :from => :ignite, :to => :idling, :on => :park, :do => ...
  before_transition :except_from => :ignite, :except_to => :idling, :except_on => :park, :do => ...

Conditions

In addition to the state/event requirements, a condition can also be defined to help determine whether the callback should be invoked.

Configuration options:

  • :if - A method, proc or string to call to determine if the callback should occur (e.g. :if => :allow_callbacks, or :if => lambda {|user| user.signup_step > 2}). The method, proc or string should return or evaluate to a true or false value.
  • :unless - A method, proc or string to call to determine if the callback should not occur (e.g. :unless => :skip_callbacks, or :unless => lambda {|user| user.signup_step <= 2}). The method, proc or string should return or evaluate to a true or false value.

Examples:

  before_transition :parked => :idling, :if => :moving?, :do => ...
  before_transition :on => :ignite, :unless => :seatbelt_on?, :do => ...

Accessing the transition

In addition to passing the object being transitioned, the actual transition describing the context (e.g. event, from, to) can be accessed as well. This additional argument is only passed if the callback allows for it.

For example,

  class Vehicle
    # Only specifies one parameter (the object being transitioned)
    before_transition all => :parked do |vehicle|
      vehicle.set_alarm
    end

    # Specifies 2 parameters (object being transitioned and actual transition)
    before_transition all => :parked do |vehicle, transition|
      vehicle.set_alarm(transition)
    end
  end

Note that the object in the callback will only be passed in as an argument if callbacks are configured to not be bound to the object involved. This is the default and may change on a per-integration basis.

See StateMachine::Transition for more information about the attributes available on the transition.

Examples

Below is an example of a class with one state machine and various types of before transitions defined for it:

  class Vehicle
    state_machine do
      # Before all transitions
      before_transition :update_dashboard

      # Before specific transition:
      before_transition [:first_gear, :idling] => :parked, :on => :park, :do => :take_off_seatbelt

      # With conditional callback:
      before_transition all => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on?

      # Using helpers:
      before_transition all - :stalled => same, :on => any - :crash, :do => :update_dashboard
      ...
    end
  end

As can be seen, any number of transitions can be created using various combinations of configuration options.

      # File lib/state_machine/machine.rb, line 1189
1189:     def before_transition(*args, &block)
1190:       options = (args.last.is_a?(Hash) ? args.pop : {})
1191:       options[:do] = args if args.any?
1192:       add_callback(:before, options, &block)
1193:     end
define_class_method(method, &block)

Defines a new class method with the given name on the machine‘s owner class. If the method is already defined in the class, then this will not override it.

Example:

  machine.define_class_method(:states) do |machine, klass|
    machine.states.keys
  end
     # File lib/state_machine/machine.rb, line 527
527:     def define_class_method(method, &block)
528:       name = self.name
529:       
530:       @class_helper_module.class_eval do
531:         define_method(method) do |*args|
532:           block.call(self.state_machine(name), self, *args)
533:         end
534:       end
535:     end
define_instance_method(method, &block)

Defines a new instance method with the given name on the machine‘s owner class. If the method is already defined in the class, then this will not override it.

Example:

  machine.define_instance_method(:state_name) do |machine, object|
    machine.states.match(object)
  end
     # File lib/state_machine/machine.rb, line 507
507:     def define_instance_method(method, &block)
508:       name = self.name
509:       
510:       @instance_helper_module.class_eval do
511:         define_method(method) do |*args|
512:           block.call(self.class.state_machine(name), self, *args)
513:         end
514:       end
515:     end
draw(options = {})

Draws a directed graph of the machine for visualizing the various events, states, and their transitions.

This requires both the Ruby graphviz gem and the graphviz library be installed on the system.

Configuration options:

  • :name - The name of the file to write to (without the file extension). Default is "#{owner_class.name}_#{name}"
  • :path - The path to write the graph file to. Default is the current directory (".").
  • :format - The image format to generate the graph in. Default is "png’.
  • :font - The name of the font to draw state names in. Default is "Arial".
  • :orientation - The direction of the graph ("portrait" or "landscape"). Default is "portrait".
  • :output - Whether to generate the output of the graph
      # File lib/state_machine/machine.rb, line 1255
1255:     def draw(options = {})
1256:       options = {
1257:         :name => "#{owner_class.name}_#{name}",
1258:         :path => '.',
1259:         :format => 'png',
1260:         :font => 'Arial',
1261:         :orientation => 'portrait',
1262:         :output => true
1263:       }.merge(options)
1264:       assert_valid_keys(options, :name, :path, :format, :font, :orientation, :output)
1265:       
1266:       begin
1267:         # Load the graphviz library
1268:         require 'rubygems'
1269:         require 'graphviz'
1270:         
1271:         graph = GraphViz.new('G',
1272:           :output => options[:format],
1273:           :file => File.join(options[:path], "#{options[:name]}.#{options[:format]}"),
1274:           :rankdir => options[:orientation] == 'landscape' ? 'LR' : 'TB'
1275:         )
1276:         
1277:         # Add nodes
1278:         states.by_priority.each do |state|
1279:           node = state.draw(graph)
1280:           node.fontname = options[:font]
1281:         end
1282:         
1283:         # Add edges
1284:         events.each do |event|
1285:           edges = event.draw(graph)
1286:           edges.each {|edge| edge.fontname = options[:font]}
1287:         end
1288:         
1289:         # Generate the graph
1290:         graph.output if options[:output]
1291:         graph
1292:       rescue LoadError
1293:         $stderr.puts 'Cannot draw the machine. `gem install ruby-graphviz` and try again.'
1294:         false
1295:       end
1296:     end
dynamic_initial_state?()

Whether a dynamic initial state is being used in the machine

     # File lib/state_machine/machine.rb, line 576
576:     def dynamic_initial_state?
577:       @initial_state.is_a?(Proc)
578:     end
event(*names, &block)

Defines one or more events for the machine and the transitions that can be performed when those events are run.

This method is also aliased as on for improved compatibility with using a domain-specific language.

Instance methods

The following instance methods are generated when a new event is defined (the "park" event is used as an example):

  • can_park? - Checks whether the "park" event can be fired given the current state of the object. This will not run validations in ORM integrations. To check whether an event can fire and passes validations, use event attributes (e.g. state_event) as described in the "Events" documentation of each ORM integration.
  • park_transition - Gets the next transition that would be performed if the "park" event were to be fired now on the object or nil if no transitions can be performed.
  • park(run_action = true) - Fires the "park" event, transitioning from the current state to the next valid state.
  • park!(run_action = true) - Fires the "park" event, transitioning from the current state to the next valid state. If the transition fails, then a StateMachine::InvalidTransition error will be raised.

With a namespace of "car", the above names map to the following methods:

  • can_park_car?
  • park_car_transition
  • park_car
  • park_car!

Defining transitions

event requires a block which allows you to define the possible transitions that can happen as a result of that event. For example,

  event :park, :stop do
    transition :idling => :parked
  end

  event :first_gear do
    transition :parked => :first_gear, :if => :seatbelt_on?
  end

See StateMachine::Event#transition for more information on the possible options that can be passed in.

Note that this block is executed within the context of the actual event object. As a result, you will not be able to reference any class methods on the model without referencing the class itself. For example,

  class Vehicle
    def self.safe_states
      [:parked, :idling, :stalled]
    end

    state_machine do
      event :park do
        transition Vehicle.safe_states => :parked
      end
    end
  end

Defining additional arguments

Additional arguments on event actions can be defined like so:

  class Vehicle
    state_machine do
      event :park do
        ...
      end
    end

    def park(kind = :parallel, *args)
      take_deep_breath if kind == :parallel
      super
    end

    def take_deep_breath
      sleep 3
    end
  end

Note that super is called instead of super(*args). This allows the entire arguments list to be accessed by transition callbacks through StateMachine::Transition#args like so:

  after_transition :on => :park do |vehicle, transition|
    kind = *transition.args
    ...
  end

Example

  class Vehicle
    state_machine do
      # The park, stop, and halt events will all share the given transitions
      event :park, :stop, :halt do
        transition [:idling, :backing_up] => :parked
      end

      event :stop do
        transition :first_gear => :idling
      end

      event :ignite do
        transition :parked => :idling
      end
    end
  end
This method is also aliased as on
      # File lib/state_machine/machine.rb, line 989
 989:     def event(*names, &block)
 990:       events = names.collect do |name|
 991:         unless event = self.events[name]
 992:           self.events << event = Event.new(self, name)
 993:         end
 994:         
 995:         if block_given?
 996:           event.instance_eval(&block)
 997:           add_states(event.known_states)
 998:         end
 999:         
1000:         event
1001:       end
1002:       
1003:       events.length == 1 ? events.first : events
1004:     end
generate_message(name, values = [])

Generates the message to use when invalidating the given object after failing to transition on a specific event

      # File lib/state_machine/machine.rb, line 1220
1220:     def generate_message(name, values = [])
1221:       (@messages[name] || self.class.default_messages[name]) % values.map {|value| value.last}
1222:     end
initial_state(object)

Gets the initial state of the machine for the given object. If a dynamic initial state was configured for this machine, then the object will be passed into the lambda block to help determine the actual state.

Examples

With a static initial state:

  class Vehicle
    state_machine :initial => :parked do
      ...
    end
  end

  vehicle = Vehicle.new
  Vehicle.state_machine.initial_state(vehicle)  # => #<StateMachine::State name=:parked value="parked" initial=true>

With a dynamic initial state:

  class Vehicle
    attr_accessor :force_idle

    state_machine :initial => lambda {|vehicle| vehicle.force_idle ? :idling : :parked} do
      ...
    end
  end

  vehicle = Vehicle.new

  vehicle.force_idle = true
  Vehicle.state_machine.initial_state(vehicle)  # => #<StateMachine::State name=:idling value="idling" initial=false>

  vehicle.force_idle = false
  Vehicle.state_machine.initial_state(vehicle)  # => #<StateMachine::State name=:parked value="parked" initial=false>
     # File lib/state_machine/machine.rb, line 571
571:     def initial_state(object)
572:       states.fetch(dynamic_initial_state? ? @initial_state.call(object) : @initial_state)
573:     end
initial_state=(new_initial_state)

Sets the initial state of the machine. This can be either the static name of a state or a lambda block which determines the initial state at creation time.

     # File lib/state_machine/machine.rb, line 484
484:     def initial_state=(new_initial_state)
485:       @initial_state = new_initial_state
486:       add_states([@initial_state]) unless dynamic_initial_state?
487:       
488:       # Update all states to reflect the new initial state
489:       states.each {|state| state.initial = (state.name == @initial_state)}
490:     end
invalidate(object, attribute, message, values = [])

Marks the given object as invalid with the given message.

By default, this is a no-op.

      # File lib/state_machine/machine.rb, line 1209
1209:     def invalidate(object, attribute, message, values = [])
1210:     end
on(*names, &block)

Alias for event

other_states(*names, &block)

Alias for state

owner_class=(klass)

Sets the class which is the owner of this state machine. Any methods generated by states, events, or other parts of the machine will be defined on the given owner class.

     # File lib/state_machine/machine.rb, line 455
455:     def owner_class=(klass)
456:       @owner_class = klass
457:       
458:       # Create modules for extending the class with state/event-specific methods
459:       class_helper_module = @class_helper_module = Module.new
460:       instance_helper_module = @instance_helper_module = Module.new
461:       owner_class.class_eval do
462:         extend class_helper_module
463:         include instance_helper_module
464:       end
465:       
466:       # Add class-/instance-level methods to the owner class for state initialization
467:       unless owner_class.included_modules.include?(StateMachine::InstanceMethods)
468:         owner_class.class_eval do
469:           extend StateMachine::ClassMethods
470:           include StateMachine::InstanceMethods
471:         end
472:         
473:         define_state_initializer
474:       end
475:       
476:       # Record this machine as matched to the name in the current owner class.
477:       # This will override any machines mapped to the same name in any superclasses.
478:       owner_class.state_machines[name] = self
479:     end
read(object, attribute, ivar = false)

Gets the current value stored in the given object‘s attribute.

For example,

  class Vehicle
    state_machine :initial => :parked do
      ...
    end
  end

  vehicle = Vehicle.new                           # => #<Vehicle:0xb7d94ab0 @state="parked">
  Vehicle.state_machine.read(vehicle, :state)     # => "parked" # Equivalent to vehicle.state
  Vehicle.state_machine.read(vehicle, :event)     # => nil      # Equivalent to vehicle.state_event
     # File lib/state_machine/machine.rb, line 855
855:     def read(object, attribute, ivar = false)
856:       attribute = self.attribute(attribute)
857:       ivar ? object.instance_variable_get("@#{attribute}") : object.send(attribute)
858:     end
reset(object)

Resets any errors previously added when invalidating the given object.

By default, this is a no-op.

      # File lib/state_machine/machine.rb, line 1215
1215:     def reset(object)
1216:     end
state(*names, &block)

Customizes the definition of one or more states in the machine.

Configuration options:

  • :value - The actual value to store when an object transitions to the state. Default is the name (stringified).
  • :cache - If a dynamic value (via a lambda block) is being used, then setting this to true will cache the evaluated result
  • :if - Determines whether an object‘s value matches the state (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}). By default, the configured value is matched.

Customizing the stored value

Whenever a state is automatically discovered in the state machine, its default value is assumed to be the stringified version of the name. For example,

  class Vehicle
    state_machine :initial => :parked do
      event :ignite do
        transition :parked => :idling
      end
    end
  end

In the above state machine, there are two states automatically discovered: :parked and :idling. These states, by default, will store their stringified equivalents when an object moves into that state (e.g. "parked" / "idling").

For legacy systems or when tying state machines into existing frameworks, it‘s oftentimes necessary to need to store a different value for a state than the default. In order to continue taking advantage of an expressive state machine and helper methods, every defined state can be re-configured with a custom stored value. For example,

  class Vehicle
    state_machine :initial => :parked do
      event :ignite do
        transition :parked => :idling
      end

      state :idling, :value => 'IDLING'
      state :parked, :value => 'PARKED
    end
  end

This is also useful if being used in association with a database and, instead of storing the state name in a column, you want to store the state‘s foreign key:

  class VehicleState < ActiveRecord::Base
  end

  class Vehicle < ActiveRecord::Base
    state_machine :attribute => :state_id, :initial => :parked do
      event :ignite do
        transition :parked => :idling
      end

      states.each do |state|
        self.state(state.name, :value => lambda { VehicleState.find_by_name(state.name.to_s).id }, :cache => true)
      end
    end
  end

In the above example, each known state is configured to store it‘s associated database id in the state_id attribute. Also, notice that a lambda block is used to define the state‘s value. This is required in situations (like testing) where the model is loaded without any existing data (i.e. no VehicleState records available).

One caveat to the above example is to keep performance in mind. To avoid constant db hits for looking up the VehicleState ids, the value is cached by specifying the :cache option. Alternatively, a custom caching strategy can be used like so:

  class VehicleState < ActiveRecord::Base
    cattr_accessor :cache_store
    self.cache_store = ActiveSupport::Cache::MemoryStore.new

    def self.find_by_name(name)
      cache_store.fetch(name) { find(:first, :conditions => {:name => name}) }
    end
  end

Dynamic values

In addition to customizing states with other value types, lambda blocks can also be specified to allow for a state‘s value to be determined dynamically at runtime. For example,

  class Vehicle
    state_machine :purchased_at, :initial => :available do
      event :purchase do
        transition all => :purchased
      end

      event :restock do
        transition all => :available
      end

      state :available, :value => nil
      state :purchased, :if => lambda {|value| !value.nil?}, :value => lambda {Time.now}
    end
  end

In the above definition, the :purchased state is customized with both a dynamic value and a value matcher.

When an object transitions to the purchased state, the value‘s lambda block will be called. This will get the current time and store it in the object‘s purchased_at attribute.

Note that the custom matcher is very important here. Since there‘s no way for the state machine to figure out an object‘s state when it‘s set to a runtime value, it must be explicitly defined. If the :if option were not configured for the state, then an ArgumentError exception would be raised at runtime, indicating that the state machine could not figure out what the current state of the object was.

Behaviors

Behaviors define a series of methods to mixin with objects when the current state matches the given one(s). This allows instance methods to behave a specific way depending on what the value of the object‘s state is.

For example,

  class Vehicle
    attr_accessor :driver
    attr_accessor :passenger

    state_machine :initial => :parked do
      event :ignite do
        transition :parked => :idling
      end

      state :parked do
        def speed
          0
        end

        def rotate_driver
          driver = self.driver
          self.driver = passenger
          self.passenger = driver
          true
        end
      end

      state :idling, :first_gear do
        def speed
          20
        end

        def rotate_driver
          self.state = 'parked'
          rotate_driver
        end
      end

      other_states :backing_up
    end
  end

In the above example, there are two dynamic behaviors defined for the class:

  • speed
  • rotate_driver

Each of these behaviors are instance methods on the Vehicle class. However, which method actually gets invoked is based on the current state of the object. Using the above class as the example:

  vehicle = Vehicle.new
  vehicle.driver = 'John'
  vehicle.passenger = 'Jane'

  # Behaviors in the "parked" state
  vehicle.state             # => "parked"
  vehicle.speed             # => 0
  vehicle.rotate_driver     # => true
  vehicle.driver            # => "Jane"
  vehicle.passenger         # => "John"

  vehicle.ignite            # => true

  # Behaviors in the "idling" state
  vehicle.state             # => "idling"
  vehicle.speed             # => 20
  vehicle.rotate_driver     # => true
  vehicle.driver            # => "John"
  vehicle.passenger         # => "Jane"

As can be seen, both the speed and rotate_driver instance method implementations changed how they behave based on what the current state of the vehicle was.

Invalid behaviors

If a specific behavior has not been defined for a state, then a NoMethodError exception will be raised, indicating that that method would not normally exist for an object with that state.

Using the example from before:

  vehicle = Vehicle.new
  vehicle.state = 'backing_up'
  vehicle.speed               # => NoMethodError: undefined method 'speed' for #<Vehicle:0xb7d296ac> in state "backing_up"

State-aware class methods

In addition to defining scopes for instance methods that are state-aware, the same can be done for certain types of class methods.

Some libraries have support for class-level methods that only run certain behaviors based on a conditions hash passed in. For example:

  class Vehicle < ActiveRecord::Base
    state_machine do
      ...
      state :first_gear, :second_gear, :third_gear do
        validates_presence_of   :speed
        validates_inclusion_of  :speed, :in => 0..25, :if => :in_school_zone?
      end
    end
  end

In the above ActiveRecord model, two validations have been defined which will only run when the Vehicle object is in one of the three states: first_gear, second_gear, or +third_gear. Notice, also, that if/unless conditions can continue to be used.

This functionality is not library-specific and can work for any class-level method that is defined like so:

  def validates_presence_of(attribute, options = {})
    ...
  end

The minimum requirement is that the last argument in the method be an options hash which contains at least :if condition support.

This method is also aliased as other_states
     # File lib/state_machine/machine.rb, line 822
822:     def state(*names, &block)
823:       options = names.last.is_a?(Hash) ? names.pop : {}
824:       assert_valid_keys(options, :value, :cache, :if)
825:       
826:       states = add_states(names)
827:       states.each do |state|
828:         if options.include?(:value)
829:           state.value = options[:value]
830:           self.states.update(state)
831:         end
832:         
833:         state.cache = options[:cache] if options.include?(:cache)
834:         state.matcher = options[:if] if options.include?(:if)
835:         state.context(&block) if block_given?
836:       end
837:       
838:       states.length == 1 ? states.first : states
839:     end
within_transaction(object) {|| ...}

Runs a transaction, rolling back any changes if the yielded block fails.

This is only applicable to integrations that involve databases. By default, this will not run any transactions since the changes aren‘t taking place within the context of a database.

      # File lib/state_machine/machine.rb, line 1229
1229:     def within_transaction(object)
1230:       if use_transactions
1231:         transaction(object) { yield }
1232:       else
1233:         yield
1234:       end
1235:     end
write(object, attribute, value)

Sets a new value in the given object‘s attribute.

For example,

  class Vehicle
    state_machine :initial => :parked do
      ...
    end
  end

  vehicle = Vehicle.new                                   # => #<Vehicle:0xb7d94ab0 @state="parked">
  Vehicle.state_machine.write(vehicle, :state, 'idling')  # => Equivalent to vehicle.state = 'idling'
  Vehicle.state_machine.write(vehicle, :event, 'park')    # => Equivalent to vehicle.state_event = 'park'
  vehicle.state                                           # => "idling"
  vehicle.event                                           # => "park"
     # File lib/state_machine/machine.rb, line 875
875:     def write(object, attribute, value)
876:       object.send("#{self.attribute(attribute)}=", value)
877:     end
Protected Instance methods
add_callback(type, options, &block)

Adds a new transition callback of the given type.

      # File lib/state_machine/machine.rb, line 1450
1450:       def add_callback(type, options, &block)
1451:         callbacks[type] << callback = Callback.new(options, &block)
1452:         add_states(callback.known_states)
1453:         callback
1454:       end
add_states(new_states)

Tracks the given set of states in the list of all known states for this machine

      # File lib/state_machine/machine.rb, line 1458
1458:       def add_states(new_states)
1459:         new_states.map do |new_state|
1460:           unless state = states[new_state]
1461:             states << state = State.new(self, new_state)
1462:           end
1463:           
1464:           state
1465:         end
1466:       end
after_initialize()

Runs additional initialization hooks. By default, this is a no-op.

      # File lib/state_machine/machine.rb, line 1300
1300:       def after_initialize
1301:       end
create_with_scope(name)

Creates a scope for finding objects with a particular value or values for the attribute.

By default, this is a no-op.

      # File lib/state_machine/machine.rb, line 1434
1434:       def create_with_scope(name)
1435:       end
create_without_scope(name)

Creates a scope for finding objects without a particular value or values for the attribute.

By default, this is a no-op.

      # File lib/state_machine/machine.rb, line 1441
1441:       def create_without_scope(name)
1442:       end
define_action_helpers(action_hook = self.action)

Adds helper methods for automatically firing events when an action is invoked

      # File lib/state_machine/machine.rb, line 1384
1384:       def define_action_helpers(action_hook = self.action)
1385:         action = self.action
1386:         private_method = owner_class.private_method_defined?(action_hook)
1387:         
1388:         if (owner_class.method_defined?(action_hook) || private_method) && !owner_class.state_machines.any? {|name, machine| machine.action == action && machine != self}
1389:           # Action is defined and hasn't already been overridden by another machine
1390:           @instance_helper_module.class_eval do
1391:             # Override the default action to invoke the before / after hooks
1392:             define_method(action_hook) do |*args|
1393:               self.class.state_machines.fire_event_attributes(self, action) { super(*args) }
1394:             end
1395:             
1396:             private action_hook if private_method
1397:           end
1398:           
1399:           true
1400:         else
1401:           # Action already defined: don't add integration-specific hooks
1402:           false
1403:         end
1404:       end
define_event_helpers()

Adds helper methods for getting information about this state machine‘s events

      # File lib/state_machine/machine.rb, line 1350
1350:       def define_event_helpers
1351:         # Gets the events that are allowed to fire on the current object
1352:         define_instance_method(attribute(:events)) do |machine, object|
1353:           machine.events.valid_for(object).map {|event| event.name}
1354:         end
1355:         
1356:         # Gets the next possible transitions that can be run on the current
1357:         # object
1358:         define_instance_method(attribute(:transitions)) do |machine, object, *args|
1359:           machine.events.transitions_for(object, *args)
1360:         end
1361:         
1362:         # Add helpers for interacting with the action
1363:         if action
1364:           # Tracks the event / transition to invoke when the action is called
1365:           event_attribute = attribute(:event)
1366:           event_transition_attribute = attribute(:event_transition)
1367:           @instance_helper_module.class_eval do
1368:             attr_writer event_attribute
1369:             
1370:             protected
1371:               attr_accessor event_transition_attribute
1372:           end
1373:           
1374:           # Interpret non-blank events as present
1375:           define_instance_method(attribute(:event)) do |machine, object|
1376:             event = machine.read(object, :event, true)
1377:             event && !(event.respond_to?(:empty?) && event.empty?) ? event.to_sym : nil
1378:           end
1379:         end
1380:       end
define_helpers()

Adds helper methods for interacting with the state machine, including for states, events, and transitions

      # File lib/state_machine/machine.rb, line 1305
1305:       def define_helpers
1306:         define_state_accessor
1307:         define_state_predicate
1308:         define_event_helpers
1309:         define_action_helpers if action
1310:         
1311:         # Gets the state name for the current value
1312:         define_instance_method(attribute(:name)) do |machine, object|
1313:           machine.states.match!(object).name
1314:         end
1315:       end
define_scopes(custom_plural = nil)

Defines the with/without scope helpers for this attribute. Both the singular and plural versions of the attribute are defined for each scope helper. A custom plural can be specified if it cannot be automatically determined by either calling pluralize on the attribute name or adding an "s" to the end of the name.

      # File lib/state_machine/machine.rb, line 1411
1411:       def define_scopes(custom_plural = nil)
1412:         plural = custom_plural || (name.to_s.respond_to?(:pluralize) ? name.to_s.pluralize : "#{name}s")
1413:         
1414:         [name, plural].uniq.each do |name|
1415:           [:with, :without].each do |kind|
1416:             method = "#{kind}_#{name}"
1417:             
1418:             if scope = send("create_#{kind}_scope", method)
1419:               # Converts state names to their corresponding values so that they
1420:               # can be looked up properly
1421:               define_class_method(method) do |machine, klass, *states|
1422:                 values = states.flatten.map {|state| machine.states.fetch(state).value}
1423:                 scope.call(klass, values)
1424:               end
1425:             end
1426:           end
1427:         end
1428:       end
define_state_accessor()

Adds reader/writer methods for accessing the state attribute

      # File lib/state_machine/machine.rb, line 1332
1332:       def define_state_accessor
1333:         attribute = self.attribute
1334:         
1335:         @instance_helper_module.class_eval do
1336:           attr_accessor attribute
1337:         end
1338:       end
define_state_initializer()

Defines the initial values for state machine attributes. Static values are set prior to the original initialize method and dynamic values are set after the initialize method in case it is dependent on it.

      # File lib/state_machine/machine.rb, line 1320
1320:       def define_state_initializer
1321:         @instance_helper_module.class_eval "def initialize(*args)\ninitialize_state_machines(:dynamic => false)\nsuper\ninitialize_state_machines(:dynamic => true)\nend\n", __FILE__, __LINE__
1322:       end
define_state_predicate()

Adds predicate method to the owner class for determining the name of the current state

      # File lib/state_machine/machine.rb, line 1342
1342:       def define_state_predicate
1343:         define_instance_method("#{name}?") do |machine, object, state|
1344:           machine.states.matches?(object, state)
1345:         end
1346:       end
transaction(object) {|| ...}

Always yields

      # File lib/state_machine/machine.rb, line 1445
1445:       def transaction(object)
1446:         yield
1447:       end