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,254 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'aasm')
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rubygems'
|
5
|
+
require 'active_record'
|
6
|
+
|
7
|
+
# A dummy class for mocking the activerecord connection class
|
8
|
+
class Connection
|
9
|
+
end
|
10
|
+
|
11
|
+
class FooBar < ActiveRecord::Base
|
12
|
+
include AASM
|
13
|
+
|
14
|
+
# Fake this column for testing purposes
|
15
|
+
attr_accessor :aasm_state
|
16
|
+
|
17
|
+
aasm_state :open
|
18
|
+
aasm_state :closed
|
19
|
+
|
20
|
+
aasm_event :view do
|
21
|
+
transitions :to => :read, :from => [:needs_attention]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Fi < ActiveRecord::Base
|
26
|
+
def aasm_read_state
|
27
|
+
"fi"
|
28
|
+
end
|
29
|
+
include AASM
|
30
|
+
end
|
31
|
+
|
32
|
+
class Fo < ActiveRecord::Base
|
33
|
+
def aasm_write_state(state)
|
34
|
+
"fo"
|
35
|
+
end
|
36
|
+
include AASM
|
37
|
+
end
|
38
|
+
|
39
|
+
class Fum < ActiveRecord::Base
|
40
|
+
def aasm_write_state_without_persistence(state)
|
41
|
+
"fum"
|
42
|
+
end
|
43
|
+
include AASM
|
44
|
+
end
|
45
|
+
|
46
|
+
class June < ActiveRecord::Base
|
47
|
+
include AASM
|
48
|
+
aasm_column :status
|
49
|
+
end
|
50
|
+
|
51
|
+
class Beaver < June
|
52
|
+
end
|
53
|
+
|
54
|
+
class Thief < ActiveRecord::Base
|
55
|
+
include AASM
|
56
|
+
aasm_initial_state Proc.new { |thief| thief.skilled ? :rich : :jailed }
|
57
|
+
aasm_state :rich
|
58
|
+
aasm_state :jailed
|
59
|
+
attr_accessor :skilled, :aasm_state
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "aasm model", :shared => true do
|
63
|
+
it "should include AASM::Persistence::ActiveRecordPersistence" do
|
64
|
+
@klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence)
|
65
|
+
end
|
66
|
+
it "should include AASM::Persistence::ActiveRecordPersistence::InstanceMethods" do
|
67
|
+
@klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence::InstanceMethods)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe FooBar, "class methods" do
|
72
|
+
before(:each) do
|
73
|
+
@klass = FooBar
|
74
|
+
end
|
75
|
+
it_should_behave_like "aasm model"
|
76
|
+
it "should include AASM::Persistence::ActiveRecordPersistence::ReadState" do
|
77
|
+
@klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence::ReadState)
|
78
|
+
end
|
79
|
+
it "should include AASM::Persistence::ActiveRecordPersistence::WriteState" do
|
80
|
+
@klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence::WriteState)
|
81
|
+
end
|
82
|
+
it "should include AASM::Persistence::ActiveRecordPersistence::WriteStateWithoutPersistence" do
|
83
|
+
@klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence::WriteStateWithoutPersistence)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe Fi, "class methods" do
|
88
|
+
before(:each) do
|
89
|
+
@klass = Fi
|
90
|
+
end
|
91
|
+
it_should_behave_like "aasm model"
|
92
|
+
it "should not include AASM::Persistence::ActiveRecordPersistence::ReadState" do
|
93
|
+
@klass.included_modules.should_not be_include(AASM::Persistence::ActiveRecordPersistence::ReadState)
|
94
|
+
end
|
95
|
+
it "should include AASM::Persistence::ActiveRecordPersistence::WriteState" do
|
96
|
+
@klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence::WriteState)
|
97
|
+
end
|
98
|
+
it "should include AASM::Persistence::ActiveRecordPersistence::WriteStateWithoutPersistence" do
|
99
|
+
@klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence::WriteStateWithoutPersistence)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe Fo, "class methods" do
|
104
|
+
before(:each) do
|
105
|
+
@klass = Fo
|
106
|
+
end
|
107
|
+
it_should_behave_like "aasm model"
|
108
|
+
it "should include AASM::Persistence::ActiveRecordPersistence::ReadState" do
|
109
|
+
@klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence::ReadState)
|
110
|
+
end
|
111
|
+
it "should not include AASM::Persistence::ActiveRecordPersistence::WriteState" do
|
112
|
+
@klass.included_modules.should_not be_include(AASM::Persistence::ActiveRecordPersistence::WriteState)
|
113
|
+
end
|
114
|
+
it "should include AASM::Persistence::ActiveRecordPersistence::WriteStateWithoutPersistence" do
|
115
|
+
@klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence::WriteStateWithoutPersistence)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe Fum, "class methods" do
|
120
|
+
before(:each) do
|
121
|
+
@klass = Fum
|
122
|
+
end
|
123
|
+
it_should_behave_like "aasm model"
|
124
|
+
it "should include AASM::Persistence::ActiveRecordPersistence::ReadState" do
|
125
|
+
@klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence::ReadState)
|
126
|
+
end
|
127
|
+
it "should include AASM::Persistence::ActiveRecordPersistence::WriteState" do
|
128
|
+
@klass.included_modules.should be_include(AASM::Persistence::ActiveRecordPersistence::WriteState)
|
129
|
+
end
|
130
|
+
it "should not include AASM::Persistence::ActiveRecordPersistence::WriteStateWithoutPersistence" do
|
131
|
+
@klass.included_modules.should_not be_include(AASM::Persistence::ActiveRecordPersistence::WriteStateWithoutPersistence)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe FooBar, "instance methods" do
|
136
|
+
before(:each) do
|
137
|
+
connection = mock(Connection, :columns => [])
|
138
|
+
FooBar.stub!(:connection).and_return(connection)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should respond to aasm read state when not previously defined" do
|
142
|
+
FooBar.new.should respond_to(:aasm_read_state)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should respond to aasm write state when not previously defined" do
|
146
|
+
FooBar.new.should respond_to(:aasm_write_state)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should respond to aasm write state without persistence when not previously defined" do
|
150
|
+
FooBar.new.should respond_to(:aasm_write_state_without_persistence)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should return the initial state when new and the aasm field is nil" do
|
154
|
+
FooBar.new.aasm_current_state.should == :open
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should return the aasm column when new and the aasm field is not nil" do
|
158
|
+
foo = FooBar.new
|
159
|
+
foo.aasm_state = "closed"
|
160
|
+
foo.aasm_current_state.should == :closed
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should return the aasm column when not new and the aasm_column is not nil" do
|
164
|
+
foo = FooBar.new
|
165
|
+
foo.stub!(:new_record?).and_return(false)
|
166
|
+
foo.aasm_state = "state"
|
167
|
+
foo.aasm_current_state.should == :state
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should allow a nil state" do
|
171
|
+
foo = FooBar.new
|
172
|
+
foo.stub!(:new_record?).and_return(false)
|
173
|
+
foo.aasm_state = nil
|
174
|
+
foo.aasm_current_state.should be_nil
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should have aasm_ensure_initial_state" do
|
178
|
+
foo = FooBar.new
|
179
|
+
foo.send :aasm_ensure_initial_state
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should call aasm_ensure_initial_state on validation before create" do
|
183
|
+
foo = FooBar.new
|
184
|
+
foo.should_receive(:aasm_ensure_initial_state).and_return(true)
|
185
|
+
foo.valid?
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should call aasm_ensure_initial_state on validation before create" do
|
189
|
+
foo = FooBar.new
|
190
|
+
foo.stub!(:new_record?).and_return(false)
|
191
|
+
foo.should_not_receive(:aasm_ensure_initial_state)
|
192
|
+
foo.valid?
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
describe 'Beavers' do
|
198
|
+
it "should have the same states as it's parent" do
|
199
|
+
Beaver.aasm_states.should == June.aasm_states
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should have the same events as it's parent" do
|
203
|
+
Beaver.aasm_events.should == June.aasm_events
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should have the same column as it's parent" do
|
207
|
+
Beaver.aasm_column.should == :status
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe AASM::Persistence::ActiveRecordPersistence::NamedScopeMethods do
|
212
|
+
class NamedScopeExample < ActiveRecord::Base
|
213
|
+
include AASM
|
214
|
+
end
|
215
|
+
|
216
|
+
context "Does not already respond_to? the scope name" do
|
217
|
+
it "should add a named_scope" do
|
218
|
+
NamedScopeExample.should_receive(:named_scope)
|
219
|
+
NamedScopeExample.aasm_state :unknown_scope
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context "Already respond_to? the scope name" do
|
224
|
+
it "should not add a named_scope" do
|
225
|
+
NamedScopeExample.should_not_receive(:named_scope)
|
226
|
+
NamedScopeExample.aasm_state :new
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe 'Thieves' do
|
232
|
+
before(:each) do
|
233
|
+
connection = mock(Connection, :columns => [])
|
234
|
+
Thief.stub!(:connection).and_return(connection)
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'should be rich if they\'re skilled' do
|
238
|
+
Thief.new(:skilled => true).aasm_current_state.should == :rich
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'should be jailed if they\'re unskilled' do
|
242
|
+
Thief.new(:skilled => false).aasm_current_state.should == :jailed
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# TODO: figure out how to test ActiveRecord reload! without a database
|
247
|
+
|
248
|
+
rescue LoadError => e
|
249
|
+
if e.message == "no such file to load -- active_record"
|
250
|
+
puts "You must install active record to run this spec. Install with sudo gem install activerecord"
|
251
|
+
else
|
252
|
+
raise
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'spec_helper')
|
2
|
+
|
3
|
+
class Foo2
|
4
|
+
include AASM
|
5
|
+
aasm_initial_state :open
|
6
|
+
aasm_state :open,
|
7
|
+
:before_enter => :before_enter_open,
|
8
|
+
:before_exit => :before_exit_open,
|
9
|
+
:after_enter => :after_enter_open,
|
10
|
+
:after_exit => :after_exit_open
|
11
|
+
aasm_state :closed,
|
12
|
+
:before_enter => :before_enter_closed,
|
13
|
+
:before_exit => :before_exit_closed,
|
14
|
+
:after_enter => :after_enter_closed,
|
15
|
+
:after_exit => :after_exit_closed
|
16
|
+
|
17
|
+
aasm_event :close, :before => :before, :after => :after do
|
18
|
+
transitions :to => :closed, :from => [:open]
|
19
|
+
end
|
20
|
+
|
21
|
+
aasm_event :open, :before => :before, :after => :after do
|
22
|
+
transitions :to => :open, :from => :closed
|
23
|
+
end
|
24
|
+
|
25
|
+
def before_enter_open
|
26
|
+
end
|
27
|
+
def before_exit_open
|
28
|
+
end
|
29
|
+
def after_enter_open
|
30
|
+
end
|
31
|
+
def after_exit_open
|
32
|
+
end
|
33
|
+
|
34
|
+
def before_enter_closed
|
35
|
+
end
|
36
|
+
def before_exit_closed
|
37
|
+
end
|
38
|
+
def after_enter_closed
|
39
|
+
end
|
40
|
+
def after_exit_closed
|
41
|
+
end
|
42
|
+
|
43
|
+
def before
|
44
|
+
end
|
45
|
+
def after
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe Foo2, '- new callbacks' do
|
50
|
+
before(:each) do
|
51
|
+
@foo = Foo2.new
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should get close callbacks" do
|
55
|
+
@foo.should_receive(:before).once.ordered
|
56
|
+
@foo.should_receive(:before_exit_open).once.ordered # these should be before the state changes
|
57
|
+
@foo.should_receive(:before_enter_closed).once.ordered
|
58
|
+
@foo.should_receive(:aasm_write_state).once.ordered.and_return(true) # this is when the state changes
|
59
|
+
@foo.should_receive(:after_exit_open).once.ordered # these should be after the state changes
|
60
|
+
@foo.should_receive(:after_enter_closed).once.ordered
|
61
|
+
@foo.should_receive(:after).once.ordered
|
62
|
+
|
63
|
+
@foo.close!
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should get open callbacks" do
|
67
|
+
@foo.close!
|
68
|
+
|
69
|
+
@foo.should_receive(:before).once.ordered
|
70
|
+
@foo.should_receive(:before_exit_closed).once.ordered # these should be before the state changes
|
71
|
+
@foo.should_receive(:before_enter_open).once.ordered
|
72
|
+
@foo.should_receive(:aasm_write_state).once.ordered.and_return(true) # this is when the state changes
|
73
|
+
@foo.should_receive(:after_exit_closed).once.ordered # these should be after the state changes
|
74
|
+
@foo.should_receive(:after_enter_open).once.ordered
|
75
|
+
@foo.should_receive(:after).once.ordered
|
76
|
+
|
77
|
+
@foo.open!
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'spec_helper')
|
2
|
+
|
3
|
+
describe AASM::SupportingClasses::Event do
|
4
|
+
before(:each) do
|
5
|
+
@name = :close_order
|
6
|
+
@success = :success_callback
|
7
|
+
end
|
8
|
+
|
9
|
+
def new_event
|
10
|
+
@event = AASM::SupportingClasses::Event.new(@name, {:success => @success}) do
|
11
|
+
transitions :to => :closed, :from => [:open, :received]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should set the name' do
|
16
|
+
new_event
|
17
|
+
@event.name.should == @name
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should set the success option' do
|
21
|
+
new_event
|
22
|
+
@event.success.should == @success
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should create StateTransitions' do
|
26
|
+
AASM::SupportingClasses::StateTransition.should_receive(:new).with({:to => :closed, :from => :open})
|
27
|
+
AASM::SupportingClasses::StateTransition.should_receive(:new).with({:to => :closed, :from => :received})
|
28
|
+
new_event
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe AASM::SupportingClasses::Event, 'when firing an event' do
|
33
|
+
it 'should raise an AASM::InvalidTransition error if the transitions are empty' do
|
34
|
+
obj = mock('object')
|
35
|
+
obj.stub!(:aasm_current_state)
|
36
|
+
|
37
|
+
event = AASM::SupportingClasses::Event.new(:event)
|
38
|
+
lambda { event.fire(obj) }.should raise_error(AASM::InvalidTransition)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should return the state of the first matching transition it finds' do
|
42
|
+
event = AASM::SupportingClasses::Event.new(:event) do
|
43
|
+
transitions :to => :closed, :from => [:open, :received]
|
44
|
+
end
|
45
|
+
|
46
|
+
obj = mock('object')
|
47
|
+
obj.stub!(:aasm_current_state).and_return(:open)
|
48
|
+
|
49
|
+
event.fire(obj).should == :closed
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe AASM::SupportingClasses::Event, 'when executing the success callback' do
|
54
|
+
class ThisNameBetterNotBeInUse
|
55
|
+
include AASM
|
56
|
+
|
57
|
+
aasm_state :initial
|
58
|
+
aasm_state :symbol
|
59
|
+
aasm_state :string
|
60
|
+
aasm_state :array
|
61
|
+
aasm_state :proc
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should send the success callback if it's a symbol" do
|
65
|
+
ThisNameBetterNotBeInUse.instance_eval {
|
66
|
+
aasm_event :with_symbol, :success => :symbol_success_callback do
|
67
|
+
transitions :to => :symbol, :from => [:initial]
|
68
|
+
end
|
69
|
+
}
|
70
|
+
|
71
|
+
model = ThisNameBetterNotBeInUse.new
|
72
|
+
model.should_receive(:symbol_success_callback)
|
73
|
+
model.with_symbol!
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should send the success callback if it's a string" do
|
77
|
+
ThisNameBetterNotBeInUse.instance_eval {
|
78
|
+
aasm_event :with_string, :success => 'string_success_callback' do
|
79
|
+
transitions :to => :string, :from => [:initial]
|
80
|
+
end
|
81
|
+
}
|
82
|
+
|
83
|
+
model = ThisNameBetterNotBeInUse.new
|
84
|
+
model.should_receive(:string_success_callback)
|
85
|
+
model.with_string!
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should call each success callback if passed an array of strings and/or symbols" do
|
89
|
+
ThisNameBetterNotBeInUse.instance_eval {
|
90
|
+
aasm_event :with_array, :success => [:success_callback1, 'success_callback2'] do
|
91
|
+
transitions :to => :array, :from => [:initial]
|
92
|
+
end
|
93
|
+
}
|
94
|
+
|
95
|
+
model = ThisNameBetterNotBeInUse.new
|
96
|
+
model.should_receive(:success_callback1)
|
97
|
+
model.should_receive(:success_callback2)
|
98
|
+
model.with_array!
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should call each success callback if passed an array of strings and/or symbols and/or procs" do
|
102
|
+
ThisNameBetterNotBeInUse.instance_eval {
|
103
|
+
aasm_event :with_array_including_procs, :success => [:success_callback1, 'success_callback2', lambda { |obj| obj.proc_success_callback }] do
|
104
|
+
transitions :to => :array, :from => [:initial]
|
105
|
+
end
|
106
|
+
}
|
107
|
+
|
108
|
+
model = ThisNameBetterNotBeInUse.new
|
109
|
+
model.should_receive(:success_callback1)
|
110
|
+
model.should_receive(:success_callback2)
|
111
|
+
model.should_receive(:proc_success_callback)
|
112
|
+
model.with_array_including_procs!
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should call the success callback if it's a proc" do
|
116
|
+
ThisNameBetterNotBeInUse.instance_eval {
|
117
|
+
aasm_event :with_proc, :success => lambda { |obj| obj.proc_success_callback } do
|
118
|
+
transitions :to => :proc, :from => [:initial]
|
119
|
+
end
|
120
|
+
}
|
121
|
+
|
122
|
+
model = ThisNameBetterNotBeInUse.new
|
123
|
+
model.should_receive(:proc_success_callback)
|
124
|
+
model.with_proc!
|
125
|
+
end
|
126
|
+
end
|