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.
@@ -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