state_machine

https://github.com/pluginaweek/state_machine

Ruby

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

StateMachine::Machine#event

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.

Configuration options:
* <tt>:human_name</tt> - The human-readable version of this event's name.
  By default, this is either defined by the integration or stringifies the
  name and converts underscores to spaces.

== Instance methods

The following instance methods are generated when a new event is defined
(the "park" event is used as an example):
* <tt>park(..., run_action = true)</tt> - Fires the "park" event,
  transitioning from the current state to the next valid state.  If the
  last argument is a boolean, it will control whether the machine's action
  gets run.
* <tt>park!(..., run_action = true)</tt> - 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.  If the last argument is a boolean, it will control whether the
  machine's action gets run.
* <tt>can_park?(requirements = {})</tt> - Checks whether the "park" event
  can be fired given the current state of the object.  This will *not* run
  validations or callbacks in ORM integrations.  It will only determine if
  the state machine defines a valid transition for the event.  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.
* <tt>park_transition(requirements = {})</tt> -  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.  Like <tt>can_park?</tt>
  this will also *not* run validations or callbacks.  It will only
  determine if the state machine defines a valid transition for the event.

With a namespace of "car", the above names map to the following methods:
* <tt>can_park_car?</tt>
* <tt>park_car_transition</tt>
* <tt>park_car</tt>
* <tt>park_car!</tt>

The <tt>can_park?</tt> and <tt>park_transition</tt> helpers both take an
optional set of requirements for determining what transitions are available
for the current object.  These requirements include:
* <tt>:from</tt> - One or more states to transition from.  If none are
  specified, then this will be the object's current state.
* <tt>:to</tt> - One or more states to transition to.  If none are
  specified, then this will match any to state.
* <tt>:guard</tt> - Whether to guard transitions with the if/unless
  conditionals defined for each one.  Default is true.

== 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?
    transition :parked => same # Allow to loopback if seatbelt is off
  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 

== Overriding the event method

By default, this will define an instance method (with the same name as the
event) that will fire the next possible transition for that.  Although the
+before_transition+, +after_transition+, and +around_transition+ hooks
allow you to define behavior that gets executed as a result of the event's
transition, you can also override the event method in order to have a
little more fine-grained control.

For example:

  class Vehicle
    state_machine do
      event :park do
        ...
      end
    end
    
    def park(*)
      take_deep_breath  # Executes before the transition (and before_transition hooks) even if no transition is possible
      if result = super # Runs the transition and all before/after/around hooks
        applaud         # Executes after the transition (and after_transition hooks)
      end
      result
    end
  end

There are a few important things to note here.  First, the method
signature is defined with an unlimited argument list in order to allow
callers to continue passing arguments that are expected by state_machine.
For example, it will still allow calls to +park+ with a single parameter
for skipping the configured action.

Second, the overridden event method must call +super+ in order to run the
logic for running the next possible transition.  In order to remain
consistent with other events, the result of +super+ is returned.

Third, any behavior defined in this method will *not* get executed if
you're taking advantage of attribute-based event transitions.  For example:

  vehicle = Vehicle.new
  vehicle.state_event = 'park'
  vehicle.save

In this case, the +park+ event will run the before/after/around transition
hooks and transition the state, but the behavior defined in the overriden
+park+ method will *not* be executed.

== Defining additional arguments

Additional arguments can be passed into events and accessed by transition
hooks like so:

  class Vehicle
    state_machine do
      after_transition :on => :park do |vehicle, transition|
        kind = *transition.args # :parallel
        ...
      end
      after_transition :on => :park, :do => :take_deep_breath
      
      event :park do
        ...
      end
      
      def take_deep_breath(transition)
        kind = *transition.args # :parallel
        ...
      end
    end
  end
  
  vehicle = Vehicle.new
  vehicle.park(:parallel)

*Remember* that if the last argument is a boolean, it will be used as the
+run_action+ parameter to the event action.  Using the +park+ action
example from above, you can might call it like so:

  vehicle.park                    # => Uses default args and runs machine action
  vehicle.park(:parallel)         # => Specifies the +kind+ argument and runs the machine action
  vehicle.park(:parallel, false)  # => Specifies the +kind+ argument and *skips* the machine action

If you decide to override the +park+ event method *and* define additional
arguments, you can do so as shown below:

  class Vehicle
    state_machine do
      event :park do
        ...
      end
    end
    
    def park(kind = :parallel, *args)
      take_deep_breath if kind == :parallel
      super
    end
  end

Note that +super+ is called instead of <tt>super(*args)</tt>.  This allow
the entire arguments list to be accessed by transition callbacks through
StateMachine::Transition#args.

=== Using matchers

The +all+ / +any+ matchers can be used to easily execute blocks for a
group of events.  Note, however, that you cannot use these matchers to
set configurations for events.  Blocks using these matchers can be
defined at any point in the state machine and will always get applied to
the proper events.

For example:

  state_machine :initial => :parked do
    ...
    
    event all - [:crash] do
      transition :stalled => :parked
    end
  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
        transition :idling => same # Allow ignite while still idling
      end
    end
  end

Source | Google | Stack overflow

Edit

git clone [email protected]:pluginaweek/state_machine.git

cd state_machine

open lib/state_machine/machine.rb

Contribute

# Make a new branch

git checkout -b -your-name--update-docs-StateMachine--Machine-event-for-pr


# Commit to git

git add lib/state_machine/machine.rbgit commit -m "better docs for StateMachine::Machine#event"


# Open pull request

gem install hub # on a mac you can `brew install hub`

hub fork

git push <your name> -your-name--update-docs-StateMachine--Machine-event-for-pr

hub pull-request


# Celebrate!