Pastie now auto-senses if line-wrap is a bad or good idea. Feedback?
## mark a section (Learn more)
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
This paste will be private.
From the Design Piracy series on my blog: