lib/state_machine.rb in state_machine-0.6.3 vs lib/state_machine.rb in state_machine-0.7.0
- old
+ new
@@ -11,23 +11,35 @@
# Configuration options:
# * <tt>:initial</tt> - The initial state of the attribute. This can be a
# static state or a lambda block which will be evaluated at runtime
# (e.g. lambda {|vehicle| vehicle.speed == 0 ? :parked : :idling}).
# Default is nil.
- # * <tt>:action</tt> - The action to invoke when an object transitions.
- # Default is nil unless otherwise specified by the configured integration.
- # * <tt>:plural</tt> - The pluralized name of the attribute. By default,
- # this will attempt to call +pluralize+ on the attribute, otherwise
- # an "s" is appended. This is used for generating scopes.
+ # * <tt>:action</tt> - The instance method to invoke when an object
+ # transitions. Default is nil unless otherwise specified by the
+ # configured integration.
# * <tt>:namespace</tt> - The name to use for namespacing all generated
# instance methods (e.g. "heater" would generate :turn_on_heater and
- # :turn_off_header for the :turn_on/:turn_off events). Default is nil.
+ # :turn_off_heater for the :turn_on/:turn_off events). Default is nil.
# * <tt>:integration</tt> - The name of the integration to use for adding
- # library-specific behavior to the machine. Built-in integrations include
- # :data_mapper, :active_record, and :sequel. By default, this is
- # determined automatically.
+ # library-specific behavior to the machine. Built-in integrations
+ # include :data_mapper, :active_record, and :sequel. By default, this
+ # is determined automatically.
#
+ # Configuration options relevant to ORM integrations:
+ # * <tt>:plural</tt> - The pluralized name of the attribute. By default,
+ # this will attempt to call +pluralize+ on the attribute. If this
+ # method is not available, an "s" is appended. This is used for
+ # generating scopes.
+ # * <tt>:messages</tt> - The error messages to use when invalidating
+ # objects due to failed transitions. Messages include:
+ # * <tt>:invalid</tt>
+ # * <tt>:invalid_event</tt>
+ # * <tt>:invalid_transition</tt>
+ # * <tt>:use_transactions</tt> - Whether transactions should be used when
+ # firing events. Default is true unless otherwise specified by the
+ # configured integration.
+ #
# This also expects a block which will be used to actually configure the
# states, events and transitions for the state machine. *Note* that this
# block will be executed within the context of the state machine. As a
# result, you will not be able to access any class methods unless you refer
# to them directly (i.e. specifying the class name).
@@ -72,45 +84,62 @@
# state_machine :status, :initial => lambda {|vehicle| vehicle.speed == 0 ? :parked : :idling} do
# ...
# end
# end
#
- # == Attribute accessor
+ # == Instance Methods
#
- # The attribute for each machine stores the value for the current state
- # of the machine. In order to access this value and modify it during
- # transitions, a reader/writer must be available. The following methods
- # will be automatically generated if they are not already defined
- # (assuming the attribute is called +state+):
+ # The following instance methods will be automatically generated by the
+ # state machine. Any existing methods will not be overwritten.
# * <tt>state</tt> - Gets the current value for the attribute
# * <tt>state=(value)</tt> - Sets the current value for the attribute
# * <tt>state?(name)</tt> - Checks the given state name against the current
# state. If the name is not a known state, then an ArgumentError is raised.
# * <tt>state_name</tt> - Gets the name of the state for the current value
+ # * <tt>state_events</tt> - Gets the list of events that can be fired on
+ # the current object's state (uses the *unqualified* event names)
+ # * <tt>state_transitions</tt> - Gets the list of possible transitions
+ # that can be made on the current object's state
#
- # For example, the following machine definition will not generate the reader
- # or writer methods since the class has already defined an attribute
- # accessor:
+ # For example,
#
# class Vehicle
- # attr_accessor :state
- #
- # state_machine do
- # ...
+ # state_machine :state, :initial => :parked do
+ # event :ignite do
+ # transition :parked => :idling
+ # end
+ #
+ # event :park do
+ # transition :idling => :parked
+ # end
# end
# end
+ #
+ # vehicle = Vehicle.new
+ # vehicle.state # => "parked"
+ # vehicle.state_name # => :parked
+ # vehicle.state?(:parked) # => true
+ #
+ # # Changing state
+ # vehicle.state = 'idling'
+ # vehicle.state # => "idling"
+ # vehicle.state_name # => :idling
+ # vehicle.state?(:parked) # => false
+ #
+ # # Getting current event / transition availability
+ # vehicle.state_events # => [:park]
+ # vehicle.park # => true
+ # vehicle.state_events # => [:ignite]
+ #
+ # vehicle.state_transitions # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
+ # vehicle.ignite
+ # vehicle.state_transitions # => [#<StateMachine::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>]
+ #
+ # # Include a "no-op" transition for a loopback
+ # vehicle.state_transitions(true) # => [#<StateMachine::Transition attribute=:state event=nil from="idling" from_name=:idling to="idling" to_name=:idling>,
+ # #<StateMachine::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>]
#
- # On the other hand, the following state machine will define *both* a
- # reader and writer method, which is functionally equivalent to the
- # example above:
- #
- # class Vehicle
- # state_machine do
- # ...
- # end
- # end
- #
# == Attribute initialization
#
# For most classes, the initial values for state machine attributes are
# automatically assigned when a new object is created. However, this
# behavior will *not* work if the class defines an +initialize+ method
@@ -210,17 +239,53 @@
# * <tt>idling?</tt>
#
# Each predicate method will return true if it matches the object's
# current state. Otherwise, it will return false.
#
- # When a namespace is configured for a state machine, the name will be
- # prepended to each state predicate like so:
- # * <tt>car_parked?</tt>
- # * <tt>car_idling?</tt>
- #
# == Events and Transitions
#
+ # Events defined on the machine are the interface to transitioning states
+ # for an object. Events can be fired either directly (through the method
+ # generated for the event) or indirectly (through attributes defined on
+ # the machine).
+ #
+ # For example,
+ #
+ # class Vehicle
+ # include DataMapper::Resource
+ # property :id, Integer, :serial => true
+ #
+ # state_machine :initial => :parked do
+ # event :ignite do
+ # transition :parked => :idling
+ # end
+ # end
+ #
+ # state_machine :alarm_state, :initial => :active do
+ # event :disable do
+ # transition all => :off
+ # end
+ # end
+ # end
+ #
+ # # Fire +ignite+ event directly
+ # vehicle = Vehicle.create # => #<Vehicle id=1 state="parked" alarm_state="active">
+ # vehicle.ignite # => true
+ # vehicle.state # => "idling"
+ # vehicle.alarm_state # => "active"
+ #
+ # # Fire +disable+ event automatically
+ # vehicle.alarm_state_event = 'disable'
+ # vehicle.save # => true
+ # vehicle.alarm_state # => "off"
+ #
+ # In the above example, the +state+ attribute is transitioned using the
+ # +ignite+ action that's generated from the state machine. On the other
+ # hand, the +alarm_state+ attribute is transitioned using the +alarm_state_event+
+ # attribute that automatically gets fired when the machine's action (+save+)
+ # is invoked.
+ #
# For more information about how to configure an event and its associated
# transitions, see StateMachine::Machine#event.
#
# == Defining callbacks
#
@@ -229,12 +294,12 @@
# see StateMachine::Machine#before_transition and
# StateMachine::Machine#after_transition.
#
# == Namespaces
#
- # When a namespace is configured for a state machine, the name provided will
- # be used in generating the instance methods for interacting with
+ # When a namespace is configured for a state machine, the name provided
+ # will be used in generating the instance methods for interacting with
# events/states in the machine. This is particularly useful when a class
# has multiple state machines and it would be difficult to differentiate
# between the various states / events.
#
# For example,
@@ -248,22 +313,22 @@
# event :turn_off do
# transition all => :off
# end
# end
#
- # state_machine :hood_state, :initial => :closed, :namespace => 'hood' do
- # event :open do
- # transition all => :opened
+ # state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do
+ # event :turn_on do
+ # transition all => :active
# end
#
- # event :close do
- # transition all => :closed
+ # event :turn_off do
+ # transition all => :off
# end
# end
# end
#
- # The above class defines two state machines: +heater_state+ and +hood_state+.
+ # The above class defines two state machines: +heater_state+ and +alarm_state+.
# For the +heater_state+ machine, the following methods are generated since
# it's namespaced by "heater":
# * <tt>can_turn_on_heater?</tt>
# * <tt>turn_on_heater</tt>
# * ...
@@ -272,32 +337,32 @@
# * ..
# * <tt>heater_off?</tt>
# * <tt>heater_on?</tt>
#
# As shown, each method is unique to the state machine so that the states
- # and events don't conflict. The same goes for the +hood_state+ machine:
- # * <tt>can_open_hood?</tt>
- # * <tt>open_hood</tt>
+ # and events don't conflict. The same goes for the +alarm_state+ machine:
+ # * <tt>can_turn_on_alarm?</tt>
+ # * <tt>turn_on_alarm</tt>
# * ...
- # * <tt>can_close_hood?</tt>
- # * <tt>close_hood</tt>
+ # * <tt>can_turn_off_alarm?</tt>
+ # * <tt>turn_off_alarm</tt>
# * ..
- # * <tt>hood_open?</tt>
- # * <tt>hood_closed?</tt>
+ # * <tt>alarm_active?</tt>
+ # * <tt>alarm_off?</tt>
#
# == Scopes
#
# For integrations that support it, a group of default scope filters will
# be automatically created for assisting in finding objects that have the
# attribute set to the value for a given set of states.
#
# For example,
#
- # Vehicle.with_state(:parked) # => Finds all vehicles where the state is parked
- # Vehicle.with_states(:parked, :idling) # => Finds all vehicles where the state is either parked or idling
+ # Vehicle.with_state(:parked) # => All vehicles where the state is parked
+ # Vehicle.with_states(:parked, :idling) # => All vehicles where the state is either parked or idling
#
- # Vehicle.without_state(:parked) # => Finds all vehicles where the state is *not* parked
- # Vehicle.without_states(:parked, :idling) # => Finds all vehicles where the state is *not* parked or idling
+ # Vehicle.without_state(:parked) # => All vehicles where the state is *not* parked
+ # Vehicle.without_states(:parked, :idling) # => All vehicles where the state is *not* parked or idling
#
# *Note* that if class methods already exist with those names (i.e.
# :with_state, :with_states, :without_state, or :without_states), then a
# scope will not be defined for that name.
#