aasm 2.1.1 → 2.1.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/.document +5 -0
- data/.gitignore +8 -0
- data/{MIT-LICENSE → LICENSE} +0 -0
- data/README.rdoc +10 -4
- data/Rakefile +86 -73
- data/VERSION +1 -0
- data/lib/aasm/aasm.rb +25 -15
- data/lib/aasm/event.rb +45 -23
- data/lib/aasm/persistence/active_record_persistence.rb +1 -1
- data/lib/aasm/state.rb +30 -10
- data/lib/aasm/state_machine.rb +2 -1
- data/lib/aasm/state_transition.rb +26 -12
- data/spec/functional/conversation.rb +49 -0
- data/spec/functional/conversation_spec.rb +8 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/unit/aasm_spec.rb +432 -0
- data/spec/unit/active_record_persistence_spec.rb +254 -0
- data/spec/unit/before_after_callbacks_spec.rb +79 -0
- data/spec/unit/event_spec.rb +126 -0
- data/spec/unit/state_spec.rb +74 -0
- data/spec/unit/state_transition_spec.rb +84 -0
- data/test/functional/auth_machine_test.rb +120 -0
- data/test/test_helper.rb +33 -0
- data/test/unit/aasm_test.rb +0 -0
- data/test/unit/event_test.rb +54 -0
- data/test/unit/state_test.rb +69 -0
- data/test/unit/state_transition_test.rb +75 -0
- metadata +77 -24
- data/CHANGELOG +0 -33
- data/TODO +0 -9
- data/doc/jamis.rb +0 -591
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'spec_helper')
|
2
|
+
|
3
|
+
# TODO These are specs ported from original aasm
|
4
|
+
describe AASM::SupportingClasses::State do
|
5
|
+
before(:each) do
|
6
|
+
@name = :astate
|
7
|
+
@options = { :crazy_custom_key => 'key' }
|
8
|
+
end
|
9
|
+
|
10
|
+
def new_state(options={})
|
11
|
+
AASM::SupportingClasses::State.new(@name, @options.merge(options))
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should set the name' do
|
15
|
+
state = new_state
|
16
|
+
|
17
|
+
state.name.should == :astate
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should set the options and expose them as options' do
|
21
|
+
state = new_state
|
22
|
+
|
23
|
+
state.options.should == @options
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should be equal to a symbol of the same name' do
|
27
|
+
state = new_state
|
28
|
+
|
29
|
+
state.should == :astate
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should be equal to a State of the same name' do
|
33
|
+
new_state.should == new_state
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should send a message to the record for an action if the action is present as a symbol' do
|
37
|
+
state = new_state(:entering => :foo)
|
38
|
+
|
39
|
+
record = mock('record')
|
40
|
+
record.should_receive(:foo)
|
41
|
+
|
42
|
+
state.call_action(:entering, record)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should send a message to the record for an action if the action is present as a string' do
|
46
|
+
state = new_state(:entering => 'foo')
|
47
|
+
|
48
|
+
record = mock('record')
|
49
|
+
record.should_receive(:foo)
|
50
|
+
|
51
|
+
state.call_action(:entering, record)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should send a message to the record for each action' do
|
55
|
+
state = new_state(:entering => [:a, :b, "c", lambda {|r| r.foobar }])
|
56
|
+
|
57
|
+
record = mock('record')
|
58
|
+
record.should_receive(:a)
|
59
|
+
record.should_receive(:b)
|
60
|
+
record.should_receive(:c)
|
61
|
+
record.should_receive(:foobar)
|
62
|
+
|
63
|
+
state.call_action(:entering, record)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should call a proc, passing in the record for an action if the action is present' do
|
67
|
+
state = new_state(:entering => Proc.new {|r| r.foobar})
|
68
|
+
|
69
|
+
record = mock('record')
|
70
|
+
record.should_receive(:foobar)
|
71
|
+
|
72
|
+
state.call_action(:entering, record)
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'spec_helper')
|
2
|
+
|
3
|
+
describe AASM::SupportingClasses::StateTransition do
|
4
|
+
it 'should set from, to, and opts attr readers' do
|
5
|
+
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
|
6
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
7
|
+
|
8
|
+
st.from.should == opts[:from]
|
9
|
+
st.to.should == opts[:to]
|
10
|
+
st.opts.should == opts
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should pass equality check if from and to are the same' do
|
14
|
+
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
|
15
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
16
|
+
|
17
|
+
obj = mock('object')
|
18
|
+
obj.stub!(:from).and_return(opts[:from])
|
19
|
+
obj.stub!(:to).and_return(opts[:to])
|
20
|
+
|
21
|
+
st.should == obj
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should fail equality check if from are not the same' do
|
25
|
+
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
|
26
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
27
|
+
|
28
|
+
obj = mock('object')
|
29
|
+
obj.stub!(:from).and_return('blah')
|
30
|
+
obj.stub!(:to).and_return(opts[:to])
|
31
|
+
|
32
|
+
st.should_not == obj
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should fail equality check if to are not the same' do
|
36
|
+
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
|
37
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
38
|
+
|
39
|
+
obj = mock('object')
|
40
|
+
obj.stub!(:from).and_return(opts[:from])
|
41
|
+
obj.stub!(:to).and_return('blah')
|
42
|
+
|
43
|
+
st.should_not == obj
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe AASM::SupportingClasses::StateTransition, '- when performing guard checks' do
|
48
|
+
it 'should return true of there is no guard' do
|
49
|
+
opts = {:from => 'foo', :to => 'bar'}
|
50
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
51
|
+
|
52
|
+
st.perform(nil).should be_true
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should call the method on the object if guard is a symbol' do
|
56
|
+
opts = {:from => 'foo', :to => 'bar', :guard => :test}
|
57
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
58
|
+
|
59
|
+
obj = mock('object')
|
60
|
+
obj.should_receive(:test)
|
61
|
+
|
62
|
+
st.perform(obj)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should call the method on the object if guard is a string' do
|
66
|
+
opts = {:from => 'foo', :to => 'bar', :guard => 'test'}
|
67
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
68
|
+
|
69
|
+
obj = mock('object')
|
70
|
+
obj.should_receive(:test)
|
71
|
+
|
72
|
+
st.perform(obj)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should call the proc passing the object if the guard is a proc' do
|
76
|
+
opts = {:from => 'foo', :to => 'bar', :guard => Proc.new {|o| o.test}}
|
77
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
78
|
+
|
79
|
+
obj = mock('object')
|
80
|
+
obj.should_receive(:test)
|
81
|
+
|
82
|
+
st.perform(obj)
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class AuthMachine
|
4
|
+
include AASM
|
5
|
+
|
6
|
+
attr_accessor :activation_code, :activated_at, :deleted_at
|
7
|
+
|
8
|
+
aasm_initial_state :pending
|
9
|
+
|
10
|
+
aasm_state :passive
|
11
|
+
aasm_state :pending, :enter => :make_activation_code
|
12
|
+
aasm_state :active, :enter => :do_activate
|
13
|
+
aasm_state :suspended
|
14
|
+
aasm_state :deleted, :enter => :do_delete, :exit => :do_undelete
|
15
|
+
|
16
|
+
aasm_event :register do
|
17
|
+
transitions :from => :passive, :to => :pending, :guard => Proc.new {|u| u.can_register? }
|
18
|
+
end
|
19
|
+
|
20
|
+
aasm_event :activate do
|
21
|
+
transitions :from => :pending, :to => :active
|
22
|
+
end
|
23
|
+
|
24
|
+
aasm_event :suspend do
|
25
|
+
transitions :from => [:passive, :pending, :active], :to => :suspended
|
26
|
+
end
|
27
|
+
|
28
|
+
aasm_event :delete do
|
29
|
+
transitions :from => [:passive, :pending, :active, :suspended], :to => :deleted
|
30
|
+
end
|
31
|
+
|
32
|
+
aasm_event :unsuspend do
|
33
|
+
transitions :from => :suspended, :to => :active, :guard => Proc.new {|u| u.has_activated? }
|
34
|
+
transitions :from => :suspended, :to => :pending, :guard => Proc.new {|u| u.has_activation_code? }
|
35
|
+
transitions :from => :suspended, :to => :passive
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
# the AR backend uses a before_validate_on_create :aasm_ensure_initial_state
|
40
|
+
# lets do something similar here for testing purposes.
|
41
|
+
aasm_enter_initial_state
|
42
|
+
end
|
43
|
+
|
44
|
+
def make_activation_code
|
45
|
+
@activation_code = 'moo'
|
46
|
+
end
|
47
|
+
|
48
|
+
def do_activate
|
49
|
+
@activated_at = Time.now
|
50
|
+
@activation_code = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def do_delete
|
54
|
+
@deleted_at = Time.now
|
55
|
+
end
|
56
|
+
|
57
|
+
def do_undelete
|
58
|
+
@deleted_at = false
|
59
|
+
end
|
60
|
+
|
61
|
+
def can_register?
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
def has_activated?
|
66
|
+
!!@activated_at
|
67
|
+
end
|
68
|
+
|
69
|
+
def has_activation_code?
|
70
|
+
!!@activation_code
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class AuthMachineTest < Test::Unit::TestCase
|
75
|
+
context 'authentication state machine' do
|
76
|
+
context 'on initialization' do
|
77
|
+
setup do
|
78
|
+
@auth = AuthMachine.new
|
79
|
+
end
|
80
|
+
|
81
|
+
should 'be in the pending state' do
|
82
|
+
assert_equal :pending, @auth.aasm_current_state
|
83
|
+
end
|
84
|
+
|
85
|
+
should 'have an activation code' do
|
86
|
+
assert @auth.has_activation_code?
|
87
|
+
assert_not_nil @auth.activation_code
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'when being unsuspended' do
|
92
|
+
should 'be active if previously activated' do
|
93
|
+
@auth = AuthMachine.new
|
94
|
+
@auth.activate!
|
95
|
+
@auth.suspend!
|
96
|
+
@auth.unsuspend!
|
97
|
+
|
98
|
+
assert_equal :active, @auth.aasm_current_state
|
99
|
+
end
|
100
|
+
|
101
|
+
should 'be pending if not previously activated, but an activation code is present' do
|
102
|
+
@auth = AuthMachine.new
|
103
|
+
@auth.suspend!
|
104
|
+
@auth.unsuspend!
|
105
|
+
|
106
|
+
assert_equal :pending, @auth.aasm_current_state
|
107
|
+
end
|
108
|
+
|
109
|
+
should 'be passive if not previously activated and there is no activation code' do
|
110
|
+
@auth = AuthMachine.new
|
111
|
+
@auth.activation_code = nil
|
112
|
+
@auth.suspend!
|
113
|
+
@auth.unsuspend!
|
114
|
+
|
115
|
+
assert_equal :passive, @auth.aasm_current_state
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'rubygems'
|
3
|
+
|
4
|
+
begin
|
5
|
+
gem 'minitest'
|
6
|
+
rescue Gem::LoadError
|
7
|
+
puts 'minitest gem not found'
|
8
|
+
end
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'minitest/autorun'
|
12
|
+
puts 'using minitest'
|
13
|
+
rescue LoadError
|
14
|
+
require 'test/unit'
|
15
|
+
puts 'using test/unit'
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'rr'
|
19
|
+
require 'shoulda'
|
20
|
+
|
21
|
+
class Test::Unit::TestCase
|
22
|
+
include RR::Adapters::TestUnit
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
require 'ruby-debug'
|
27
|
+
Debugger.start
|
28
|
+
rescue LoadError
|
29
|
+
end
|
30
|
+
|
31
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
32
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
33
|
+
require 'aasm'
|
File without changes
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class EventTest < Test::Unit::TestCase
|
4
|
+
def new_event
|
5
|
+
@event = AASM::SupportingClasses::Event.new(@name, {:success => @success}) do
|
6
|
+
transitions :to => :closed, :from => [:open, :received]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'event' do
|
11
|
+
setup do
|
12
|
+
@name = :close_order
|
13
|
+
@success = :success_callback
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'set the name' do
|
17
|
+
assert_equal @name, new_event.name
|
18
|
+
end
|
19
|
+
|
20
|
+
should 'set the success option' do
|
21
|
+
assert_equal @success, new_event.success
|
22
|
+
end
|
23
|
+
|
24
|
+
should 'create StateTransitions' do
|
25
|
+
mock(AASM::SupportingClasses::StateTransition).new({:to => :closed, :from => :open})
|
26
|
+
mock(AASM::SupportingClasses::StateTransition).new({:to => :closed, :from => :received})
|
27
|
+
new_event
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when firing' do
|
31
|
+
should 'raise an AASM::InvalidTransition error if the transitions are empty' do
|
32
|
+
event = AASM::SupportingClasses::Event.new(:event)
|
33
|
+
|
34
|
+
obj = OpenStruct.new
|
35
|
+
obj.aasm_current_state = :open
|
36
|
+
|
37
|
+
assert_raise AASM::InvalidTransition do
|
38
|
+
event.fire(obj)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
should 'return the state of the first matching transition it finds' do
|
43
|
+
event = AASM::SupportingClasses::Event.new(:event) do
|
44
|
+
transitions :to => :closed, :from => [:open, :received]
|
45
|
+
end
|
46
|
+
|
47
|
+
obj = OpenStruct.new
|
48
|
+
obj.aasm_current_state = :open
|
49
|
+
|
50
|
+
assert_equal :closed, event.fire(obj)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StateTest < Test::Unit::TestCase
|
4
|
+
def new_state(options={})
|
5
|
+
AASM::SupportingClasses::State.new(@name, @options.merge(options))
|
6
|
+
end
|
7
|
+
|
8
|
+
context 'state' do
|
9
|
+
setup do
|
10
|
+
@name = :astate
|
11
|
+
@options = { :crazy_custom_key => 'key' }
|
12
|
+
end
|
13
|
+
|
14
|
+
should 'set the name' do
|
15
|
+
assert_equal :astate, new_state.name
|
16
|
+
end
|
17
|
+
|
18
|
+
should 'set the display_name from name' do
|
19
|
+
assert_equal "Astate", new_state.display_name
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'set the display_name from options' do
|
23
|
+
assert_equal "A State", new_state(:display => "A State").display_name
|
24
|
+
end
|
25
|
+
|
26
|
+
should 'set the options and expose them as options' do
|
27
|
+
assert_equal @options, new_state.options
|
28
|
+
end
|
29
|
+
|
30
|
+
should 'equal a symbol of the same name' do
|
31
|
+
assert_equal new_state, :astate
|
32
|
+
end
|
33
|
+
|
34
|
+
should 'equal a state of the same name' do
|
35
|
+
assert_equal new_state, new_state
|
36
|
+
end
|
37
|
+
|
38
|
+
should 'send a message to the record for an action if the action is present as a symbol' do
|
39
|
+
state = new_state(:entering => :foo)
|
40
|
+
mock(record = Object.new).foo
|
41
|
+
state.call_action(:entering, record)
|
42
|
+
end
|
43
|
+
|
44
|
+
should 'send a message to the record for an action if the action is present as a string' do
|
45
|
+
state = new_state(:entering => 'foo')
|
46
|
+
mock(record = Object.new).foo
|
47
|
+
state.call_action(:entering, record)
|
48
|
+
end
|
49
|
+
|
50
|
+
should 'call a proc with the record as its argument for an action if the action is present as a proc' do
|
51
|
+
state = new_state(:entering => Proc.new {|r| r.foobar})
|
52
|
+
mock(record = Object.new).foobar
|
53
|
+
state.call_action(:entering, record)
|
54
|
+
end
|
55
|
+
|
56
|
+
should 'send a message to the record for each action if the action is present as an array' do
|
57
|
+
state = new_state(:entering => [:a, :b, 'c', lambda {|r| r.foobar}])
|
58
|
+
|
59
|
+
record = Object.new
|
60
|
+
mock(record).a
|
61
|
+
mock(record).b
|
62
|
+
mock(record).c
|
63
|
+
mock(record).foobar
|
64
|
+
|
65
|
+
state.call_action(:entering, record)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StateTransitionTest < Test::Unit::TestCase
|
4
|
+
context 'state transition' do
|
5
|
+
setup do
|
6
|
+
@opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
|
7
|
+
@st = AASM::SupportingClasses::StateTransition.new(@opts)
|
8
|
+
end
|
9
|
+
|
10
|
+
should 'set from, to, and opts attr readers' do
|
11
|
+
assert_equal @opts[:from], @st.from
|
12
|
+
assert_equal @opts[:to], @st.to
|
13
|
+
assert_equal @opts, @st.options
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'pass equality check if from and to are the same' do
|
17
|
+
obj = OpenStruct.new
|
18
|
+
obj.from = @opts[:from]
|
19
|
+
obj.to = @opts[:to]
|
20
|
+
|
21
|
+
assert_equal @st, obj
|
22
|
+
end
|
23
|
+
|
24
|
+
should 'fail equality check if from is not the same' do
|
25
|
+
obj = OpenStruct.new
|
26
|
+
obj.from = 'blah'
|
27
|
+
obj.to = @opts[:to]
|
28
|
+
|
29
|
+
assert_not_equal @st, obj
|
30
|
+
end
|
31
|
+
|
32
|
+
should 'fail equality check if to is not the same' do
|
33
|
+
obj = OpenStruct.new
|
34
|
+
obj.from = @opts[:from]
|
35
|
+
obj.to = 'blah'
|
36
|
+
|
37
|
+
assert_not_equal @st, obj
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'when performing guard checks' do
|
41
|
+
should 'return true if there is no guard' do
|
42
|
+
opts = {:from => 'foo', :to => 'bar'}
|
43
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
44
|
+
assert st.perform(nil)
|
45
|
+
end
|
46
|
+
|
47
|
+
should 'call the method on the object if guard is a symbol' do
|
48
|
+
opts = {:from => 'foo', :to => 'bar', :guard => :test_guard}
|
49
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
50
|
+
|
51
|
+
mock(obj = Object.new).test_guard
|
52
|
+
|
53
|
+
st.perform(obj)
|
54
|
+
end
|
55
|
+
|
56
|
+
should 'call the method on the object if guard is a string' do
|
57
|
+
opts = {:from => 'foo', :to => 'bar', :guard => 'test_guard'}
|
58
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
59
|
+
|
60
|
+
mock(obj = Object.new).test_guard
|
61
|
+
|
62
|
+
st.perform(obj)
|
63
|
+
end
|
64
|
+
|
65
|
+
should 'call the proc passing the object if guard is a proc' do
|
66
|
+
opts = {:from => 'foo', :to => 'bar', :guard => Proc.new {|o| o.test_guard}}
|
67
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
68
|
+
|
69
|
+
mock(obj = Object.new).test_guard
|
70
|
+
|
71
|
+
st.perform(obj)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|