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: <tt>order.close_order!</tt>.
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