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