aasm 2.1.1 → 2.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,7 +15,7 @@ module AASM
15
15
  attr_reader :name
16
16
 
17
17
  def initialize(name)
18
- @name = name
18
+ @name = name
19
19
  @initial_state = nil
20
20
  @states = []
21
21
  @events = {}
@@ -25,6 +25,7 @@ module AASM
25
25
  def clone
26
26
  klone = super
27
27
  klone.states = states.clone
28
+ klone.events = events.clone
28
29
  klone
29
30
  end
30
31
 
@@ -2,6 +2,7 @@ module AASM
2
2
  module SupportingClasses
3
3
  class StateTransition
4
4
  attr_reader :from, :to, :opts
5
+ alias_method :options, :opts
5
6
 
6
7
  def initialize(opts)
7
8
  @from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
@@ -10,27 +11,40 @@ module AASM
10
11
 
11
12
  def perform(obj)
12
13
  case @guard
13
- when Symbol, String
14
- obj.send(@guard)
15
- when Proc
16
- @guard.call(obj)
17
- else
18
- true
14
+ when Symbol, String
15
+ obj.send(@guard)
16
+ when Proc
17
+ @guard.call(obj)
18
+ else
19
+ true
19
20
  end
20
21
  end
21
22
 
22
23
  def execute(obj, *args)
23
- case @on_transition
24
- when Symbol, String
25
- obj.send(@on_transition, *args)
26
- when Proc
27
- @on_transition.call(obj, *args)
28
- end
24
+ @on_transition.is_a?(Array) ?
25
+ @on_transition.each {|ot| _execute(obj, ot, *args)} :
26
+ _execute(obj, @on_transition, *args)
29
27
  end
30
28
 
31
29
  def ==(obj)
32
30
  @from == obj.from && @to == obj.to
33
31
  end
32
+
33
+ def from?(value)
34
+ @from == value
35
+ end
36
+
37
+ private
38
+
39
+ def _execute(obj, on_transition, *args)
40
+ case on_transition
41
+ when Symbol, String
42
+ obj.send(on_transition, *args)
43
+ when Proc
44
+ on_transition.call(obj, *args)
45
+ end
46
+ end
47
+
34
48
  end
35
49
  end
36
50
  end
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'aasm')
2
+
3
+ class Conversation
4
+ include AASM
5
+
6
+ aasm_initial_state :needs_attention
7
+
8
+ aasm_state :needs_attention
9
+ aasm_state :read
10
+ aasm_state :closed
11
+ aasm_state :awaiting_response
12
+ aasm_state :junk
13
+
14
+ aasm_event :new_message do
15
+ end
16
+
17
+ aasm_event :view do
18
+ transitions :to => :read, :from => [:needs_attention]
19
+ end
20
+
21
+ aasm_event :reply do
22
+ end
23
+
24
+ aasm_event :close do
25
+ transitions :to => :closed, :from => [:read, :awaiting_response]
26
+ end
27
+
28
+ aasm_event :junk do
29
+ transitions :to => :junk, :from => [:read]
30
+ end
31
+
32
+ aasm_event :unjunk do
33
+ end
34
+
35
+ def initialize(persister)
36
+ @persister = persister
37
+ end
38
+
39
+
40
+ private
41
+ def aasm_read_state
42
+ @persister.read_state
43
+ end
44
+
45
+ def aasm_write_state(state)
46
+ @persister.write_state(state)
47
+ end
48
+
49
+ end
@@ -0,0 +1,8 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+ require File.join(File.dirname(__FILE__), 'conversation')
3
+
4
+ describe Conversation, 'description' do
5
+ it '.aasm_states should contain all of the states' do
6
+ Conversation.aasm_states.should == [:needs_attention, :read, :closed, :awaiting_response, :junk]
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'aasm'
5
+
6
+ require 'spec'
7
+ require 'spec/autorun'
8
+
9
+ Spec::Runner.configure do |config|
10
+
11
+ end
@@ -0,0 +1,432 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ class Foo
4
+ include AASM
5
+ aasm_initial_state :open
6
+ aasm_state :open, :exit => :exit
7
+ aasm_state :closed, :enter => :enter
8
+
9
+ aasm_event :close, :success => :success_callback do
10
+ transitions :to => :closed, :from => [:open]
11
+ end
12
+
13
+ aasm_event :null do
14
+ transitions :to => :closed, :from => [:open], :guard => :always_false
15
+ end
16
+
17
+ def always_false
18
+ false
19
+ end
20
+
21
+ def success_callback
22
+ end
23
+
24
+ def enter
25
+ end
26
+ def exit
27
+ end
28
+ end
29
+
30
+ class FooTwo < Foo
31
+ include AASM
32
+ aasm_state :foo
33
+ end
34
+
35
+ class Bar
36
+ include AASM
37
+
38
+ aasm_state :read
39
+ aasm_state :ended
40
+
41
+ aasm_event :foo do
42
+ transitions :to => :ended, :from => [:read]
43
+ end
44
+ end
45
+
46
+ class Baz < Bar
47
+ end
48
+
49
+ class Banker
50
+ include AASM
51
+ aasm_initial_state Proc.new { |banker| banker.rich? ? :retired : :selling_bad_mortgages }
52
+ aasm_state :retired
53
+ aasm_state :selling_bad_mortgages
54
+ RICH = 1_000_000
55
+ attr_accessor :balance
56
+ def initialize(balance = 0); self.balance = balance; end
57
+ def rich?; self.balance >= RICH; end
58
+ end
59
+
60
+
61
+ describe AASM, '- class level definitions' do
62
+ it 'should define a class level aasm_initial_state() method on its including class' do
63
+ Foo.should respond_to(:aasm_initial_state)
64
+ end
65
+
66
+ it 'should define a class level aasm_state() method on its including class' do
67
+ Foo.should respond_to(:aasm_state)
68
+ end
69
+
70
+ it 'should define a class level aasm_event() method on its including class' do
71
+ Foo.should respond_to(:aasm_event)
72
+ end
73
+
74
+ it 'should define a class level aasm_states() method on its including class' do
75
+ Foo.should respond_to(:aasm_states)
76
+ end
77
+
78
+ it 'should define a class level aasm_states_for_select() method on its including class' do
79
+ Foo.should respond_to(:aasm_states_for_select)
80
+ end
81
+
82
+ it 'should define a class level aasm_events() method on its including class' do
83
+ Foo.should respond_to(:aasm_events)
84
+ end
85
+
86
+ end
87
+
88
+
89
+ describe AASM, '- subclassing' do
90
+ it 'should have the parent states' do
91
+ Foo.aasm_states.each do |state|
92
+ FooTwo.aasm_states.should include(state)
93
+ end
94
+ end
95
+
96
+ it 'should not add the child states to the parent machine' do
97
+ Foo.aasm_states.should_not include(:foo)
98
+ end
99
+ end
100
+
101
+
102
+ describe AASM, '- aasm_states_for_select' do
103
+ it "should return a select friendly array of states in the form of [['Friendly name', 'state_name']]" do
104
+ Foo.aasm_states_for_select.should == [['Open', 'open'], ['Closed', 'closed']]
105
+ end
106
+ end
107
+
108
+ describe AASM, '- instance level definitions' do
109
+ before(:each) do
110
+ @foo = Foo.new
111
+ end
112
+
113
+ it 'should define a state querying instance method on including class' do
114
+ @foo.should respond_to(:open?)
115
+ end
116
+
117
+ it 'should define an event! inance method' do
118
+ @foo.should respond_to(:close!)
119
+ end
120
+ end
121
+
122
+ describe AASM, '- initial states' do
123
+ before(:each) do
124
+ @foo = Foo.new
125
+ @bar = Bar.new
126
+ end
127
+
128
+ it 'should set the initial state' do
129
+ @foo.aasm_current_state.should == :open
130
+ end
131
+
132
+ it '#open? should be initially true' do
133
+ @foo.open?.should be_true
134
+ end
135
+
136
+ it '#closed? should be initially false' do
137
+ @foo.closed?.should be_false
138
+ end
139
+
140
+ it 'should use the first state defined if no initial state is given' do
141
+ @bar.aasm_current_state.should == :read
142
+ end
143
+
144
+ it 'should determine initial state from the Proc results' do
145
+ Banker.new(Banker::RICH - 1).aasm_current_state.should == :selling_bad_mortgages
146
+ Banker.new(Banker::RICH + 1).aasm_current_state.should == :retired
147
+ end
148
+ end
149
+
150
+ describe AASM, '- event firing with persistence' do
151
+ it 'should fire the Event' do
152
+ foo = Foo.new
153
+
154
+ Foo.aasm_events[:close].should_receive(:fire).with(foo)
155
+ foo.close!
156
+ end
157
+
158
+ it 'should update the current state' do
159
+ foo = Foo.new
160
+ foo.close!
161
+
162
+ foo.aasm_current_state.should == :closed
163
+ end
164
+
165
+ it 'should call the success callback if one was provided' do
166
+ foo = Foo.new
167
+
168
+ foo.should_receive(:success_callback)
169
+
170
+ foo.close!
171
+ end
172
+
173
+ it 'should attempt to persist if aasm_write_state is defined' do
174
+ foo = Foo.new
175
+
176
+ def foo.aasm_write_state
177
+ end
178
+
179
+ foo.should_receive(:aasm_write_state)
180
+
181
+ foo.close!
182
+ end
183
+
184
+ it 'should return true if aasm_write_state is defined and returns true' do
185
+ foo = Foo.new
186
+
187
+ def foo.aasm_write_state(state)
188
+ true
189
+ end
190
+
191
+ foo.close!.should be_true
192
+ end
193
+
194
+ it 'should return false if aasm_write_state is defined and returns false' do
195
+ foo = Foo.new
196
+
197
+ def foo.aasm_write_state(state)
198
+ false
199
+ end
200
+
201
+ foo.close!.should be_false
202
+ end
203
+
204
+ it "should not update the aasm_current_state if the write fails" do
205
+ foo = Foo.new
206
+
207
+ def foo.aasm_write_state
208
+ false
209
+ end
210
+
211
+ foo.should_receive(:aasm_write_state)
212
+
213
+ foo.close!
214
+ foo.aasm_current_state.should == :open
215
+ end
216
+ end
217
+
218
+ describe AASM, '- event firing without persistence' do
219
+ it 'should fire the Event' do
220
+ foo = Foo.new
221
+
222
+ Foo.aasm_events[:close].should_receive(:fire).with(foo)
223
+ foo.close
224
+ end
225
+
226
+ it 'should update the current state' do
227
+ foo = Foo.new
228
+ foo.close
229
+
230
+ foo.aasm_current_state.should == :closed
231
+ end
232
+
233
+ it 'should attempt to persist if aasm_write_state is defined' do
234
+ foo = Foo.new
235
+
236
+ def foo.aasm_write_state
237
+ end
238
+
239
+ foo.should_receive(:aasm_write_state_without_persistence)
240
+
241
+ foo.close
242
+ end
243
+ end
244
+
245
+ describe AASM, '- persistence' do
246
+ it 'should read the state if it has not been set and aasm_read_state is defined' do
247
+ foo = Foo.new
248
+ def foo.aasm_read_state
249
+ end
250
+
251
+ foo.should_receive(:aasm_read_state)
252
+
253
+ foo.aasm_current_state
254
+ end
255
+ end
256
+
257
+ describe AASM, '- getting events for a state' do
258
+ it '#aasm_events_for_current_state should use current state' do
259
+ foo = Foo.new
260
+ foo.should_receive(:aasm_current_state)
261
+ foo.aasm_events_for_current_state
262
+ end
263
+
264
+ it '#aasm_events_for_current_state should use aasm_events_for_state' do
265
+ foo = Foo.new
266
+ foo.stub!(:aasm_current_state).and_return(:foo)
267
+ foo.should_receive(:aasm_events_for_state).with(:foo)
268
+ foo.aasm_events_for_current_state
269
+ end
270
+ end
271
+
272
+ describe AASM, '- event callbacks' do
273
+ describe "with aasm_event_fired defined" do
274
+ before do
275
+ @foo = Foo.new
276
+ def @foo.aasm_event_fired(event, from, to)
277
+ end
278
+ end
279
+
280
+ it 'should call it for successful bang fire' do
281
+ @foo.should_receive(:aasm_event_fired).with(:close, :open, :closed)
282
+ @foo.close!
283
+ end
284
+
285
+ it 'should call it for successful non-bang fire' do
286
+ @foo.should_receive(:aasm_event_fired)
287
+ @foo.close
288
+ end
289
+
290
+ it 'should not call it for failing bang fire' do
291
+ @foo.stub!(:set_aasm_current_state_with_persistence).and_return(false)
292
+ @foo.should_not_receive(:aasm_event_fired)
293
+ @foo.close!
294
+ end
295
+ end
296
+
297
+ describe "with aasm_event_failed defined" do
298
+ before do
299
+ @foo = Foo.new
300
+ def @foo.aasm_event_failed(event, from)
301
+ end
302
+ end
303
+
304
+ it 'should call it when transition failed for bang fire' do
305
+ @foo.should_receive(:aasm_event_failed).with(:null, :open)
306
+ @foo.null!
307
+ end
308
+
309
+ it 'should call it when transition failed for non-bang fire' do
310
+ @foo.should_receive(:aasm_event_failed).with(:null, :open)
311
+ @foo.null
312
+ end
313
+
314
+ it 'should not call it if persist fails for bang fire' do
315
+ @foo.stub!(:set_aasm_current_state_with_persistence).and_return(false)
316
+ @foo.should_receive(:aasm_event_failed)
317
+ @foo.close!
318
+ end
319
+ end
320
+ end
321
+
322
+ describe AASM, '- state actions' do
323
+ it "should call enter when entering state" do
324
+ foo = Foo.new
325
+ foo.should_receive(:enter)
326
+
327
+ foo.close
328
+ end
329
+
330
+ it "should call exit when exiting state" do
331
+ foo = Foo.new
332
+ foo.should_receive(:exit)
333
+
334
+ foo.close
335
+ end
336
+ end
337
+
338
+
339
+ describe Baz do
340
+ it "should have the same states as it's parent" do
341
+ Baz.aasm_states.should == Bar.aasm_states
342
+ end
343
+
344
+ it "should have the same events as it's parent" do
345
+ Baz.aasm_events.should == Bar.aasm_events
346
+ end
347
+ end
348
+
349
+
350
+ class ChetanPatil
351
+ include AASM
352
+ aasm_initial_state :sleeping
353
+ aasm_state :sleeping
354
+ aasm_state :showering
355
+ aasm_state :working
356
+ aasm_state :dating
357
+ aasm_state :prettying_up
358
+
359
+ aasm_event :wakeup do
360
+ transitions :from => :sleeping, :to => [:showering, :working]
361
+ end
362
+
363
+ aasm_event :dress do
364
+ transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
365
+ transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
366
+ transitions :from => :showering, :to => :prettying_up, :on_transition => [:condition_hair, :fix_hair]
367
+ end
368
+
369
+ def wear_clothes(shirt_color, trouser_type)
370
+ end
371
+
372
+ def condition_hair
373
+ end
374
+
375
+ def fix_hair
376
+ end
377
+ end
378
+
379
+
380
+ describe ChetanPatil do
381
+ it 'should transition to specified next state (sleeping to showering)' do
382
+ cp = ChetanPatil.new
383
+ cp.wakeup! :showering
384
+
385
+ cp.aasm_current_state.should == :showering
386
+ end
387
+
388
+ it 'should transition to specified next state (sleeping to working)' do
389
+ cp = ChetanPatil.new
390
+ cp.wakeup! :working
391
+
392
+ cp.aasm_current_state.should == :working
393
+ end
394
+
395
+ it 'should transition to default (first or showering) state' do
396
+ cp = ChetanPatil.new
397
+ cp.wakeup!
398
+
399
+ cp.aasm_current_state.should == :showering
400
+ end
401
+
402
+ it 'should transition to default state when on_transition invoked' do
403
+ cp = ChetanPatil.new
404
+ cp.dress!(nil, 'purple', 'dressy')
405
+
406
+ cp.aasm_current_state.should == :working
407
+ end
408
+
409
+ it 'should call on_transition method with args' do
410
+ cp = ChetanPatil.new
411
+ cp.wakeup! :showering
412
+
413
+ cp.should_receive(:wear_clothes).with('blue', 'jeans')
414
+ cp.dress! :working, 'blue', 'jeans'
415
+ end
416
+
417
+ it 'should call on_transition proc' do
418
+ cp = ChetanPatil.new
419
+ cp.wakeup! :showering
420
+
421
+ cp.should_receive(:wear_clothes).with('purple', 'slacks')
422
+ cp.dress!(:dating, 'purple', 'slacks')
423
+ end
424
+
425
+ it 'should call on_transition with an array of methods' do
426
+ cp = ChetanPatil.new
427
+ cp.wakeup! :showering
428
+ cp.should_receive(:condition_hair)
429
+ cp.should_receive(:fix_hair)
430
+ cp.dress!(:prettying_up)
431
+ end
432
+ end