Report abuse


			
Index: _logging_etc_comments.txt
===================================================================
--- _logging_etc_comments.txt	(revision 0)
+++ _logging_etc_comments.txt	(revision 0)
@@ -0,0 +1,21 @@
+I made some changes to AASM, here they are.
+
+Blog article with description and examples of client code:
+http://thirstydoh.wordpress.com/2007/12/05/aasm-improvements/
+
+What the patch does:
+
+- acts_as_state_machine method now accepts optional :log_transitions flag telling whether to fire transition logging (false by default) and :transitions_logger specifying client method that should do it (:log_transition by default)
+
+- StateTransition#perform now wraps some callback actions in transaction. I made it mainly to log the transition inside the same transaction as transiition itself; other callbacks like :after also can benefit from it
+
+- class State now exposes its options, to be able to use custom metadata link to it like :owner in my example:
+
+state States::REJECTED_BY_FINANCE, :owner => Roles::APPROVING_MANAGER 
+
+- acts_as_state_machine now injects states_table attribute to client class to access state metadata easily, without having to do frightening self.class.read_inheritable_attribute(:states). It couldn't be just 'states' attribute because there's already one giving just array of state symbols.
+
+- acts_as_state_machine method now writes :state_events_table attribute to client class allowing to get event list for given state in the order they specified
+
+Yours sinserely,
+Artem Vasiliev
Index: lib/acts_as_state_machine.rb
===================================================================
--- lib/acts_as_state_machine.rb	(revision 66)
+++ lib/acts_as_state_machine.rb	(working copy)
@@ -12,7 +12,7 @@

       module SupportingClasses
         class State
-          attr_reader :name
+          attr_reader :name, :opts

           def initialize(name, opts)
             @name, @opts = name, opts
@@ -41,29 +41,38 @@
           attr_reader :from, :to, :opts

           def initialize(opts)
-            @from, @to, @guard = opts[:from], opts[:to], opts[:guard]
+            @from, @to, @event, @guard = opts[:from], opts[:to], opts[:event], opts[:guard]
             @opts = opts
           end

           def guard(obj)
             @guard ? obj.send(:run_transition_action, @guard) : true
           end
-
+                    
           def perform(record)
-            return false unless guard(record)
+            valid = true
+            valid = record.valid? if record.class.read_inheritable_attribute(:validate_on_transitions)
+            transition_valid = guard(record)
+            return false unless transition_valid && valid
             loopback = record.current_state == to
             states = record.class.read_inheritable_attribute(:states)
             next_state = states[to]
             old_state = states[record.current_state]
-          
-            next_state.entering(record) unless loopback
-          
-            record.update_attribute(record.class.state_column, to.to_s)
-          
-            next_state.entered(record) unless loopback
-            old_state.exited(record) unless loopback
-            true
+
+            record.class.transaction do
+              next_state.entering(record) unless loopback
+
+              record.update_attribute(record.class.state_column, to.to_s)
+              if record.class.read_inheritable_attribute(:log_transitions)
+                record.send(record.class.read_inheritable_attribute(:transitions_logger), @from, @to, @event, opts)
+              end
+
+              next_state.entered(record) unless loopback
+              old_state.exited(record) unless loopback
+              true
+            end
           end
+

           def ==(obj)
             @from == obj.from && @to == obj.to
@@ -75,10 +84,13 @@
           attr_reader :transitions
           attr_reader :opts

-          def initialize(name, opts, transition_table, &block)
+          def initialize(name, opts, transition_table, state_events_table, &block)
             @name = name.to_sym
             @transitions = transition_table[@name] = []
             instance_eval(&block) if block
+            @transitions.each do |tr|
+              state_events_table[tr.from] << @name
+            end
             @opts = opts
             @opts.freeze
             @transitions.freeze
@@ -90,19 +102,21 @@
           end

           def fire(record)
-            next_states(record).each do |transition|
+            result = next_states(record).each do |transition|
               break true if transition.perform(record)
             end
+            raise ActiveRecord::RecordInvalid.new(record) unless result == true
+            true
           end

           def transitions(trans_opts)
             Array(trans_opts[:from]).each do |s|
-              @transitions << SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym}))
+              @transitions << SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym, :event => @name}))
             end
           end
         end
       end
-      
+
       module ActMacro
         # Configuration options are
         #
@@ -113,16 +127,21 @@
           raise NoInitialState unless opts[:initial]

           write_inheritable_attribute :states, {}
+          write_inheritable_attribute :state_events_table, {}          
           write_inheritable_attribute :initial_state, opts[:initial]
           write_inheritable_attribute :transition_table, {}
           write_inheritable_attribute :event_table, {}
           write_inheritable_attribute :state_column, opts[:column] || 'state'
-          
+          write_inheritable_attribute :log_transitions, opts[:log_transitions] || false 
+          write_inheritable_attribute :transitions_logger, opts[:transitions_logger] || :log_transition
+          write_inheritable_attribute :validate_on_transitions, opts[:validate_on_transitions] || false
+
           class_inheritable_reader    :initial_state
           class_inheritable_reader    :state_column
           class_inheritable_reader    :transition_table
           class_inheritable_reader    :event_table
-          
+          class_inheritable_reader    :state_events_table
+
           self.send(:include, ScottBarron::Acts::StateMachine::InstanceMethods)

           before_create               :set_initial_state
@@ -169,7 +188,11 @@
         def states
           read_inheritable_attribute(:states).keys
         end
-        
+
+        def states_table
+          read_inheritable_attribute(:states)
+        end
+
         # Define an event.  This takes a block which describes all valid transitions
         # for this event.
         #
@@ -195,9 +218,10 @@
         # Example: order.close_order!.
         def event(event, opts={}, &block)
           tt = read_inheritable_attribute(:transition_table)
+          state_events_table = read_inheritable_attribute(:state_events_table)          

           et = read_inheritable_attribute(:event_table)
-          e = et[event.to_sym] = SupportingClasses::Event.new(event, opts, tt, &block)
+          e = et[event.to_sym] = SupportingClasses::Event.new(event, opts, tt, state_events_table, &block)
           define_method("#{event.to_s}!") { e.fire(self) }
         end

@@ -216,6 +240,8 @@
         def state(name, opts={})
           state = SupportingClasses::State.new(name.to_sym, opts)
           read_inheritable_attribute(:states)[name.to_sym] = state
+
+          state_events_table[name.to_sym] = []

           define_method("#{state.name}?") { current_state == state.name }
         end