state_machine
https://github.com/pluginaweek/state_machine
Ruby
Adds support for creating state machines for attributes on any Ruby class
StateMachine::Machine#on
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
endEdit
git clone [email protected]:pluginaweek/state_machine.git
cd state_machine
open lib/state_machine/machine.rb
Contribute
# Make a new branchgit checkout -b -your-name--update-docs-StateMachine--Machine-on-for-pr
# Commit to gitgit add lib/state_machine/machine.rbgit commit -m "better docs for StateMachine::Machine#on"
# Open pull requestgem install hub # on a mac you can `brew install hub`
hub fork
git push <your name> -your-name--update-docs-StateMachine--Machine-on-for-pr
hub pull-request
# Celebrate!