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. #