lib/state_machine.rb in state_machine-0.4.1 vs lib/state_machine.rb in state_machine-0.4.2
- old
+ new
@@ -7,20 +7,21 @@
module MacroMethods
# Creates a new state machine for the given attribute. The default
# attribute, if not specified, is "state".
#
# Configuration options:
- # * +initial+ - The initial value to set the attribute to. This can be a static value or a dynamic proc which will be evaluated at runtime. Default is nil.
+ # * +initial+ - The initial value to set the attribute to. This can be a static value or a lambda block which will be evaluated at runtime. Default is nil.
# * +action+ - The action to invoke when an object transitions. Default is nil unless otherwise specified by the configured integration.
# * +plural+ - The pluralized name of the attribute. By default, this will attempt to call +pluralize+ on the attribute, otherwise an "s" is appended.
- # * +integration+ - The name of the integration to use for adding library-specific behavior to the machine. Built-in integrations include :data_mapper and :active_record. By default, this is determined automatically.
+ # * +namespace+ - The name to use for namespacing all generated instance methods (e.g. "email" => "activate_email", "deactivate_email", etc.). Default is no namespace.
+ # * +integration+ - 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.
#
# This also requires a block which will be used to actually configure the
- # 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).
+ # 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).
#
# For examples on the types of configured state machines and blocks, see
# the section below.
#
# == Examples
@@ -93,20 +94,96 @@
# 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
+ # without properly calling +super+.
+ #
+ # For example,
+ #
+ # class Vehicle
+ # state_machine :state, :initial => 'parked' do
+ # ...
+ # end
+ # end
+ #
+ # v = Vehicle.new # => #<Vehicle:0xb7c8dbf8 @state="parked">
+ # v.state # => "parked"
+ #
+ # In the above example, no +initialize+ method is defined. As a result,
+ # the default behavior of initializing the state machine attributes is used.
+ #
+ # In the following example, a custom +initialize+ method is defined:
+ #
+ # class Vehicle
+ # state_machine :state, :initial => 'parked' do
+ # ...
+ # end
+ #
+ # def initialize
+ # end
+ # end
+ #
+ # v = Vehicle.new # => #<Vehicle:0xb7c77678>
+ # v.state # => nil
+ #
+ # Since the +initialize+ method is defined, the state machine attributes
+ # never get initialized. In order to ensure that all initialization hooks
+ # are called, the custom method *must* call +super+ without any arguments
+ # like so:
+ #
+ # class Vehicle
+ # state_machine :state, :initial => 'parked' do
+ # ...
+ # end
+ #
+ # def initialize(attributes = {})
+ # ...
+ # super()
+ # end
+ # end
+ #
+ # v = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
+ # v.state # => "parked"
+ #
+ # Because of the way the inclusion of modules works in Ruby, calling <tt>super()</tt>
+ # will not only call the superclass's +initialize+, but also +initialize+ on
+ # all included modules. This allows the original state machine hook to get
+ # called properly.
+ #
+ # If you want to avoid calling the superclass's constructor, but still want
+ # to initialize the state machine attributes:
+ #
+ # class Vehicle
+ # state_machine :state, :initial => 'parked' do
+ # ...
+ # end
+ #
+ # def initialize(attributes = {})
+ # ...
+ # initialize_state_machines
+ # end
+ # end
+ #
+ # v = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
+ # v.state # => "parked"
+ #
# == States
#
# All of the valid states for the machine are automatically tracked based
# on the events, transitions, and callbacks defined for the machine. If
# there are additional states that are never referenced, these should be
# explicitly added using the StateMachine::Machine#other_states
# helper.
#
- # For each state tracked, a predicate method for that state is generated
- # on the class. For example,
+ # When using String or Symbol-based states, a predicate method for that
+ # state is generated on the class. For example,
#
# class Vehicle
# state_machine :initial => 'parked' do
# event :ignite do
# transition :to => 'idling'
@@ -120,20 +197,80 @@
# * <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
#
# For more information about how to configure an event and its associated
# transitions, see StateMachine::Machine#event.
#
# == Defining callbacks
#
# Within the +state_machine+ block, you can also define callbacks for
- # particular states. For more information about defining these callbacks,
+ # transitions. For more information about defining these callbacks,
# 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
+ # 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,
+ #
+ # class Vehicle
+ # state_machine :heater_state, :initial => 'off' :namespace => 'heater' do
+ # event :turn_on do
+ # transition :to => 'on', :from => 'off'
+ # end
+ #
+ # event :turn_off do
+ # transition :to => 'off', :from => 'on'
+ # end
+ # end
+ #
+ # state_machine :hood_state, :initial => 'closed', :namespace => 'hood' do
+ # event :open do
+ # transition :to => 'opened', :from => 'closed'
+ # end
+ #
+ # event :close do
+ # transition :to => 'closed', :from => 'opened'
+ # end
+ # end
+ # end
+ #
+ # The above class defines two state machines: +heater_state+ and +hood_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>
+ # * ...
+ # * <tt>can_turn_off_heater?</tt>
+ # * <tt>turn_off_heater</tt>
+ # * ..
+ # * <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>
+ # * ...
+ # * <tt>can_close_hood?</tt>
+ # * <tt>close_hood</tt>
+ # * ..
+ # * <tt>hood_open?</tt>
+ # * <tt>hood_closed?</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