mirror of
https://github.com/aasm/aasm
synced 2023-03-27 23:22:41 -04:00
cleanup, some refactoring, additional tests (one modeled after a common use of AASM, from restful authentication), fire callbacks on entering initial state, whitespace
This commit is contained in:
parent
80d7896ece
commit
dc002d4684
14 changed files with 506 additions and 74 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
||||||
*.sw?
|
*.sw?
|
||||||
*~
|
*~
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.idea
|
||||||
coverage
|
coverage
|
||||||
pkg
|
pkg
|
||||||
rdoc
|
rdoc
|
||||||
|
|
27
Rakefile
27
Rakefile
|
@ -11,6 +11,7 @@ begin
|
||||||
gem.authors = ["Scott Barron", "Scott Petersen", "Travis Tilley"]
|
gem.authors = ["Scott Barron", "Scott Petersen", "Travis Tilley"]
|
||||||
gem.email = "ttilley@gmail.com"
|
gem.email = "ttilley@gmail.com"
|
||||||
gem.add_development_dependency "rspec"
|
gem.add_development_dependency "rspec"
|
||||||
|
gem.add_development_dependency "shoulda"
|
||||||
gem.add_development_dependency 'sdoc'
|
gem.add_development_dependency 'sdoc'
|
||||||
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
||||||
end
|
end
|
||||||
|
@ -20,18 +21,40 @@ rescue LoadError
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'spec/rake/spectask'
|
require 'spec/rake/spectask'
|
||||||
|
require 'rake/testtask'
|
||||||
|
|
||||||
|
Rake::TestTask.new(:test) do |test|
|
||||||
|
test.libs << 'lib' << 'test'
|
||||||
|
test.pattern = 'test/**/*_test.rb'
|
||||||
|
test.verbose = true
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
require 'rcov/rcovtask'
|
||||||
|
Rcov::RcovTask.new(:rcov_shoulda) do |test|
|
||||||
|
test.libs << 'test'
|
||||||
|
test.pattern = 'test/**/*_test.rb'
|
||||||
|
test.verbose = true
|
||||||
|
end
|
||||||
|
rescue LoadError
|
||||||
|
task :rcov do
|
||||||
|
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Spec::Rake::SpecTask.new(:spec) do |spec|
|
Spec::Rake::SpecTask.new(:spec) do |spec|
|
||||||
spec.libs << 'lib' << 'spec'
|
spec.libs << 'lib' << 'spec'
|
||||||
spec.spec_files = FileList['spec/**/*_spec.rb']
|
spec.spec_files = FileList['spec/**/*_spec.rb']
|
||||||
spec.spec_opts = ['-cfs']
|
spec.spec_opts = ['-cfs']
|
||||||
end
|
end
|
||||||
|
|
||||||
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
Spec::Rake::SpecTask.new(:rcov_rspec) do |spec|
|
||||||
spec.libs << 'lib' << 'spec'
|
spec.libs << 'lib' << 'spec'
|
||||||
spec.pattern = 'spec/**/*_spec.rb'
|
spec.pattern = 'spec/**/*_spec.rb'
|
||||||
spec.rcov = true
|
spec.rcov = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
task :test => :check_dependencies
|
||||||
task :spec => :check_dependencies
|
task :spec => :check_dependencies
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
@ -59,7 +82,7 @@ rescue LoadError
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
task :default => :spec
|
task :default => :test
|
||||||
|
|
||||||
begin
|
begin
|
||||||
require 'rake/rdoctask'
|
require 'rake/rdoctask'
|
||||||
|
|
|
@ -84,7 +84,20 @@ module AASM
|
||||||
@aasm_current_state = aasm_read_state
|
@aasm_current_state = aasm_read_state
|
||||||
end
|
end
|
||||||
return @aasm_current_state if @aasm_current_state
|
return @aasm_current_state if @aasm_current_state
|
||||||
aasm_determine_state_name(self.class.aasm_initial_state)
|
|
||||||
|
aasm_enter_initial_state
|
||||||
|
end
|
||||||
|
|
||||||
|
def aasm_enter_initial_state
|
||||||
|
state_name = aasm_determine_state_name(self.class.aasm_initial_state)
|
||||||
|
state = aasm_state_object_for_state(state_name)
|
||||||
|
|
||||||
|
state.call_action(:before_enter, self)
|
||||||
|
state.call_action(:enter, self)
|
||||||
|
self.aasm_current_state = state_name
|
||||||
|
state.call_action(:after_enter, self)
|
||||||
|
|
||||||
|
state_name
|
||||||
end
|
end
|
||||||
|
|
||||||
def aasm_events_for_current_state
|
def aasm_events_for_current_state
|
||||||
|
@ -97,6 +110,7 @@ module AASM
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_aasm_current_state_with_persistence(state)
|
def set_aasm_current_state_with_persistence(state)
|
||||||
save_success = true
|
save_success = true
|
||||||
if self.respond_to?(:aasm_write_state) || self.private_methods.include?('aasm_write_state')
|
if self.respond_to?(:aasm_write_state) || self.private_methods.include?('aasm_write_state')
|
||||||
|
@ -116,12 +130,12 @@ module AASM
|
||||||
|
|
||||||
def aasm_determine_state_name(state)
|
def aasm_determine_state_name(state)
|
||||||
case state
|
case state
|
||||||
when Symbol, String
|
when Symbol, String
|
||||||
state
|
state
|
||||||
when Proc
|
when Proc
|
||||||
state.call(self)
|
state.call(self)
|
||||||
else
|
else
|
||||||
raise NotImplementedError, "Unrecognized state-type given. Expected Symbol, String, or Proc."
|
raise NotImplementedError, "Unrecognized state-type given. Expected Symbol, String, or Proc."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -144,13 +158,13 @@ module AASM
|
||||||
|
|
||||||
unless new_state_name.nil?
|
unless new_state_name.nil?
|
||||||
new_state = aasm_state_object_for_state(new_state_name)
|
new_state = aasm_state_object_for_state(new_state_name)
|
||||||
|
|
||||||
# new before_ callbacks
|
# new before_ callbacks
|
||||||
old_state.call_action(:before_exit, self)
|
old_state.call_action(:before_exit, self)
|
||||||
new_state.call_action(:before_enter, self)
|
new_state.call_action(:before_enter, self)
|
||||||
|
|
||||||
new_state.call_action(:enter, self)
|
new_state.call_action(:enter, self)
|
||||||
|
|
||||||
persist_successful = true
|
persist_successful = true
|
||||||
if persist
|
if persist
|
||||||
persist_successful = set_aasm_current_state_with_persistence(new_state_name)
|
persist_successful = set_aasm_current_state_with_persistence(new_state_name)
|
||||||
|
@ -159,7 +173,7 @@ module AASM
|
||||||
self.aasm_current_state = new_state_name
|
self.aasm_current_state = new_state_name
|
||||||
end
|
end
|
||||||
|
|
||||||
if persist_successful
|
if persist_successful
|
||||||
old_state.call_action(:after_exit, self)
|
old_state.call_action(:after_exit, self)
|
||||||
new_state.call_action(:after_enter, self)
|
new_state.call_action(:after_enter, self)
|
||||||
event.call_action(:after, self)
|
event.call_action(:after, self)
|
||||||
|
|
|
@ -4,13 +4,11 @@ module AASM
|
||||||
module SupportingClasses
|
module SupportingClasses
|
||||||
class Event
|
class Event
|
||||||
attr_reader :name, :success, :options
|
attr_reader :name, :success, :options
|
||||||
|
|
||||||
def initialize(name, options = {}, &block)
|
def initialize(name, options = {}, &block)
|
||||||
@name = name
|
@name = name
|
||||||
@success = options[:success]
|
|
||||||
@transitions = []
|
@transitions = []
|
||||||
@options = options
|
update(options, &block)
|
||||||
instance_eval(&block) if block
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def fire(obj, to_state=nil, *args)
|
def fire(obj, to_state=nil, *args)
|
||||||
|
@ -37,35 +35,59 @@ module AASM
|
||||||
@transitions.select { |t| t.from == state }
|
@transitions.select { |t| t.from == state }
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute_success_callback(obj, success = nil)
|
|
||||||
callback = success || @success
|
|
||||||
case(callback)
|
|
||||||
when String, Symbol
|
|
||||||
obj.send(callback)
|
|
||||||
when Proc
|
|
||||||
callback.call(obj)
|
|
||||||
when Array
|
|
||||||
callback.each{|meth|self.execute_success_callback(obj, meth)}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def call_action(action, record)
|
|
||||||
action = @options[action]
|
|
||||||
case action
|
|
||||||
when Symbol, String
|
|
||||||
record.send(action)
|
|
||||||
when Proc
|
|
||||||
action.call(record)
|
|
||||||
when Array
|
|
||||||
action.each { |a| record.send(a) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def all_transitions
|
def all_transitions
|
||||||
@transitions
|
@transitions
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def call_action(action, record)
|
||||||
|
action = @options[action]
|
||||||
|
action.is_a?(Array) ?
|
||||||
|
action.each {|a| _call_action(a, record)} :
|
||||||
|
_call_action(action, record)
|
||||||
|
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
|
||||||
|
@options = options
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute_success_callback(obj, success = nil)
|
||||||
|
callback = success || @success
|
||||||
|
case(callback)
|
||||||
|
when String, Symbol
|
||||||
|
obj.send(callback)
|
||||||
|
when Proc
|
||||||
|
callback.call(obj)
|
||||||
|
when Array
|
||||||
|
callback.each{|meth|self.execute_success_callback(obj, meth)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def _call_action(action, record)
|
||||||
|
case action
|
||||||
|
when Symbol, String
|
||||||
|
record.send(action)
|
||||||
|
when Proc
|
||||||
|
action.call(record)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def transitions(trans_opts)
|
def transitions(trans_opts)
|
||||||
Array(trans_opts[:from]).each do |s|
|
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}))
|
||||||
|
|
|
@ -155,7 +155,7 @@ module AASM
|
||||||
# foo.aasm_state # => nil
|
# foo.aasm_state # => nil
|
||||||
#
|
#
|
||||||
def aasm_ensure_initial_state
|
def aasm_ensure_initial_state
|
||||||
send("#{self.class.aasm_column}=", self.aasm_current_state.to_s)
|
send("#{self.class.aasm_column}=", self.aasm_enter_initial_state.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,8 @@ module AASM
|
||||||
attr_reader :name, :options
|
attr_reader :name, :options
|
||||||
|
|
||||||
def initialize(name, options={})
|
def initialize(name, options={})
|
||||||
@name, @options = name, options
|
@name = name
|
||||||
|
update(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def ==(state)
|
def ==(state)
|
||||||
|
@ -17,30 +18,38 @@ module AASM
|
||||||
|
|
||||||
def call_action(action, record)
|
def call_action(action, record)
|
||||||
action = @options[action]
|
action = @options[action]
|
||||||
if action.is_a? Array
|
action.is_a?(Array) ?
|
||||||
action.each do |a|
|
action.each {|a| _call_action(a, record)} :
|
||||||
_call(a, record)
|
_call_action(action, record)
|
||||||
end
|
end
|
||||||
else
|
|
||||||
_call(action, record)
|
def display_name
|
||||||
end
|
@display_name ||= name.to_s.gsub(/_/, ' ').capitalize
|
||||||
end
|
end
|
||||||
|
|
||||||
def for_select
|
def for_select
|
||||||
[name.to_s.gsub(/_/, ' ').capitalize, name.to_s]
|
[display_name, name.to_s]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update(options = {})
|
||||||
|
if options.key?(:display) then
|
||||||
|
@display_name = options.delete(:display)
|
||||||
|
end
|
||||||
|
@options = options
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def _call(action, record)
|
|
||||||
|
def _call_action(action, record)
|
||||||
case action
|
case action
|
||||||
when Symbol, String
|
when Symbol, String
|
||||||
record.send(action)
|
record.send(action)
|
||||||
when Proc
|
when Proc
|
||||||
action.call(record)
|
action.call(record)
|
||||||
when Array
|
|
||||||
action.each { |a| record.send(a) }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,7 +15,7 @@ module AASM
|
||||||
attr_reader :name
|
attr_reader :name
|
||||||
|
|
||||||
def initialize(name)
|
def initialize(name)
|
||||||
@name = name
|
@name = name
|
||||||
@initial_state = nil
|
@initial_state = nil
|
||||||
@states = []
|
@states = []
|
||||||
@events = {}
|
@events = {}
|
||||||
|
|
|
@ -2,6 +2,7 @@ module AASM
|
||||||
module SupportingClasses
|
module SupportingClasses
|
||||||
class StateTransition
|
class StateTransition
|
||||||
attr_reader :from, :to, :opts
|
attr_reader :from, :to, :opts
|
||||||
|
alias_method :options, :opts
|
||||||
|
|
||||||
def initialize(opts)
|
def initialize(opts)
|
||||||
@from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
|
@from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
|
||||||
|
@ -10,29 +11,40 @@ module AASM
|
||||||
|
|
||||||
def perform(obj)
|
def perform(obj)
|
||||||
case @guard
|
case @guard
|
||||||
when Symbol, String
|
when Symbol, String
|
||||||
obj.send(@guard)
|
obj.send(@guard)
|
||||||
when Proc
|
when Proc
|
||||||
@guard.call(obj)
|
@guard.call(obj)
|
||||||
else
|
else
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute(obj, *args)
|
def execute(obj, *args)
|
||||||
case @on_transition
|
@on_transition.is_a?(Array) ?
|
||||||
when Symbol, String
|
@on_transition.each {|ot| _execute(obj, ot, *args)} :
|
||||||
obj.send(@on_transition, *args)
|
_execute(obj, @on_transition, *args)
|
||||||
when Array
|
|
||||||
@on_transition.each{|m| obj.send(m, *args) }
|
|
||||||
when Proc
|
|
||||||
@on_transition.call(obj, *args)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def ==(obj)
|
def ==(obj)
|
||||||
@from == obj.from && @to == obj.to
|
@from == obj.from && @to == obj.to
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def from?(value)
|
||||||
|
@from == value
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def _execute(obj, on_transition, *args)
|
||||||
|
case on_transition
|
||||||
|
when Symbol, String
|
||||||
|
obj.send(on_transition, *args)
|
||||||
|
when Proc
|
||||||
|
on_transition.call(obj, *args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
120
test/functional/auth_machine_test.rb
Normal file
120
test/functional/auth_machine_test.rb
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class AuthMachine
|
||||||
|
include AASM
|
||||||
|
|
||||||
|
attr_accessor :activation_code, :activated_at, :deleted_at
|
||||||
|
|
||||||
|
aasm_initial_state :pending
|
||||||
|
|
||||||
|
aasm_state :passive
|
||||||
|
aasm_state :pending, :enter => :make_activation_code
|
||||||
|
aasm_state :active, :enter => :do_activate
|
||||||
|
aasm_state :suspended
|
||||||
|
aasm_state :deleted, :enter => :do_delete, :exit => :do_undelete
|
||||||
|
|
||||||
|
aasm_event :register do
|
||||||
|
transitions :from => :passive, :to => :pending, :guard => Proc.new {|u| u.can_register? }
|
||||||
|
end
|
||||||
|
|
||||||
|
aasm_event :activate do
|
||||||
|
transitions :from => :pending, :to => :active
|
||||||
|
end
|
||||||
|
|
||||||
|
aasm_event :suspend do
|
||||||
|
transitions :from => [:passive, :pending, :active], :to => :suspended
|
||||||
|
end
|
||||||
|
|
||||||
|
aasm_event :delete do
|
||||||
|
transitions :from => [:passive, :pending, :active, :suspended], :to => :deleted
|
||||||
|
end
|
||||||
|
|
||||||
|
aasm_event :unsuspend do
|
||||||
|
transitions :from => :suspended, :to => :active, :guard => Proc.new {|u| u.has_activated? }
|
||||||
|
transitions :from => :suspended, :to => :pending, :guard => Proc.new {|u| u.has_activation_code? }
|
||||||
|
transitions :from => :suspended, :to => :passive
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
# the AR backend uses a before_validate_on_create :aasm_ensure_initial_state
|
||||||
|
# lets do something similar here for testing purposes.
|
||||||
|
aasm_enter_initial_state
|
||||||
|
end
|
||||||
|
|
||||||
|
def make_activation_code
|
||||||
|
@activation_code = 'moo'
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_activate
|
||||||
|
@activated_at = Time.now
|
||||||
|
@activation_code = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_delete
|
||||||
|
@deleted_at = Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_undelete
|
||||||
|
@deleted_at = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_register?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_activated?
|
||||||
|
!!@activated_at
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_activation_code?
|
||||||
|
!!@activation_code
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class AuthMachineTest < Test::Unit::TestCase
|
||||||
|
context 'authentication state machine' do
|
||||||
|
context 'on initialization' do
|
||||||
|
setup do
|
||||||
|
@auth = AuthMachine.new
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'be in the pending state' do
|
||||||
|
assert_equal :pending, @auth.aasm_current_state
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'have an activation code' do
|
||||||
|
assert @auth.has_activation_code?
|
||||||
|
assert_not_nil @auth.activation_code
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when being unsuspended' do
|
||||||
|
should 'be active if previously activated' do
|
||||||
|
@auth = AuthMachine.new
|
||||||
|
@auth.activate!
|
||||||
|
@auth.suspend!
|
||||||
|
@auth.unsuspend!
|
||||||
|
|
||||||
|
assert_equal :active, @auth.aasm_current_state
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'be pending if not previously activated, but an activation code is present' do
|
||||||
|
@auth = AuthMachine.new
|
||||||
|
@auth.suspend!
|
||||||
|
@auth.unsuspend!
|
||||||
|
|
||||||
|
assert_equal :pending, @auth.aasm_current_state
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'be passive if not previously activated and there is no activation code' do
|
||||||
|
@auth = AuthMachine.new
|
||||||
|
@auth.activation_code = nil
|
||||||
|
@auth.suspend!
|
||||||
|
@auth.unsuspend!
|
||||||
|
|
||||||
|
assert_equal :passive, @auth.aasm_current_state
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
33
test/test_helper.rb
Normal file
33
test/test_helper.rb
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
require 'ostruct'
|
||||||
|
require 'rubygems'
|
||||||
|
|
||||||
|
begin
|
||||||
|
gem 'minitest'
|
||||||
|
rescue Gem::LoadError
|
||||||
|
puts 'minitest gem not found'
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
require 'minitest/autorun'
|
||||||
|
puts 'using minitest'
|
||||||
|
rescue LoadError
|
||||||
|
require 'test/unit'
|
||||||
|
puts 'using test/unit'
|
||||||
|
end
|
||||||
|
|
||||||
|
require 'rr'
|
||||||
|
require 'shoulda'
|
||||||
|
|
||||||
|
class Test::Unit::TestCase
|
||||||
|
include RR::Adapters::TestUnit
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
require 'ruby-debug'
|
||||||
|
Debugger.start
|
||||||
|
rescue LoadError
|
||||||
|
end
|
||||||
|
|
||||||
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
||||||
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
||||||
|
require 'aasm'
|
0
test/unit/aasm_test.rb
Normal file
0
test/unit/aasm_test.rb
Normal file
54
test/unit/event_test.rb
Normal file
54
test/unit/event_test.rb
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class EventTest < Test::Unit::TestCase
|
||||||
|
def new_event
|
||||||
|
@event = AASM::SupportingClasses::Event.new(@name, {:success => @success}) do
|
||||||
|
transitions :to => :closed, :from => [:open, :received]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'event' do
|
||||||
|
setup do
|
||||||
|
@name = :close_order
|
||||||
|
@success = :success_callback
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'set the name' do
|
||||||
|
assert_equal @name, new_event.name
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'set the success option' do
|
||||||
|
assert_equal @success, new_event.success
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'create StateTransitions' do
|
||||||
|
mock(AASM::SupportingClasses::StateTransition).new({:to => :closed, :from => :open})
|
||||||
|
mock(AASM::SupportingClasses::StateTransition).new({:to => :closed, :from => :received})
|
||||||
|
new_event
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when firing' do
|
||||||
|
should 'raise an AASM::InvalidTransition error if the transitions are empty' do
|
||||||
|
event = AASM::SupportingClasses::Event.new(:event)
|
||||||
|
|
||||||
|
obj = OpenStruct.new
|
||||||
|
obj.aasm_current_state = :open
|
||||||
|
|
||||||
|
assert_raise AASM::InvalidTransition do
|
||||||
|
event.fire(obj)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'return the state of the first matching transition it finds' do
|
||||||
|
event = AASM::SupportingClasses::Event.new(:event) do
|
||||||
|
transitions :to => :closed, :from => [:open, :received]
|
||||||
|
end
|
||||||
|
|
||||||
|
obj = OpenStruct.new
|
||||||
|
obj.aasm_current_state = :open
|
||||||
|
|
||||||
|
assert_equal :closed, event.fire(obj)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
69
test/unit/state_test.rb
Normal file
69
test/unit/state_test.rb
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class StateTest < Test::Unit::TestCase
|
||||||
|
def new_state(options={})
|
||||||
|
AASM::SupportingClasses::State.new(@name, @options.merge(options))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'state' do
|
||||||
|
setup do
|
||||||
|
@name = :astate
|
||||||
|
@options = { :crazy_custom_key => 'key' }
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'set the name' do
|
||||||
|
assert_equal :astate, new_state.name
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'set the display_name from name' do
|
||||||
|
assert_equal "Astate", new_state.display_name
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'set the display_name from options' do
|
||||||
|
assert_equal "A State", new_state(:display => "A State").display_name
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'set the options and expose them as options' do
|
||||||
|
assert_equal @options, new_state.options
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'equal a symbol of the same name' do
|
||||||
|
assert_equal new_state, :astate
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'equal a state of the same name' do
|
||||||
|
assert_equal new_state, new_state
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'send a message to the record for an action if the action is present as a symbol' do
|
||||||
|
state = new_state(:entering => :foo)
|
||||||
|
mock(record = Object.new).foo
|
||||||
|
state.call_action(:entering, record)
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'send a message to the record for an action if the action is present as a string' do
|
||||||
|
state = new_state(:entering => 'foo')
|
||||||
|
mock(record = Object.new).foo
|
||||||
|
state.call_action(:entering, record)
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'call a proc with the record as its argument for an action if the action is present as a proc' do
|
||||||
|
state = new_state(:entering => Proc.new {|r| r.foobar})
|
||||||
|
mock(record = Object.new).foobar
|
||||||
|
state.call_action(:entering, record)
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'send a message to the record for each action if the action is present as an array' do
|
||||||
|
state = new_state(:entering => [:a, :b, 'c', lambda {|r| r.foobar}])
|
||||||
|
|
||||||
|
record = Object.new
|
||||||
|
mock(record).a
|
||||||
|
mock(record).b
|
||||||
|
mock(record).c
|
||||||
|
mock(record).foobar
|
||||||
|
|
||||||
|
state.call_action(:entering, record)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
75
test/unit/state_transition_test.rb
Normal file
75
test/unit/state_transition_test.rb
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class StateTransitionTest < Test::Unit::TestCase
|
||||||
|
context 'state transition' do
|
||||||
|
setup do
|
||||||
|
@opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
|
||||||
|
@st = AASM::SupportingClasses::StateTransition.new(@opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'set from, to, and opts attr readers' do
|
||||||
|
assert_equal @opts[:from], @st.from
|
||||||
|
assert_equal @opts[:to], @st.to
|
||||||
|
assert_equal @opts, @st.options
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'pass equality check if from and to are the same' do
|
||||||
|
obj = OpenStruct.new
|
||||||
|
obj.from = @opts[:from]
|
||||||
|
obj.to = @opts[:to]
|
||||||
|
|
||||||
|
assert_equal @st, obj
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'fail equality check if from is not the same' do
|
||||||
|
obj = OpenStruct.new
|
||||||
|
obj.from = 'blah'
|
||||||
|
obj.to = @opts[:to]
|
||||||
|
|
||||||
|
assert_not_equal @st, obj
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'fail equality check if to is not the same' do
|
||||||
|
obj = OpenStruct.new
|
||||||
|
obj.from = @opts[:from]
|
||||||
|
obj.to = 'blah'
|
||||||
|
|
||||||
|
assert_not_equal @st, obj
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when performing guard checks' do
|
||||||
|
should 'return true if there is no guard' do
|
||||||
|
opts = {:from => 'foo', :to => 'bar'}
|
||||||
|
st = AASM::SupportingClasses::StateTransition.new(opts)
|
||||||
|
assert st.perform(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'call the method on the object if guard is a symbol' do
|
||||||
|
opts = {:from => 'foo', :to => 'bar', :guard => :test_guard}
|
||||||
|
st = AASM::SupportingClasses::StateTransition.new(opts)
|
||||||
|
|
||||||
|
mock(obj = Object.new).test_guard
|
||||||
|
|
||||||
|
st.perform(obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'call the method on the object if guard is a string' do
|
||||||
|
opts = {:from => 'foo', :to => 'bar', :guard => 'test_guard'}
|
||||||
|
st = AASM::SupportingClasses::StateTransition.new(opts)
|
||||||
|
|
||||||
|
mock(obj = Object.new).test_guard
|
||||||
|
|
||||||
|
st.perform(obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
should 'call the proc passing the object if guard is a proc' do
|
||||||
|
opts = {:from => 'foo', :to => 'bar', :guard => Proc.new {|o| o.test_guard}}
|
||||||
|
st = AASM::SupportingClasses::StateTransition.new(opts)
|
||||||
|
|
||||||
|
mock(obj = Object.new).test_guard
|
||||||
|
|
||||||
|
st.perform(obj)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue