edge-state-machine 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +12 -9
- data/lib/active_record/edge-state-machine.rb +54 -9
- data/lib/edge-state-machine/event.rb +20 -91
- data/lib/edge-state-machine/exception.rb +8 -0
- data/lib/edge-state-machine/machine.rb +23 -68
- data/lib/edge-state-machine/state.rb +25 -23
- data/lib/edge-state-machine/transition.rb +32 -25
- data/lib/edge-state-machine/version.rb +1 -1
- data/lib/edge-state-machine.rb +51 -36
- data/lib/mongoid/edge-state-machine.rb +54 -9
- data/spec/active_record/active_record_helper.rb +2 -0
- data/spec/active_record/active_record_spec.rb +36 -144
- data/spec/active_record/samples/order.rb +47 -0
- data/spec/active_record/samples/traffic_light.rb +46 -0
- data/spec/event_spec.rb +13 -21
- data/spec/machine_spec.rb +11 -14
- data/spec/mongoid/mongoid_helper.rb +4 -2
- data/spec/mongoid/mongoid_spec.rb +35 -153
- data/spec/mongoid/samples/order.rb +54 -0
- data/spec/mongoid/samples/traffic_light.rb +46 -0
- data/spec/non_persistent/non_persistent_helper.rb +9 -0
- data/spec/non_persistent/non_persistent_spec.rb +80 -0
- data/spec/non_persistent/samples/dice.rb +31 -0
- data/spec/non_persistent/samples/microwave.rb +54 -0
- data/spec/non_persistent/samples/user.rb +23 -0
- data/spec/state_spec.rb +19 -14
- data/spec/transition_spec.rb +69 -31
- metadata +24 -14
    
        data/README.rdoc
    CHANGED
    
    | @@ -1,14 +1,9 @@ | |
| 1 | 
            -
            = Travis Build Status
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            {<img src="https://secure.travis-ci.org/danpersa/edge-state-machine.png"/>}[http://travis-ci.org/danpersa/edge-state-machine]
         | 
| 4 | 
            -
             | 
| 5 1 | 
             
            = Edge State Machine
         | 
| 6 2 |  | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
            commit}[http://github.com/rails/rails/commit/db49c706b62e7ea2ab93f05399dbfddf5087ee0c].
         | 
| 3 | 
            +
            Edge state machine wants to be a complete state machine solution.
         | 
| 4 | 
            +
            It offers support for ActiveRecord and Mongoid
         | 
| 10 5 |  | 
| 11 | 
            -
             | 
| 6 | 
            +
            {<img src="https://secure.travis-ci.org/danpersa/edge-state-machine.png"/>}[http://travis-ci.org/danpersa/edge-state-machine]
         | 
| 12 7 |  | 
| 13 8 | 
             
            == Installation
         | 
| 14 9 |  | 
| @@ -60,4 +55,12 @@ If you're using Rails + Mongoid + Bundler | |
| 60 55 | 
             
                event :available do
         | 
| 61 56 | 
             
                  transitions :to => :available, :from => [:out_of_stock], :on_transition => :send_alerts
         | 
| 62 57 | 
             
                end
         | 
| 63 | 
            -
              end
         | 
| 58 | 
            +
              end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            = Credits
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            The gem is based on Rick Olson's code of ActiveModel::StateMachine,
         | 
| 63 | 
            +
            axed from ActiveModel in {this
         | 
| 64 | 
            +
            commit}[http://github.com/rails/rails/commit/db49c706b62e7ea2ab93f05399dbfddf5087ee0c].
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            And on Krzysiek Heród's gem, {Transitions}[https://github.com/netizer/transitions], which added Mongoid support.
         | 
| @@ -5,28 +5,73 @@ module ActiveRecord | |
| 5 5 | 
             
                included do
         | 
| 6 6 | 
             
                  include ::EdgeStateMachine
         | 
| 7 7 | 
             
                  after_initialize :set_initial_state
         | 
| 8 | 
            -
                   | 
| 8 | 
            +
                  validate :state_variables_presence
         | 
| 9 9 | 
             
                  validate :state_inclusion
         | 
| 10 10 | 
             
                end
         | 
| 11 11 |  | 
| 12 | 
            +
                # The optional options argument is passed to find when reloading so you may
         | 
| 13 | 
            +
                # do e.g. record.reload(:lock => true) to reload the same record with an
         | 
| 14 | 
            +
                # exclusive row lock.
         | 
| 15 | 
            +
                def reload(options = nil)
         | 
| 16 | 
            +
                  super.tap do
         | 
| 17 | 
            +
                    @current_states = {}
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 12 21 | 
             
                protected
         | 
| 13 22 |  | 
| 14 | 
            -
                def  | 
| 15 | 
            -
                   | 
| 16 | 
            -
                   | 
| 23 | 
            +
                def load_from_persistence(machine_name)
         | 
| 24 | 
            +
                  machine = self.class.state_machines[machine_name]
         | 
| 25 | 
            +
                  send machine.persisted_variable_name.to_s
         | 
| 17 26 | 
             
                end
         | 
| 18 27 |  | 
| 19 | 
            -
                def  | 
| 20 | 
            -
                  self. | 
| 28 | 
            +
                def save_to_persistence(new_state, machine_name, options = {})
         | 
| 29 | 
            +
                  machine = self.class.state_machines[machine_name]
         | 
| 30 | 
            +
                  send("#{machine.persisted_variable_name}=".to_sym, new_state)
         | 
| 31 | 
            +
                  save! if options[:save]
         | 
| 21 32 | 
             
                end
         | 
| 22 33 |  | 
| 23 34 | 
             
                def set_initial_state
         | 
| 24 | 
            -
                   | 
| 35 | 
            +
                  # set the initial state for each state machine in this class
         | 
| 36 | 
            +
                  self.class.state_machines.keys.each do |machine_name|
         | 
| 37 | 
            +
                    machine = self.class.state_machines[machine_name]
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    if persisted_variable_value(machine.persisted_variable_name).blank?
         | 
| 40 | 
            +
                      if load_from_persistence(machine_name)
         | 
| 41 | 
            +
                        send("#{machine.persisted_variable_name}=".to_sym, load_from_persistence(machine_name))
         | 
| 42 | 
            +
                      else
         | 
| 43 | 
            +
                        send("#{machine.persisted_variable_name}=".to_sym, machine.initial_state_name)
         | 
| 44 | 
            +
                      end
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def persisted_variable_value(name)
         | 
| 50 | 
            +
                  send(name.to_s)
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def state_variables_presence
         | 
| 54 | 
            +
                  # validate that state is in the right set of values
         | 
| 55 | 
            +
                  self.class.state_machines.keys.each do |machine_name|
         | 
| 56 | 
            +
                    machine = self.class.state_machines[machine_name]
         | 
| 57 | 
            +
                    validates_presence_of machine.persisted_variable_name.to_sym
         | 
| 58 | 
            +
                  end
         | 
| 25 59 | 
             
                end
         | 
| 26 60 |  | 
| 27 61 | 
             
                def state_inclusion
         | 
| 28 | 
            -
                   | 
| 29 | 
            -
             | 
| 62 | 
            +
                  # validate that state is in the right set of values
         | 
| 63 | 
            +
                  self.class.state_machines.keys.each do |machine_name|
         | 
| 64 | 
            +
                    machine = self.class.state_machines[machine_name]
         | 
| 65 | 
            +
                    unless machine.states.keys.include?(persisted_variable_value(machine.persisted_variable_name).to_sym)
         | 
| 66 | 
            +
                      self.errors.add(machine.persisted_variable_name.to_sym, :inclusion, :value => persisted_variable_value(machine.persisted_variable_name))
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                module ClassMethods
         | 
| 72 | 
            +
                  def add_scope(state, machine_name)
         | 
| 73 | 
            +
                    machine = state_machines[machine_name]
         | 
| 74 | 
            +
                    scope state.name, where(machine.persisted_variable_name.to_sym => state.name.to_s)
         | 
| 30 75 | 
             
                  end
         | 
| 31 76 | 
             
                end
         | 
| 32 77 | 
             
              end
         | 
| @@ -2,106 +2,35 @@ module EdgeStateMachine | |
| 2 2 | 
             
              class Event
         | 
| 3 3 | 
             
                attr_reader :name, :success, :timestamp
         | 
| 4 4 |  | 
| 5 | 
            -
                def initialize( | 
| 6 | 
            -
                  @machine | 
| 7 | 
            -
                   | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
                    end
         | 
| 11 | 
            -
             | 
| 12 | 
            -
                    machine.klass.send(:define_method, name.to_s) do |*args|
         | 
| 13 | 
            -
                      machine.fire_event(name, self, false, *args)
         | 
| 14 | 
            -
                    end
         | 
| 15 | 
            -
                  end
         | 
| 16 | 
            -
                  update(options, &block)
         | 
| 5 | 
            +
                def initialize(name, machine, &transitions)
         | 
| 6 | 
            +
                  @machine = machine
         | 
| 7 | 
            +
                  @name = name
         | 
| 8 | 
            +
                  @transitions = []
         | 
| 9 | 
            +
                  instance_eval(&transitions) if block_given?
         | 
| 17 10 | 
             
                end
         | 
| 18 11 |  | 
| 19 | 
            -
                def fire(obj,  | 
| 20 | 
            -
                   | 
| 21 | 
            -
                   | 
| 12 | 
            +
                def fire(obj, options = {})
         | 
| 13 | 
            +
                  current_state = obj.current_state(@machine.name)
         | 
| 14 | 
            +
                  transition = @transitions.select{ |t| t.from.include? current_state.name }.first
         | 
| 22 15 |  | 
| 23 | 
            -
                   | 
| 24 | 
            -
                   | 
| 25 | 
            -
                    next if to_state && !Array(transition.to).include?(to_state)
         | 
| 26 | 
            -
                    if transition.perform(obj)
         | 
| 27 | 
            -
                      next_state = to_state || Array(transition.to).first
         | 
| 28 | 
            -
                      transition.execute(obj, *args)
         | 
| 29 | 
            -
                      break
         | 
| 30 | 
            -
                    end
         | 
| 31 | 
            -
                  end
         | 
| 32 | 
            -
                  # Update timestamps on obj if a timestamp has been defined
         | 
| 33 | 
            -
                  update_event_timestamp(obj, next_state) if timestamp_defined?
         | 
| 34 | 
            -
                  next_state
         | 
| 35 | 
            -
                end
         | 
| 16 | 
            +
                  raise NoTransitionFound.new("No transition found for event #{@name}") if transition.nil?
         | 
| 17 | 
            +
                  return false unless transition.possible?(obj)
         | 
| 36 18 |  | 
| 37 | 
            -
             | 
| 38 | 
            -
                   | 
| 39 | 
            -
             | 
| 19 | 
            +
                  next_state = @machine.states[transition.find_next_state(obj)]
         | 
| 20 | 
            +
                  raise NoStateFound.new("Invalid state #{transition.to.to_s} for transition.") if next_state.nil?
         | 
| 21 | 
            +
                  transition.execute(obj)
         | 
| 40 22 |  | 
| 41 | 
            -
             | 
| 42 | 
            -
                   | 
| 43 | 
            -
             | 
| 44 | 
            -
                  else
         | 
| 45 | 
            -
                    name == event.name
         | 
| 46 | 
            -
                  end
         | 
| 47 | 
            -
                end
         | 
| 48 | 
            -
                
         | 
| 49 | 
            -
                # Has the timestamp option been specified for this event?
         | 
| 50 | 
            -
                def timestamp_defined?
         | 
| 51 | 
            -
                  !@timestamp.nil?
         | 
| 52 | 
            -
                end
         | 
| 23 | 
            +
                  current_state.execute_action(:exit, obj)
         | 
| 24 | 
            +
                  #klass._previous_state = current_state.name.to_s
         | 
| 25 | 
            +
                  next_state.execute_action(:enter, obj)
         | 
| 53 26 |  | 
| 54 | 
            -
             | 
| 55 | 
            -
                   | 
| 56 | 
            -
                  self.timestamp = options[:timestamp] if options[:timestamp]
         | 
| 57 | 
            -
                  instance_eval(&block) if block
         | 
| 58 | 
            -
                  self
         | 
| 27 | 
            +
                  obj.set_current_state(next_state, @machine.name, options)
         | 
| 28 | 
            +
                  true
         | 
| 59 29 | 
             
                end
         | 
| 60 | 
            -
                
         | 
| 61 | 
            -
                # update the timestamp attribute on obj
         | 
| 62 | 
            -
                def update_event_timestamp(obj, next_state)
         | 
| 63 | 
            -
                  obj.send "#{timestamp_attribute_name(obj, next_state)}=", Time.now
         | 
| 64 | 
            -
                end
         | 
| 65 | 
            -
                
         | 
| 66 | 
            -
                # Set the timestamp attribute.
         | 
| 67 | 
            -
                # @raise [ArgumentError] timestamp should be either a String, Symbol or true
         | 
| 68 | 
            -
                def timestamp=(value)
         | 
| 69 | 
            -
                  case value
         | 
| 70 | 
            -
                  when String, Symbol, TrueClass
         | 
| 71 | 
            -
                    @timestamp = value
         | 
| 72 | 
            -
                  else
         | 
| 73 | 
            -
                    raise ArgumentError, "timestamp must be either: true, a String or a Symbol"
         | 
| 74 | 
            -
                  end
         | 
| 75 | 
            -
                end
         | 
| 76 | 
            -
                
         | 
| 77 30 |  | 
| 78 31 | 
             
                private
         | 
| 79 | 
            -
                
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                # If the timestamp was simply true it returns the default_timestamp_name
         | 
| 82 | 
            -
                # otherwise, returns the user-specified timestamp name
         | 
| 83 | 
            -
                def timestamp_attribute_name(obj, next_state)
         | 
| 84 | 
            -
                  timestamp == true ? default_timestamp_name(obj, next_state) : @timestamp
         | 
| 85 | 
            -
                end
         | 
| 86 | 
            -
                
         | 
| 87 | 
            -
                # If @timestamp is true, try a default timestamp name
         | 
| 88 | 
            -
                def default_timestamp_name(obj, next_state)
         | 
| 89 | 
            -
                  at_name = "%s_at" % next_state
         | 
| 90 | 
            -
                  on_name = "%s_on" % next_state
         | 
| 91 | 
            -
                  case
         | 
| 92 | 
            -
                  when obj.respond_to?(at_name) then at_name
         | 
| 93 | 
            -
                  when obj.respond_to?(on_name) then on_name
         | 
| 94 | 
            -
                  else
         | 
| 95 | 
            -
                    raise NoMethodError, "Couldn't find a suitable timestamp field for event: #{@name}. 
         | 
| 96 | 
            -
                      Please define #{at_name} or #{on_name} in #{obj.class}"
         | 
| 97 | 
            -
                  end
         | 
| 98 | 
            -
                end
         | 
| 99 | 
            -
                
         | 
| 100 | 
            -
             | 
| 101 | 
            -
                def transitions(trans_opts)
         | 
| 102 | 
            -
                  Array(trans_opts[:from]).each do |s|
         | 
| 103 | 
            -
                    @transitions << Transition.new(trans_opts.merge({:from => s.to_sym}))
         | 
| 104 | 
            -
                  end
         | 
| 32 | 
            +
                def transition(trans_opts)
         | 
| 33 | 
            +
                  @transitions << EdgeStateMachine::Transition.new(trans_opts)
         | 
| 105 34 | 
             
                end
         | 
| 106 35 | 
             
              end
         | 
| 107 36 | 
             
            end
         | 
| @@ -0,0 +1,8 @@ | |
| 1 | 
            +
            module EdgeStateMachine
         | 
| 2 | 
            +
              class InvalidTransition     < StandardError; end
         | 
| 3 | 
            +
              class InvalidMethodOverride < StandardError; end
         | 
| 4 | 
            +
              class NoTransitionFound < Exception; end
         | 
| 5 | 
            +
              class NoStateFound < Exception; end
         | 
| 6 | 
            +
              class NoEventFound < Exception; end
         | 
| 7 | 
            +
              class NoGuardFound < Exception; end
         | 
| 8 | 
            +
            end
         | 
| @@ -1,85 +1,40 @@ | |
| 1 1 | 
             
            module EdgeStateMachine
         | 
| 2 2 | 
             
              class Machine
         | 
| 3 | 
            -
                 | 
| 4 | 
            -
                 | 
| 5 | 
            -
                attr_reader :klass, :name, :auto_scopes
         | 
| 3 | 
            +
                attr_accessor :states, :events, :klass, :persisted_variable_name
         | 
| 4 | 
            +
                attr_reader :name, :initial_state_name
         | 
| 6 5 |  | 
| 7 | 
            -
                def initialize(klass, name,  | 
| 8 | 
            -
                  @klass | 
| 9 | 
            -
                   | 
| 6 | 
            +
                def initialize(klass, name, &block)
         | 
| 7 | 
            +
                  @klass = klass
         | 
| 8 | 
            +
                  @name = name
         | 
| 9 | 
            +
                  @states = Hash.new
         | 
| 10 | 
            +
                  @events = Hash.new
         | 
| 11 | 
            +
                  instance_eval(&block) if block_given?
         | 
| 10 12 | 
             
                end
         | 
| 11 13 |  | 
| 12 | 
            -
                def initial_state
         | 
| 13 | 
            -
                  @ | 
| 14 | 
            +
                def initial_state(name)
         | 
| 15 | 
            +
                  @initial_state_name = name
         | 
| 14 16 | 
             
                end
         | 
| 15 17 |  | 
| 16 | 
            -
                def  | 
| 17 | 
            -
                  @ | 
| 18 | 
            -
                  @auto_scopes = options[:auto_scopes]
         | 
| 19 | 
            -
                  instance_eval(&block) if block
         | 
| 20 | 
            -
                  include_scopes if @auto_scopes && defined?(ActiveRecord::Base) && @klass < ActiveRecord::Base
         | 
| 21 | 
            -
                  self
         | 
| 18 | 
            +
                def persisted_to(name)
         | 
| 19 | 
            +
                  @persisted_variable_name = name
         | 
| 22 20 | 
             
                end
         | 
| 23 21 |  | 
| 24 | 
            -
                def  | 
| 25 | 
            -
                   | 
| 26 | 
            -
                  if new_state = @events[event].fire(record, nil, *args)
         | 
| 27 | 
            -
                    state_index[new_state].call_action(:enter, record)
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                    if record.respond_to?(event_fired_callback)
         | 
| 30 | 
            -
                      record.send(event_fired_callback, record.current_state, new_state, event)
         | 
| 31 | 
            -
                    end
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                    record.current_state(@name, new_state, persist)
         | 
| 34 | 
            -
                    record.send(@events[event].success) if @events[event].success
         | 
| 35 | 
            -
                    true
         | 
| 36 | 
            -
                  else
         | 
| 37 | 
            -
                    if record.respond_to?(event_failed_callback)
         | 
| 38 | 
            -
                      record.send(event_failed_callback, event)
         | 
| 39 | 
            -
                    end
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                    false
         | 
| 42 | 
            -
                  end
         | 
| 43 | 
            -
                end
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                def states_for_select
         | 
| 46 | 
            -
                  states.map { |st| [st.display_name, st.name.to_s] }
         | 
| 47 | 
            -
                end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                def events_for(state)
         | 
| 50 | 
            -
                  events = @events.values.select { |event| event.transitions_from_state?(state) }
         | 
| 51 | 
            -
                  events.map! { |event| event.name }
         | 
| 22 | 
            +
                def create_scopes(bool = false)
         | 
| 23 | 
            +
                  @create_scopes = bool
         | 
| 52 24 | 
             
                end
         | 
| 53 25 |  | 
| 54 | 
            -
                def  | 
| 55 | 
            -
                   | 
| 26 | 
            +
                def create_scopes?
         | 
| 27 | 
            +
                  @create_scopes
         | 
| 56 28 | 
             
                end
         | 
| 57 29 |  | 
| 58 | 
            -
                 | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
                  @states | 
| 30 | 
            +
                def state(name, &state)
         | 
| 31 | 
            +
                  state = EdgeStateMachine::State.new(name, &state)
         | 
| 32 | 
            +
                  @initial_state_name ||= state.name
         | 
| 33 | 
            +
                  @states[name.to_sym] = state
         | 
| 62 34 | 
             
                end
         | 
| 63 35 |  | 
| 64 | 
            -
                def event(name,  | 
| 65 | 
            -
                   | 
| 66 | 
            -
                end
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                def event_fired_callback
         | 
| 69 | 
            -
                  @event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
         | 
| 70 | 
            -
                end
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                def event_failed_callback
         | 
| 73 | 
            -
                  @event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
         | 
| 74 | 
            -
                end
         | 
| 75 | 
            -
             | 
| 76 | 
            -
                def include_scopes
         | 
| 77 | 
            -
                  @states.each do |state|
         | 
| 78 | 
            -
                    state_name = state.name.to_s
         | 
| 79 | 
            -
                    raise InvalidMethodOverride if @klass.respond_to?(state_name)
         | 
| 80 | 
            -
                    @klass.scope state_name, @klass.where(:state => state_name)
         | 
| 81 | 
            -
                  end
         | 
| 36 | 
            +
                def event(name, &transitions)
         | 
| 37 | 
            +
                  @events[name.to_sym] ||= EdgeStateMachine::Event.new(name, self, &transitions)
         | 
| 82 38 | 
             
                end
         | 
| 83 39 | 
             
              end
         | 
| 84 | 
            -
            end
         | 
| 85 | 
            -
             | 
| 40 | 
            +
            end
         | 
| @@ -2,44 +2,46 @@ module EdgeStateMachine | |
| 2 2 | 
             
              class State
         | 
| 3 3 | 
             
                attr_reader :name, :options
         | 
| 4 4 |  | 
| 5 | 
            -
                def initialize(name,  | 
| 5 | 
            +
                def initialize(name, &block)
         | 
| 6 6 | 
             
                  @name = name
         | 
| 7 | 
            -
                   | 
| 8 | 
            -
             | 
| 9 | 
            -
                  end
         | 
| 10 | 
            -
                  update(options)
         | 
| 7 | 
            +
                  @options = Hash.new
         | 
| 8 | 
            +
                  instance_eval(&block) if block_given?
         | 
| 11 9 | 
             
                end
         | 
| 12 10 |  | 
| 13 | 
            -
                def  | 
| 14 | 
            -
                   | 
| 15 | 
            -
                    name == state
         | 
| 16 | 
            -
                  else
         | 
| 17 | 
            -
                    name == state.name
         | 
| 18 | 
            -
                  end
         | 
| 11 | 
            +
                def enter(method = nil, &block)
         | 
| 12 | 
            +
                  @options[:enter] = method.nil? ? block : method
         | 
| 19 13 | 
             
                end
         | 
| 20 14 |  | 
| 21 | 
            -
                def  | 
| 22 | 
            -
                   | 
| 15 | 
            +
                def exit(method = nil, &block)
         | 
| 16 | 
            +
                  @options[:exit] = method.nil? ? block : method
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def execute_action(action, base)
         | 
| 20 | 
            +
                  action = @options[action.to_sym]
         | 
| 23 21 | 
             
                  case action
         | 
| 24 22 | 
             
                  when Symbol, String
         | 
| 25 | 
            -
                     | 
| 23 | 
            +
                    base.send(action)
         | 
| 26 24 | 
             
                  when Proc
         | 
| 27 | 
            -
                    action.call( | 
| 25 | 
            +
                    action.call(base)
         | 
| 28 26 | 
             
                  end
         | 
| 29 27 | 
             
                end
         | 
| 30 28 |  | 
| 31 | 
            -
                def display_name
         | 
| 32 | 
            -
                  @display_name  | 
| 29 | 
            +
                def use_display_name(display_name)
         | 
| 30 | 
            +
                  @display_name = display_name
         | 
| 33 31 | 
             
                end
         | 
| 34 32 |  | 
| 35 | 
            -
                def  | 
| 36 | 
            -
                   | 
| 33 | 
            +
                def display_name
         | 
| 34 | 
            +
                  @display_name ||= name.to_s.gsub(/_/, ' ').capitalize
         | 
| 37 35 | 
             
                end
         | 
| 38 36 |  | 
| 39 | 
            -
                def  | 
| 40 | 
            -
                   | 
| 41 | 
            -
             | 
| 42 | 
            -
                   | 
| 37 | 
            +
                def ==(state)
         | 
| 38 | 
            +
                  if state.is_a? Symbol
         | 
| 39 | 
            +
                    name == state
         | 
| 40 | 
            +
                  elsif state.is_a? String
         | 
| 41 | 
            +
                    name == state
         | 
| 42 | 
            +
                  else
         | 
| 43 | 
            +
                    name == state.name
         | 
| 44 | 
            +
                  end
         | 
| 43 45 | 
             
                end
         | 
| 44 46 | 
             
              end
         | 
| 45 47 | 
             
            end
         | 
| @@ -3,36 +3,37 @@ module EdgeStateMachine | |
| 3 3 | 
             
                attr_reader :from, :to, :options
         | 
| 4 4 |  | 
| 5 5 | 
             
                def initialize(opts)
         | 
| 6 | 
            -
                  @from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
         | 
| 7 | 
            -
                  @options = opts
         | 
| 6 | 
            +
                  @from, @to, @guard, @on_transition = [opts[:from]].flatten, [opts[:to]].flatten, opts[:guard], [opts[:on_transition]].flatten
         | 
| 8 7 | 
             
                end
         | 
| 9 8 |  | 
| 10 | 
            -
                def  | 
| 11 | 
            -
                   | 
| 12 | 
            -
                   | 
| 13 | 
            -
                     | 
| 14 | 
            -
                   | 
| 15 | 
            -
             | 
| 9 | 
            +
                def find_next_state(obj)
         | 
| 10 | 
            +
                  # if we have many states we can go but no guard
         | 
| 11 | 
            +
                  if @guard.nil? && @to.size > 1
         | 
| 12 | 
            +
                    raise NoGuardFound.new("There are many possible 'to' states but there is no 'guard' to decide which state to go")
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                  if @guard
         | 
| 15 | 
            +
                    return execute_action(@guard, obj)
         | 
| 16 16 | 
             
                  else
         | 
| 17 | 
            -
                     | 
| 17 | 
            +
                    return @to.first
         | 
| 18 18 | 
             
                  end
         | 
| 19 19 | 
             
                end
         | 
| 20 20 |  | 
| 21 | 
            -
                def  | 
| 22 | 
            -
                   | 
| 23 | 
            -
                   | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
                      obj.send( | 
| 21 | 
            +
                def possible?(obj)
         | 
| 22 | 
            +
                  next_state = find_next_state(obj)
         | 
| 23 | 
            +
                  return true if @to.include? next_state
         | 
| 24 | 
            +
                  false
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def execute(obj)
         | 
| 28 | 
            +
                  @on_transition.each do |transition|
         | 
| 29 | 
            +
                    case transition
         | 
| 30 | 
            +
                    when Symbol, String
         | 
| 31 | 
            +
                      obj.send(transition)
         | 
| 32 | 
            +
                    when Proc
         | 
| 33 | 
            +
                      transition.call(obj)
         | 
| 34 | 
            +
                    else
         | 
| 35 | 
            +
                      raise ArgumentError, "You can only pass a Symbol, a String or a Proc to 'on_transition' - got #{transition.class}." unless transition.nil?
         | 
| 32 36 | 
             
                    end
         | 
| 33 | 
            -
                  else
         | 
| 34 | 
            -
                    # TODO We probably should check for this in the constructor and not that late.
         | 
| 35 | 
            -
                    raise ArgumentError, "You can only pass a Symbol, a String, a Proc or an Array to 'on_transition' - got #{@on_transition.class}." unless @on_transition.nil?
         | 
| 36 37 | 
             
                  end
         | 
| 37 38 | 
             
                end
         | 
| 38 39 |  | 
| @@ -40,8 +41,14 @@ module EdgeStateMachine | |
| 40 41 | 
             
                  @from == obj.from && @to == obj.to
         | 
| 41 42 | 
             
                end
         | 
| 42 43 |  | 
| 43 | 
            -
                 | 
| 44 | 
            -
             | 
| 44 | 
            +
                private
         | 
| 45 | 
            +
                def execute_action(action, base)
         | 
| 46 | 
            +
                  case action
         | 
| 47 | 
            +
                  when Symbol, String
         | 
| 48 | 
            +
                    base.send(action)
         | 
| 49 | 
            +
                  when Proc
         | 
| 50 | 
            +
                    action.call(base)
         | 
| 51 | 
            +
                  end
         | 
| 45 52 | 
             
                end
         | 
| 46 53 | 
             
              end
         | 
| 47 54 | 
             
            end
         | 
    
        data/lib/edge-state-machine.rb
    CHANGED
    
    | @@ -2,11 +2,15 @@ require "edge-state-machine/event" | |
| 2 2 | 
             
            require "edge-state-machine/machine"
         | 
| 3 3 | 
             
            require "edge-state-machine/state"
         | 
| 4 4 | 
             
            require "edge-state-machine/transition"
         | 
| 5 | 
            +
            require "edge-state-machine/exception"
         | 
| 5 6 | 
             
            require "edge-state-machine/version"
         | 
| 6 7 |  | 
| 7 8 | 
             
            module EdgeStateMachine
         | 
| 8 | 
            -
             | 
| 9 | 
            -
               | 
| 9 | 
            +
             | 
| 10 | 
            +
              def self.included(base)
         | 
| 11 | 
            +
                base.extend(ClassMethods)
         | 
| 12 | 
            +
                base.send :include, InstanceMethods
         | 
| 13 | 
            +
              end
         | 
| 10 14 |  | 
| 11 15 | 
             
              module ClassMethods
         | 
| 12 16 | 
             
                def inherited(klass)
         | 
| @@ -22,52 +26,63 @@ module EdgeStateMachine | |
| 22 26 | 
             
                  @state_machines = value ? value.dup : nil
         | 
| 23 27 | 
             
                end
         | 
| 24 28 |  | 
| 25 | 
            -
                def state_machine(name =  | 
| 26 | 
            -
                   | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            +
                def state_machine(name = :default, &block)
         | 
| 30 | 
            +
                  machine = Machine.new(self, name, &block)
         | 
| 31 | 
            +
                  state_machines[name] ||= machine
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  machine.persisted_variable_name ||= :state
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  machine.states.values.each do |state|
         | 
| 36 | 
            +
                    state_name = state.name
         | 
| 37 | 
            +
                    define_method "#{state_name}?" do
         | 
| 38 | 
            +
                      state_name == current_state(name).name
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                    add_scope(state, name) if machine.create_scopes?
         | 
| 29 41 | 
             
                  end
         | 
| 30 | 
            -
                  name ||= :default
         | 
| 31 | 
            -
                  state_machines[name] ||= Machine.new(self, name)
         | 
| 32 | 
            -
                  block ? state_machines[name].update(options, &block) : state_machines[name]
         | 
| 33 | 
            -
                end
         | 
| 34 42 |  | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 43 | 
            +
                  machine.events.keys.each do |key|
         | 
| 44 | 
            +
                    define_method "#{key}" do
         | 
| 45 | 
            +
                      fire_event(machine.name, {:save => false}, key)
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    define_method "#{key}!" do
         | 
| 49 | 
            +
                      fire_event(machine.name, {:save => true}, key)
         | 
| 50 | 
            +
                    end
         | 
| 40 51 | 
             
                  end
         | 
| 41 52 | 
             
                end
         | 
| 42 53 | 
             
              end
         | 
| 43 54 |  | 
| 44 | 
            -
               | 
| 45 | 
            -
                 | 
| 46 | 
            -
              end
         | 
| 55 | 
            +
              module InstanceMethods
         | 
| 56 | 
            +
                attr_writer :current_state
         | 
| 47 57 |  | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
                 | 
| 52 | 
            -
                  if persist && respond_to?(:write_state)
         | 
| 53 | 
            -
                    write_state(sm, new_state)
         | 
| 54 | 
            -
                  end
         | 
| 58 | 
            +
                def initial_state_name(name = :default)
         | 
| 59 | 
            +
                  machine = self.class.state_machines[name]
         | 
| 60 | 
            +
                  return machine.initial_state_name
         | 
| 61 | 
            +
                end
         | 
| 55 62 |  | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 63 | 
            +
                def current_state(name = :default)
         | 
| 64 | 
            +
                  @current_states ||= {}
         | 
| 65 | 
            +
                  machine = self.class.state_machines[name]
         | 
| 66 | 
            +
                  if (respond_to? :load_from_persistence)
         | 
| 67 | 
            +
                    @current_states[name] ||= self.class.state_machines[name].states[load_from_persistence(name).to_sym]
         | 
| 58 68 | 
             
                  end
         | 
| 69 | 
            +
                  @current_states[name] ||= machine.states[machine.initial_state_name]
         | 
| 70 | 
            +
                end
         | 
| 59 71 |  | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
                  value = instance_variable_get(ivar)
         | 
| 64 | 
            -
                  return value if value
         | 
| 72 | 
            +
                def current_state_name(name = :default)
         | 
| 73 | 
            +
                  current_state(name).name
         | 
| 74 | 
            +
                end
         | 
| 65 75 |  | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
                   | 
| 76 | 
            +
                def fire_event(name = :default, options = {}, event_name)
         | 
| 77 | 
            +
                  machine = self.class.state_machines[name]
         | 
| 78 | 
            +
                  event = machine.events[event_name]
         | 
| 79 | 
            +
                  raise Stateflow::NoEventFound.new("No event matches #{event_name}") if event.nil?
         | 
| 80 | 
            +
                  event.fire(self, options)
         | 
| 81 | 
            +
                end
         | 
| 69 82 |  | 
| 70 | 
            -
             | 
| 83 | 
            +
                def set_current_state(new_state, machine_name = :default, options = {})
         | 
| 84 | 
            +
                  save_to_persistence(new_state.name.to_s, machine_name, options) if self.respond_to? :save_to_persistence
         | 
| 85 | 
            +
                  @current_states[machine_name] = new_state
         | 
| 71 86 | 
             
                end
         | 
| 72 87 | 
             
              end
         | 
| 73 88 | 
             
            end
         |