alexrevin-aasm_numerical 2.3.1
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 +11 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +149 -0
- data/Rakefile +27 -0
- data/lib/alexrevin-aasm_numerical.rb +10 -0
- data/lib/alexrevin-aasm_numerical/aasm.rb +222 -0
- data/lib/alexrevin-aasm_numerical/event.rb +127 -0
- data/lib/alexrevin-aasm_numerical/localizer.rb +36 -0
- data/lib/alexrevin-aasm_numerical/persistence.rb +14 -0
- data/lib/alexrevin-aasm_numerical/persistence/active_record_persistence.rb +257 -0
- data/lib/alexrevin-aasm_numerical/state.rb +53 -0
- data/lib/alexrevin-aasm_numerical/state_machine.rb +31 -0
- data/lib/alexrevin-aasm_numerical/state_transition.rb +46 -0
- data/lib/alexrevin-aasm_numerical/supporting_classes.rb +6 -0
- data/lib/alexrevin-aasm_numerical/version.rb +3 -0
- data/spec/database.yml +3 -0
- data/spec/en.yml +10 -0
- data/spec/functional/conversation.rb +49 -0
- data/spec/functional/conversation_spec.rb +8 -0
- data/spec/schema.rb +7 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/unit/aasm_spec.rb +462 -0
- data/spec/unit/active_record_persistence_spec.rb +246 -0
- data/spec/unit/before_after_callbacks_spec.rb +79 -0
- data/spec/unit/event_spec.rb +140 -0
- data/spec/unit/localizer_spec.rb +51 -0
- data/spec/unit/state_spec.rb +85 -0
- data/spec/unit/state_transition_spec.rb +163 -0
- data/test/functional/auth_machine_test.rb +148 -0
- data/test/models/process.rb +18 -0
- data/test/test_helper.rb +43 -0
- data/test/unit/aasm_test.rb +0 -0
- data/test/unit/event_test.rb +54 -0
- data/test/unit/state_machine_test.rb +37 -0
- data/test/unit/state_test.rb +69 -0
- data/test/unit/state_transition_test.rb +75 -0
- metadata +254 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record'
|
3
|
+
require 'logger'
|
4
|
+
require 'i18n'
|
5
|
+
|
6
|
+
ActiveRecord::Base.logger = Logger.new(STDERR)
|
7
|
+
|
8
|
+
class LocalizerTestModel < ActiveRecord::Base
|
9
|
+
include AASM
|
10
|
+
|
11
|
+
attr_accessor :aasm_state
|
12
|
+
|
13
|
+
aasm_initial_state :open
|
14
|
+
aasm_state :opened
|
15
|
+
aasm_state :closed
|
16
|
+
|
17
|
+
aasm_event :close
|
18
|
+
aasm_event :open
|
19
|
+
end
|
20
|
+
|
21
|
+
describe AASM::Localizer do
|
22
|
+
before(:all) do
|
23
|
+
I18n.load_path << 'spec/en.yml'
|
24
|
+
I18n.default_locale = :en
|
25
|
+
end
|
26
|
+
|
27
|
+
after(:all) { I18n.load_path.clear }
|
28
|
+
|
29
|
+
let (:foo_opened) { LocalizerTestModel.new }
|
30
|
+
let (:foo_closed) { LocalizerTestModel.new.tap { |x| x.aasm_state = :closed } }
|
31
|
+
|
32
|
+
context '.human_state' do
|
33
|
+
it 'should return translated state value' do
|
34
|
+
foo_opened.human_state.should == "It's opened now!"
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should return humanized value if not localized' do
|
38
|
+
foo_closed.human_state.should == "Closed"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context '.human_event_name' do
|
43
|
+
it 'should return translated event name' do
|
44
|
+
LocalizerTestModel.human_event_name(:close).should == "Let's close it!"
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should return humanized event name' do
|
48
|
+
LocalizerTestModel.human_event_name(:open).should == "Open"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.expand_path(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 stop calling actions if one of them raises :halt_aasm_chain" do
|
67
|
+
state = new_state(:entering => [:a, :b, :c])
|
68
|
+
|
69
|
+
record = mock('record')
|
70
|
+
record.should_receive(:a)
|
71
|
+
record.should_receive(:b).and_throw(:halt_aasm_chain)
|
72
|
+
record.should_not_receive(:c)
|
73
|
+
|
74
|
+
state.call_action(:entering, record)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should call a proc, passing in the record for an action if the action is present' do
|
78
|
+
state = new_state(:entering => Proc.new {|r| r.foobar})
|
79
|
+
|
80
|
+
record = mock('record')
|
81
|
+
record.should_receive(:foobar)
|
82
|
+
|
83
|
+
state.call_action(:entering, record)
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require File.expand_path(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
|
85
|
+
|
86
|
+
describe AASM::SupportingClasses::StateTransition, '- when executing the transition with a Proc' do
|
87
|
+
it 'should call a Proc on the object with args' do
|
88
|
+
opts = {:from => 'foo', :to => 'bar', :on_transition => Proc.new {|o| o.test}}
|
89
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
90
|
+
args = {:arg1 => '1', :arg2 => '2'}
|
91
|
+
obj = mock('object')
|
92
|
+
|
93
|
+
opts[:on_transition].should_receive(:call).with(any_args)
|
94
|
+
|
95
|
+
st.execute(obj, args)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should call a Proc on the object without args' do
|
99
|
+
opts = {:from => 'foo', :to => 'bar', :on_transition => Proc.new {||}}
|
100
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
101
|
+
args = {:arg1 => '1', :arg2 => '2'}
|
102
|
+
obj = mock('object')
|
103
|
+
|
104
|
+
opts[:on_transition].should_receive(:call).with(no_args)
|
105
|
+
|
106
|
+
st.execute(obj, args)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe AASM::SupportingClasses::StateTransition, '- when executing the transition with an :on_transtion method call' do
|
111
|
+
it 'should accept a String for the method name' do
|
112
|
+
opts = {:from => 'foo', :to => 'bar', :on_transition => 'test'}
|
113
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
114
|
+
args = {:arg1 => '1', :arg2 => '2'}
|
115
|
+
obj = mock('object')
|
116
|
+
|
117
|
+
obj.should_receive(:test)
|
118
|
+
|
119
|
+
st.execute(obj, args)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should accept a Symbol for the method name' do
|
123
|
+
opts = {:from => 'foo', :to => 'bar', :on_transition => :test}
|
124
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
125
|
+
args = {:arg1 => '1', :arg2 => '2'}
|
126
|
+
obj = mock('object')
|
127
|
+
|
128
|
+
obj.should_receive(:test)
|
129
|
+
|
130
|
+
st.execute(obj, args)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should pass args if the target method accepts them' do
|
134
|
+
opts = {:from => 'foo', :to => 'bar', :on_transition => :test}
|
135
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
136
|
+
args = {:arg1 => '1', :arg2 => '2'}
|
137
|
+
obj = mock('object')
|
138
|
+
|
139
|
+
obj.class.class_eval do
|
140
|
+
define_method(:test) {|*args| 'success'}
|
141
|
+
end
|
142
|
+
|
143
|
+
return_value = st.execute(obj, args)
|
144
|
+
|
145
|
+
return_value.should == 'success'
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should NOT pass args if the target method does NOT accept them' do
|
149
|
+
opts = {:from => 'foo', :to => 'bar', :on_transition => :test}
|
150
|
+
st = AASM::SupportingClasses::StateTransition.new(opts)
|
151
|
+
args = {:arg1 => '1', :arg2 => '2'}
|
152
|
+
obj = mock('object')
|
153
|
+
|
154
|
+
obj.class.class_eval do
|
155
|
+
define_method(:test) {|*args| 'success'}
|
156
|
+
end
|
157
|
+
|
158
|
+
return_value = st.execute(obj, args)
|
159
|
+
|
160
|
+
return_value.should == 'success'
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
@@ -0,0 +1,148 @@
|
|
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
|
+
# a dummy event that can never happen
|
33
|
+
aasm_event :unpassify do
|
34
|
+
transitions :from => :passive, :to => :active, :guard => Proc.new {|u| false }
|
35
|
+
end
|
36
|
+
|
37
|
+
aasm_event :unsuspend do
|
38
|
+
transitions :from => :suspended, :to => :active, :guard => Proc.new {|u| u.has_activated? }
|
39
|
+
transitions :from => :suspended, :to => :pending, :guard => Proc.new {|u| u.has_activation_code? }
|
40
|
+
transitions :from => :suspended, :to => :passive
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
# the AR backend uses a before_validate_on_create :aasm_ensure_initial_state
|
45
|
+
# lets do something similar here for testing purposes.
|
46
|
+
aasm_enter_initial_state
|
47
|
+
end
|
48
|
+
|
49
|
+
def make_activation_code
|
50
|
+
@activation_code = 'moo'
|
51
|
+
end
|
52
|
+
|
53
|
+
def do_activate
|
54
|
+
@activated_at = Time.now
|
55
|
+
@activation_code = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def do_delete
|
59
|
+
@deleted_at = Time.now
|
60
|
+
end
|
61
|
+
|
62
|
+
def do_undelete
|
63
|
+
@deleted_at = false
|
64
|
+
end
|
65
|
+
|
66
|
+
def can_register?
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
def has_activated?
|
71
|
+
!!@activated_at
|
72
|
+
end
|
73
|
+
|
74
|
+
def has_activation_code?
|
75
|
+
!!@activation_code
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class AuthMachineTest < Test::Unit::TestCase
|
80
|
+
context 'authentication state machine' do
|
81
|
+
context 'on initialization' do
|
82
|
+
setup do
|
83
|
+
@auth = AuthMachine.new
|
84
|
+
end
|
85
|
+
|
86
|
+
should 'be in the pending state' do
|
87
|
+
assert_equal :pending, @auth.aasm_current_state
|
88
|
+
end
|
89
|
+
|
90
|
+
should 'have an activation code' do
|
91
|
+
assert @auth.has_activation_code?
|
92
|
+
assert_not_nil @auth.activation_code
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'when being unsuspended' do
|
97
|
+
|
98
|
+
should 'be able to be unsuspended' do
|
99
|
+
@auth = AuthMachine.new
|
100
|
+
@auth.activate!
|
101
|
+
@auth.suspend!
|
102
|
+
assert @auth.may_unsuspend?
|
103
|
+
end
|
104
|
+
|
105
|
+
should 'not be able to be unsuspended into active' do
|
106
|
+
@auth = AuthMachine.new
|
107
|
+
@auth.suspend!
|
108
|
+
assert_equal false, @auth.may_unsuspend?(:active)
|
109
|
+
end
|
110
|
+
|
111
|
+
should 'not be able to be unpassified' do
|
112
|
+
@auth = AuthMachine.new
|
113
|
+
@auth.activate!
|
114
|
+
@auth.suspend!
|
115
|
+
@auth.unsuspend!
|
116
|
+
|
117
|
+
assert_equal false, @auth.may_unpassify?
|
118
|
+
end
|
119
|
+
|
120
|
+
should 'be active if previously activated' do
|
121
|
+
@auth = AuthMachine.new
|
122
|
+
@auth.activate!
|
123
|
+
@auth.suspend!
|
124
|
+
@auth.unsuspend!
|
125
|
+
|
126
|
+
assert_equal :active, @auth.aasm_current_state
|
127
|
+
end
|
128
|
+
|
129
|
+
should 'be pending if not previously activated, but an activation code is present' do
|
130
|
+
@auth = AuthMachine.new
|
131
|
+
@auth.suspend!
|
132
|
+
@auth.unsuspend!
|
133
|
+
|
134
|
+
assert_equal :pending, @auth.aasm_current_state
|
135
|
+
end
|
136
|
+
|
137
|
+
should 'be passive if not previously activated and there is no activation code' do
|
138
|
+
@auth = AuthMachine.new
|
139
|
+
@auth.activation_code = nil
|
140
|
+
@auth.suspend!
|
141
|
+
@auth.unsuspend!
|
142
|
+
|
143
|
+
assert_equal :passive, @auth.aasm_current_state
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|