lib/state_machine/machine.rb in state_machine-0.7.4 vs lib/state_machine/machine.rb in state_machine-0.7.5
- old
+ new
@@ -67,10 +67,50 @@
# As shown, even though the state is set prior to calling the +save+ action
# on the object, it will be rolled back to the original state if the action
# fails. *Note* that this will also be the case if an exception is raised
# while calling the action.
#
+ # === Indirect transitions
+ #
+ # In addition to the action being run as the _result_ of an event, the action
+ # can also be used to run events itself. For example, using the above as an
+ # example:
+ #
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7c27024 @state="parked">
+ #
+ # vehicle.state_event = 'ignite'
+ # vehicle.save # => true
+ # vehicle.state # => "idling"
+ # vehicle.state_event # => nil
+ #
+ # As can be seen, the +save+ action automatically invokes the event stored in
+ # the +state_event+ attribute (<tt>:ignite</tt> in this case).
+ #
+ # One important note about using this technique for running transitions is
+ # that if the class in which the state machine is defined *also* defines the
+ # action being invoked (and not a superclass), then it must manually run the
+ # StateMachine hook that checks for event attributes.
+ #
+ # For example, in ActiveRecord, DataMapper, and Sequel, the default action
+ # (+save+) is already defined in a base class. As a result, when a state
+ # machine is defined in a model / resource, StateMachine can automatically
+ # hook into the +save+ action.
+ #
+ # On the other hand, the Vehicle class from above defined its own +save+
+ # method (and there is no +save+ method in its superclass). As a result, it
+ # must be modified like so:
+ #
+ # def save
+ # self.class.state_machines.fire_event_attributes(self, :save) do
+ # @saving_state = state
+ # fail != true
+ # end
+ # end
+ #
+ # This will add in the functionality for firing the event stored in the
+ # +state_event+ attribute.
+ #
# == Callbacks
#
# Callbacks are supported for hooking before and after every possible
# transition in the machine. Each callback is invoked in the order in which
# it was defined. See StateMachine::Machine#before_transition
@@ -525,10 +565,12 @@
# Customizes the definition of one or more states in the machine.
#
# Configuration options:
# * <tt>:value</tt> - The actual value to store when an object transitions
# to the state. Default is the name (stringified).
+ # * <tt>:cache</tt> - If a dynamic value (via a lambda block) is being used,
+ # then setting this to true will cache the evaluated result
# * <tt>:if</tt> - Determines whether an object's value matches the state
# (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}).
# By default, the configured value is matched.
#
# == Customizing the stored value
@@ -577,17 +619,36 @@
# state_machine :state_id, :initial => :parked do
# event :ignite do
# transition :parked => :idling
# end
#
- # states.each {|state| self.state(state.name, :value => VehicleState.find_by_name(state.name.to_s).id)}
+ # states.each do |state|
+ # self.state(state.name, :value => lambda { VehicleState.find_by_name(state.name.to_s).id }, :cache => true)
+ # end
# end
# end
#
# In the above example, each known state is configured to store it's
- # associated database id in the +state_id+ attribute.
+ # associated database id in the +state_id+ attribute. Also, notice that a
+ # lambda block is used to define the state's value. This is required in
+ # situations (like testing) where the model is loaded without any existing
+ # data (i.e. no VehicleState records available).
#
+ # One caveat to the above example is to keep performance in mind. To avoid
+ # constant db hits for looking up the VehicleState ids, the value is cached
+ # by specifying the <tt>:cache</tt> option. Alternatively, a custom
+ # caching strategy can be used like so:
+ #
+ # class VehicleState < ActiveRecord::Base
+ # cattr_accessor :cache_store
+ # self.cache_store = ActiveSupport::Cache::MemoryStore.new
+ #
+ # def self.find_by_name(name)
+ # cache_store.fetch(name) { find(:first, :conditions => {:name => name}) }
+ # end
+ # end
+ #
# === Dynamic values
#
# In addition to customizing states with other value types, lambda blocks
# can also be specified to allow for a state's value to be determined
# dynamically at runtime. For example,
@@ -743,19 +804,20 @@
#
# The minimum requirement is that the last argument in the method be an
# options hash which contains at least <tt>:if</tt> condition support.
def state(*names, &block)
options = names.last.is_a?(Hash) ? names.pop : {}
- assert_valid_keys(options, :value, :if)
+ assert_valid_keys(options, :value, :cache, :if)
states = add_states(names)
states.each do |state|
if options.include?(:value)
state.value = options[:value]
self.states.update(state)
end
+ state.cache = options[:cache] if options.include?(:cache)
state.matcher = options[:if] if options.include?(:if)
state.context(&block) if block_given?
end
states.length == 1 ? states.first : states
@@ -1275,12 +1337,10 @@
if (owner_class.method_defined?(action_hook) || private_method) && !owner_class.state_machines.any? {|attribute, machine| machine.action == action && machine != self}
# Action is defined and hasn't already been overridden by another machine
@instance_helper_module.class_eval do
# Override the default action to invoke the before / after hooks
define_method(action_hook) do |*args|
- value = nil
- result = self.class.state_machines.fire_attribute_events(self, action) { value = super(*args) }
- value.nil? ? result : value
+ self.class.state_machines.fire_event_attributes(self, action) { super(*args) }
end
private action_hook if private_method
end