Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial commit

  • Loading branch information...
commit a8c33d08d0f0b7da97da71208b17f9207cf75e4f 0 parents
@theflow authored
19 README
@@ -0,0 +1,19 @@
+EdgeStateMachine
+================
+
+This is the ActiveModel::StateMachine code extracted from edge Rails to
+be used as a Rails plugin.
+
+Tested with Rails 2.3
+
+Example
+=======
+
+The state machine included in Rails 3 is mostly aasm with different names. So take
+a look at the original
+
+ http://github.com/rubyist/aasm
+
+and the stuff in Rails 3:
+
+ http://blog.envylabs.com/2009/08/the-rails-state-machine/
23 Rakefile
@@ -0,0 +1,23 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the edge_state_machine plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.libs << 'test'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the edge_state_machine plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'EdgeStateMachine'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
1  init.rb
@@ -0,0 +1 @@
+require 'edge_state_machine'
70 lib/active_model/state_machine.rb
@@ -0,0 +1,70 @@
+module ActiveModel
+ module StateMachine
+ autoload :Event, 'active_model/state_machine/event'
+ autoload :Machine, 'active_model/state_machine/machine'
+ autoload :State, 'active_model/state_machine/state'
+ autoload :StateTransition, 'active_model/state_machine/state_transition'
+
+ extend ActiveSupport::Concern
+
+ class InvalidTransition < Exception
+ end
+
+ module ClassMethods
+ def inherited(klass)
+ super
+ klass.state_machines = state_machines
+ end
+
+ def state_machines
+ @state_machines ||= {}
+ end
+
+ def state_machines=(value)
+ @state_machines = value ? value.dup : nil
+ end
+
+ def state_machine(name = nil, options = {}, &block)
+ if name.is_a?(Hash)
+ options = name
+ name = nil
+ end
+ name ||= :default
+ state_machines[name] ||= Machine.new(self, name)
+ block ? state_machines[name].update(options, &block) : state_machines[name]
+ end
+
+ def define_state_query_method(state_name)
+ name = "#{state_name}?"
+ undef_method(name) if method_defined?(name)
+ class_eval "def #{name}; current_state.to_s == %(#{state_name}) end"
+ end
+ end
+
+ def current_state(name = nil, new_state = nil, persist = false)
+ sm = self.class.state_machine(name)
+ ivar = sm.current_state_variable
+ if name && new_state
+ if persist && respond_to?(:write_state)
+ write_state(sm, new_state)
+ end
+
+ if respond_to?(:write_state_without_persistence)
+ write_state_without_persistence(sm, new_state)
+ end
+
+ instance_variable_set(ivar, new_state)
+ else
+ instance_variable_set(ivar, nil) unless instance_variable_defined?(ivar)
+ value = instance_variable_get(ivar)
+ return value if value
+
+ if respond_to?(:read_state)
+ value = instance_variable_set(ivar, read_state(sm))
+ end
+
+ value || sm.initial_state
+ end
+ end
+ end
+end
62 lib/active_model/state_machine/event.rb
@@ -0,0 +1,62 @@
+module ActiveModel
+ module StateMachine
+ class Event
+ attr_reader :name, :success
+
+ def initialize(machine, name, options = {}, &block)
+ @machine, @name, @transitions = machine, name, []
+ if machine
+ machine.klass.send(:define_method, "#{name}!") do |*args|
+ machine.fire_event(name, self, true, *args)
+ end
+
+ machine.klass.send(:define_method, name.to_s) do |*args|
+ machine.fire_event(name, self, false, *args)
+ end
+ end
+ update(options, &block)
+ end
+
+ def fire(obj, to_state = nil, *args)
+ transitions = @transitions.select { |t| t.from == obj.current_state(@machine ? @machine.name : nil) }
+ raise InvalidTransition if transitions.size == 0
+
+ next_state = nil
+ transitions.each do |transition|
+ next if to_state && !Array(transition.to).include?(to_state)
+ if transition.perform(obj)
+ next_state = to_state || Array(transition.to).first
+ transition.execute(obj, *args)
+ break
+ end
+ end
+ next_state
+ end
+
+ def transitions_from_state?(state)
+ @transitions.any? { |t| t.from? state }
+ end
+
+ def ==(event)
+ if event.is_a? Symbol
+ name == event
+ else
+ name == event.name
+ end
+ end
+
+ def update(options = {}, &block)
+ if options.key?(:success) then @success = options[:success] end
+ if block then instance_eval(&block) end
+ self
+ end
+
+ private
+ def transitions(trans_opts)
+ Array(trans_opts[:from]).each do |s|
+ @transitions << StateTransition.new(trans_opts.merge({:from => s.to_sym}))
+ end
+ end
+ end
+ end
+end
75 lib/active_model/state_machine/machine.rb
@@ -0,0 +1,75 @@
+module ActiveModel
+ module StateMachine
+ class Machine
+ attr_writer :initial_state
+ attr_accessor :states, :events, :state_index
+ attr_reader :klass, :name
+
+ def initialize(klass, name, options = {}, &block)
+ @klass, @name, @states, @state_index, @events = klass, name, [], {}, {}
+ update(options, &block)
+ end
+
+ def initial_state
+ @initial_state ||= (states.first ? states.first.name : nil)
+ end
+
+ def update(options = {}, &block)
+ if options.key?(:initial) then @initial_state = options[:initial] end
+ if block then instance_eval(&block) end
+ self
+ end
+
+ def fire_event(event, record, persist, *args)
+ state_index[record.current_state(@name)].call_action(:exit, record)
+ if new_state = @events[event].fire(record, *args)
+ state_index[new_state].call_action(:enter, record)
+
+ if record.respond_to?(event_fired_callback)
+ record.send(event_fired_callback, record.current_state, new_state)
+ end
+
+ record.current_state(@name, new_state, persist)
+ record.send(@events[event].success) if @events[event].success
+ true
+ else
+ if record.respond_to?(event_failed_callback)
+ record.send(event_failed_callback, event)
+ end
+
+ false
+ end
+ end
+
+ def states_for_select
+ states.map { |st| [st.display_name, st.name.to_s] }
+ end
+
+ def events_for(state)
+ events = @events.values.select { |event| event.transitions_from_state?(state) }
+ events.map! { |event| event.name }
+ end
+
+ def current_state_variable
+ "@#{@name}_current_state"
+ end
+
+ private
+ def state(name, options = {})
+ @states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
+ end
+
+ def event(name, options = {}, &block)
+ (@events[name] ||= Event.new(self, name)).update(options, &block)
+ end
+
+ def event_fired_callback
+ @event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
+ end
+
+ def event_failed_callback
+ @event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
+ end
+ end
+ end
+end
47 lib/active_model/state_machine/state.rb
@@ -0,0 +1,47 @@
+module ActiveModel
+ module StateMachine
+ class State
+ attr_reader :name, :options
+
+ def initialize(name, options = {})
+ @name = name
+ if machine = options.delete(:machine)
+ machine.klass.define_state_query_method(name)
+ end
+ update(options)
+ end
+
+ def ==(state)
+ if state.is_a? Symbol
+ name == state
+ else
+ name == state.name
+ end
+ end
+
+ def call_action(action, record)
+ action = @options[action]
+ case action
+ when Symbol, String
+ record.send(action)
+ when Proc
+ action.call(record)
+ end
+ end
+
+ def display_name
+ @display_name ||= name.to_s.gsub(/_/, ' ').capitalize
+ end
+
+ def for_select
+ [display_name, name.to_s]
+ end
+
+ def update(options = {})
+ if options.key?(:display) then @display_name = options.delete(:display) end
+ @options = options
+ self
+ end
+ end
+ end
+end
40 lib/active_model/state_machine/state_transition.rb
@@ -0,0 +1,40 @@
+module ActiveModel
+ module StateMachine
+ class StateTransition
+ attr_reader :from, :to, :options
+
+ def initialize(opts)
+ @from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
+ @options = opts
+ end
+
+ def perform(obj)
+ case @guard
+ when Symbol, String
+ obj.send(@guard)
+ when Proc
+ @guard.call(obj)
+ else
+ true
+ end
+ end
+
+ def execute(obj, *args)
+ case @on_transition
+ when Symbol, String
+ obj.send(@on_transition, *args)
+ when Proc
+ @on_transition.call(obj, *args)
+ end
+ end
+
+ def ==(obj)
+ @from == obj.from && @to == obj.to
+ end
+
+ def from?(value)
+ @from == value
+ end
+ end
+ end
+end
24 lib/active_record/state_machine.rb
@@ -0,0 +1,24 @@
+module ActiveRecord
+ module StateMachine #:nodoc:
+ extend ActiveSupport::Concern
+ include ActiveModel::StateMachine
+
+ included do
+ before_validation :set_initial_state
+ validates_presence_of :state
+ end
+
+ protected
+ def write_state(state_machine, state)
+ update_attributes! :state => state.to_s
+ end
+
+ def read_state(state_machine)
+ self.state.to_sym
+ end
+
+ def set_initial_state
+ self.state ||= self.class.state_machine.initial_state.to_s
+ end
+ end
+end
25 lib/active_support/concern.rb
@@ -0,0 +1,25 @@
+require 'active_support/dependency_module'
+
+module ActiveSupport
+ module Concern
+ include DependencyModule
+
+ def append_features(base)
+ if super
+ base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
+ base.send :include, const_get("InstanceMethods") if const_defined?("InstanceMethods")
+ base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
+ end
+ end
+
+ def included(base = nil, &block)
+ if base.nil?
+ @_included_block = block
+ else
+ super
+ end
+ end
+
+ alias_method :include, :depends_on
+ end
+end
17 lib/active_support/dependency_module.rb
@@ -0,0 +1,17 @@
+module ActiveSupport
+ module DependencyModule
+ def append_features(base)
+ return false if base < self
+ (@_dependencies ||= []).each { |dep| base.send(:include, dep) }
+ super
+ end
+
+ def depends_on(*mods)
+ mods.each do |mod|
+ next if self < mod
+ @_dependencies ||= []
+ @_dependencies << mod
+ end
+ end
+ end
+end
9 lib/edge_state_machine.rb
@@ -0,0 +1,9 @@
+require 'active_support/concern'
+
+module ActiveModel
+ autoload :StateMachine, 'active_model/state_machine'
+end
+
+module ActiveRecord
+ autoload :StateMachine, 'active_record/state_machine'
+end
49 test/state_machine/event_test.rb
@@ -0,0 +1,49 @@
+require 'test_helper'
+
+class EventTest < ActiveSupport::TestCase
+ def setup
+ @state_name = :close_order
+ @success = :success_callback
+ end
+
+ def new_event
+ @event = ActiveModel::StateMachine::Event.new(nil, @state_name, {:success => @success}) do
+ transitions :to => :closed, :from => [:open, :received]
+ end
+ end
+
+ test 'should set the name' do
+ assert_equal @state_name, new_event.name
+ end
+
+ test 'should set the success option' do
+ assert_equal @success, new_event.success
+ end
+
+ test 'should create StateTransitions' do
+ ActiveModel::StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :open)
+ ActiveModel::StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :received)
+ new_event
+ end
+end
+
+class EventBeingFiredTest < ActiveSupport::TestCase
+ test 'should raise an AASM::InvalidTransition error if the transitions are empty' do
+ event = ActiveModel::StateMachine::Event.new(nil, :event)
+
+ assert_raise ActiveModel::StateMachine::InvalidTransition do
+ event.fire(nil)
+ end
+ end
+
+ test 'should return the state of the first matching transition it finds' do
+ event = ActiveModel::StateMachine::Event.new(nil, :event) do
+ transitions :to => :closed, :from => [:open, :received]
+ end
+
+ obj = stub
+ obj.stubs(:current_state).returns(:open)
+
+ assert_equal :closed, event.fire(obj)
+ end
+end
43 test/state_machine/machine_test.rb
@@ -0,0 +1,43 @@
+require 'test_helper'
+
+class MachineTestSubject
+ include ActiveModel::StateMachine
+
+ state_machine do
+ state :open
+ state :closed
+ end
+
+ state_machine :initial => :foo do
+ event :shutdown do
+ transitions :from => :open, :to => :closed
+ end
+
+ event :timeout do
+ transitions :from => :open, :to => :closed
+ end
+ end
+
+ state_machine :extra, :initial => :bar do
+ end
+end
+
+class StateMachineMachineTest < ActiveSupport::TestCase
+ test "allows reuse of existing machines" do
+ assert_equal 2, MachineTestSubject.state_machines.size
+ end
+
+ test "sets #initial_state from :initial option" do
+ assert_equal :bar, MachineTestSubject.state_machine(:extra).initial_state
+ end
+
+ test "accesses non-default state machine" do
+ assert_kind_of ActiveModel::StateMachine::Machine, MachineTestSubject.state_machine(:extra)
+ end
+
+ test "finds events for given state" do
+ events = MachineTestSubject.state_machine.events_for(:open)
+ assert events.include?(:shutdown)
+ assert events.include?(:timeout)
+ end
+end
72 test/state_machine/state_test.rb
@@ -0,0 +1,72 @@
+require 'test_helper'
+
+class StateTestSubject
+ include ActiveModel::StateMachine
+
+ state_machine do
+ end
+end
+
+class StateTest < ActiveSupport::TestCase
+ def setup
+ @state_name = :astate
+ @machine = StateTestSubject.state_machine
+ @options = { :crazy_custom_key => 'key', :machine => @machine }
+ end
+
+ def new_state(options={})
+ ActiveModel::StateMachine::State.new(@state_name, @options.merge(options))
+ end
+
+ test 'sets the name' do
+ assert_equal :astate, new_state.name
+ end
+
+ test 'sets the display_name from name' do
+ assert_equal "Astate", new_state.display_name
+ end
+
+ test 'sets the display_name from options' do
+ assert_equal "A State", new_state(:display => "A State").display_name
+ end
+
+ test 'sets the options and expose them as options' do
+ @options.delete(:machine)
+ assert_equal @options, new_state.options
+ end
+
+ test 'equals a symbol of the same name' do
+ assert_equal new_state, :astate
+ end
+
+ test 'equals a State of the same name' do
+ assert_equal new_state, new_state
+ end
+
+ test 'should send a message to the record for an action if the action is present as a symbol' do
+ state = new_state(:entering => :foo)
+
+ record = stub
+ record.expects(:foo)
+
+ state.call_action(:entering, record)
+ end
+
+ test 'should send a message to the record for an action if the action is present as a string' do
+ state = new_state(:entering => 'foo')
+
+ record = stub
+ record.expects(:foo)
+
+ state.call_action(:entering, record)
+ end
+
+ test 'should call a proc, passing in the record for an action if the action is present' do
+ state = new_state(:entering => Proc.new {|r| r.foobar})
+
+ record = stub
+ record.expects(:foobar)
+
+ state.call_action(:entering, record)
+ end
+end
84 test/state_machine/state_transition_test.rb
@@ -0,0 +1,84 @@
+require 'test_helper'
+
+class StateTransitionTest < ActiveSupport::TestCase
+ test 'should set from, to, and opts attr readers' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ assert_equal opts[:from], st.from
+ assert_equal opts[:to], st.to
+ assert_equal opts, st.options
+ end
+
+ test 'should pass equality check if from and to are the same' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.stubs(:from).returns(opts[:from])
+ obj.stubs(:to).returns(opts[:to])
+
+ assert_equal st, obj
+ end
+
+ test 'should fail equality check if from are not the same' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.stubs(:from).returns('blah')
+ obj.stubs(:to).returns(opts[:to])
+
+ assert_not_equal st, obj
+ end
+
+ test 'should fail equality check if to are not the same' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.stubs(:from).returns(opts[:from])
+ obj.stubs(:to).returns('blah')
+
+ assert_not_equal st, obj
+ end
+end
+
+class StateTransitionGuardCheckTest < ActiveSupport::TestCase
+ test 'should return true of there is no guard' do
+ opts = {:from => 'foo', :to => 'bar'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ assert st.perform(nil)
+ end
+
+ test 'should call the method on the object if guard is a symbol' do
+ opts = {:from => 'foo', :to => 'bar', :guard => :test_guard}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.expects(:test_guard)
+
+ st.perform(obj)
+ end
+
+ test 'should call the method on the object if guard is a string' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'test_guard'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.expects(:test_guard)
+
+ st.perform(obj)
+ end
+
+ test 'should call the proc passing the object if the guard is a proc' do
+ opts = {:from => 'foo', :to => 'bar', :guard => Proc.new {|o| o.test_guard}}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.expects(:test_guard)
+
+ st.perform(obj)
+ end
+end
312 test/state_machine_test.rb
@@ -0,0 +1,312 @@
+require 'test_helper'
+
+class StateMachineSubject
+ include ActiveModel::StateMachine
+
+ state_machine do
+ state :open, :exit => :exit
+ state :closed, :enter => :enter
+
+ event :close, :success => :success_callback do
+ transitions :to => :closed, :from => [:open]
+ end
+
+ event :null do
+ transitions :to => :closed, :from => [:open], :guard => :always_false
+ end
+ end
+
+ state_machine :bar do
+ state :read
+ state :ended
+
+ event :foo do
+ transitions :to => :ended, :from => [:read]
+ end
+ end
+
+ def always_false
+ false
+ end
+
+ def success_callback
+ end
+
+ def enter
+ end
+ def exit
+ end
+end
+
+class StateMachineSubjectSubclass < StateMachineSubject
+end
+
+class StateMachineClassLevelTest < ActiveSupport::TestCase
+ test 'defines a class level #state_machine method on its including class' do
+ assert StateMachineSubject.respond_to?(:state_machine)
+ end
+
+ test 'defines a class level #state_machines method on its including class' do
+ assert StateMachineSubject.respond_to?(:state_machines)
+ end
+
+ test 'class level #state_machine returns machine instance' do
+ assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machine
+ end
+
+ test 'class level #state_machine returns machine instance with given name' do
+ assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machine(:default)
+ end
+
+ test 'class level #state_machines returns hash of machine instances' do
+ assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machines[:default]
+ end
+
+ test "should return a select friendly array of states in the form of [['Friendly name', 'state_name']]" do
+ assert_equal [['Open', 'open'], ['Closed', 'closed']], StateMachineSubject.state_machine.states_for_select
+ end
+end
+
+class StateMachineInstanceLevelTest < ActiveSupport::TestCase
+ def setup
+ @foo = StateMachineSubject.new
+ end
+
+ test 'defines an accessor for the current state' do
+ assert @foo.respond_to?(:current_state)
+ end
+
+ test 'defines a state querying instance method on including class' do
+ assert @foo.respond_to?(:open?)
+ end
+
+ test 'defines an event! instance method' do
+ assert @foo.respond_to?(:close!)
+ end
+
+ test 'defines an event instance method' do
+ assert @foo.respond_to?(:close)
+ end
+end
+
+class StateMachineInitialStatesTest < ActiveSupport::TestCase
+ def setup
+ @foo = StateMachineSubject.new
+ end
+
+ test 'sets the initial state' do
+ assert_equal :open, @foo.current_state
+ end
+
+ test '#open? should be initially true' do
+ assert @foo.open?
+ end
+
+ test '#closed? should be initially false' do
+ assert !@foo.closed?
+ end
+
+ test 'uses the first state defined if no initial state is given' do
+ assert_equal :read, @foo.current_state(:bar)
+ end
+end
+
+class StateMachineEventFiringWithPersistenceTest < ActiveSupport::TestCase
+ def setup
+ @subj = StateMachineSubject.new
+ end
+
+ test 'updates the current state' do
+ @subj.close!
+
+ assert_equal :closed, @subj.current_state
+ end
+
+ test 'fires the Event' do
+ @subj.class.state_machine.events[:close].expects(:fire).with(@subj)
+ @subj.close!
+ end
+
+ test 'calls the success callback if one was provided' do
+ @subj.expects(:success_callback)
+ @subj.close!
+ end
+
+ test 'attempts to persist if write_state is defined' do
+ def @subj.write_state
+ end
+
+ @subj.expects(:write_state)
+ @subj.close!
+ end
+end
+
+class StateMachineEventFiringWithoutPersistence < ActiveSupport::TestCase
+ test 'updates the current state' do
+ subj = StateMachineSubject.new
+ assert_equal :open, subj.current_state
+ subj.close
+ assert_equal :closed, subj.current_state
+ end
+
+ test 'fires the Event' do
+ subj = StateMachineSubject.new
+
+ StateMachineSubject.state_machine.events[:close].expects(:fire).with(subj)
+ subj.close
+ end
+
+ test 'attempts to persist if write_state is defined' do
+ subj = StateMachineSubject.new
+
+ def subj.write_state
+ end
+
+ subj.expects(:write_state_without_persistence)
+
+ subj.close
+ end
+end
+
+class StateMachinePersistenceTest < ActiveSupport::TestCase
+ test 'reads the state if it has not been set and read_state is defined' do
+ subj = StateMachineSubject.new
+ def subj.read_state
+ end
+
+ subj.expects(:read_state).with(StateMachineSubject.state_machine)
+
+ subj.current_state
+ end
+end
+
+class StateMachineEventCallbacksTest < ActiveSupport::TestCase
+ test 'should call aasm_event_fired if defined and successful for bang fire' do
+ subj = StateMachineSubject.new
+ def subj.aasm_event_fired(from, to)
+ end
+
+ subj.expects(:event_fired)
+
+ subj.close!
+ end
+
+ test 'should call aasm_event_fired if defined and successful for non-bang fire' do
+ subj = StateMachineSubject.new
+ def subj.aasm_event_fired(from, to)
+ end
+
+ subj.expects(:event_fired)
+
+ subj.close
+ end
+
+ test 'should call aasm_event_failed if defined and transition failed for bang fire' do
+ subj = StateMachineSubject.new
+ def subj.event_failed(event)
+ end
+
+ subj.expects(:event_failed)
+
+ subj.null!
+ end
+
+ test 'should call aasm_event_failed if defined and transition failed for non-bang fire' do
+ subj = StateMachineSubject.new
+ def subj.aasm_event_failed(event)
+ end
+
+ subj.expects(:event_failed)
+
+ subj.null
+ end
+end
+
+class StateMachineStateActionsTest < ActiveSupport::TestCase
+ test "calls enter when entering state" do
+ subj = StateMachineSubject.new
+ subj.expects(:enter)
+ subj.close
+ end
+
+ test "calls exit when exiting state" do
+ subj = StateMachineSubject.new
+ subj.expects(:exit)
+ subj.close
+ end
+end
+
+class StateMachineInheritanceTest < ActiveSupport::TestCase
+ test "has the same states as its parent" do
+ assert_equal StateMachineSubject.state_machine.states, StateMachineSubjectSubclass.state_machine.states
+ end
+
+ test "has the same events as its parent" do
+ assert_equal StateMachineSubject.state_machine.events, StateMachineSubjectSubclass.state_machine.events
+ end
+end
+
+class StateMachineSubject
+ state_machine :chetan_patil, :initial => :sleeping do
+ state :sleeping
+ state :showering
+ state :working
+ state :dating
+
+ event :wakeup do
+ transitions :from => :sleeping, :to => [:showering, :working]
+ end
+
+ event :dress do
+ transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
+ transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
+ end
+ end
+
+ def wear_clothes(shirt_color, trouser_type)
+ end
+end
+
+class StateMachineWithComplexTransitionsTest < ActiveSupport::TestCase
+ def setup
+ @subj = StateMachineSubject.new
+ end
+
+ test 'transitions to specified next state (sleeping to showering)' do
+ @subj.wakeup! :showering
+
+ assert_equal :showering, @subj.current_state(:chetan_patil)
+ end
+
+ test 'transitions to specified next state (sleeping to working)' do
+ @subj.wakeup! :working
+
+ assert_equal :working, @subj.current_state(:chetan_patil)
+ end
+
+ test 'transitions to default (first or showering) state' do
+ @subj.wakeup!
+
+ assert_equal :showering, @subj.current_state(:chetan_patil)
+ end
+
+ test 'transitions to default state when on_transition invoked' do
+ @subj.dress!(nil, 'purple', 'dressy')
+
+ assert_equal :working, @subj.current_state(:chetan_patil)
+ end
+
+ test 'calls on_transition method with args' do
+ @subj.wakeup! :showering
+
+ @subj.expects(:wear_clothes).with('blue', 'jeans')
+ @subj.dress! :working, 'blue', 'jeans'
+ end
+
+ test 'calls on_transition proc' do
+ @subj.wakeup! :showering
+
+ @subj.expects(:wear_clothes).with('purple', 'slacks')
+ @subj.dress!(:dating, 'purple', 'slacks')
+ end
+end
5 test/test_helper.rb
@@ -0,0 +1,5 @@
+require 'rubygems'
+require 'test/unit'
+require 'active_support/test_case'
+
+require 'edge_state_machine'
Please sign in to comment.
Something went wrong with that request. Please try again.