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