mattscilipoti-state_machine 0.8.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/CHANGELOG.rdoc +298 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +474 -0
  4. data/Rakefile +98 -0
  5. data/examples/AutoShop_state.png +0 -0
  6. data/examples/Car_state.png +0 -0
  7. data/examples/TrafficLight_state.png +0 -0
  8. data/examples/Vehicle_state.png +0 -0
  9. data/examples/auto_shop.rb +11 -0
  10. data/examples/car.rb +19 -0
  11. data/examples/merb-rest/controller.rb +51 -0
  12. data/examples/merb-rest/model.rb +28 -0
  13. data/examples/merb-rest/view_edit.html.erb +24 -0
  14. data/examples/merb-rest/view_index.html.erb +23 -0
  15. data/examples/merb-rest/view_new.html.erb +13 -0
  16. data/examples/merb-rest/view_show.html.erb +17 -0
  17. data/examples/rails-rest/controller.rb +43 -0
  18. data/examples/rails-rest/migration.rb +11 -0
  19. data/examples/rails-rest/model.rb +23 -0
  20. data/examples/rails-rest/view_edit.html.erb +25 -0
  21. data/examples/rails-rest/view_index.html.erb +23 -0
  22. data/examples/rails-rest/view_new.html.erb +14 -0
  23. data/examples/rails-rest/view_show.html.erb +17 -0
  24. data/examples/traffic_light.rb +7 -0
  25. data/examples/vehicle.rb +31 -0
  26. data/init.rb +1 -0
  27. data/lib/state_machine.rb +388 -0
  28. data/lib/state_machine/assertions.rb +36 -0
  29. data/lib/state_machine/callback.rb +189 -0
  30. data/lib/state_machine/condition_proxy.rb +94 -0
  31. data/lib/state_machine/eval_helpers.rb +67 -0
  32. data/lib/state_machine/event.rb +252 -0
  33. data/lib/state_machine/event_collection.rb +122 -0
  34. data/lib/state_machine/extensions.rb +149 -0
  35. data/lib/state_machine/guard.rb +230 -0
  36. data/lib/state_machine/integrations.rb +68 -0
  37. data/lib/state_machine/integrations/active_record.rb +492 -0
  38. data/lib/state_machine/integrations/active_record/locale.rb +11 -0
  39. data/lib/state_machine/integrations/active_record/observer.rb +41 -0
  40. data/lib/state_machine/integrations/data_mapper.rb +351 -0
  41. data/lib/state_machine/integrations/data_mapper/observer.rb +139 -0
  42. data/lib/state_machine/integrations/sequel.rb +322 -0
  43. data/lib/state_machine/machine.rb +1467 -0
  44. data/lib/state_machine/machine_collection.rb +155 -0
  45. data/lib/state_machine/matcher.rb +123 -0
  46. data/lib/state_machine/matcher_helpers.rb +54 -0
  47. data/lib/state_machine/node_collection.rb +152 -0
  48. data/lib/state_machine/state.rb +249 -0
  49. data/lib/state_machine/state_collection.rb +112 -0
  50. data/lib/state_machine/tasks.rb +30 -0
  51. data/lib/state_machine/transition.rb +394 -0
  52. data/tasks/state_machine.rake +1 -0
  53. data/test/classes/switch.rb +11 -0
  54. data/test/functional/state_machine_test.rb +941 -0
  55. data/test/test_helper.rb +4 -0
  56. data/test/unit/assertions_test.rb +40 -0
  57. data/test/unit/callback_test.rb +455 -0
  58. data/test/unit/condition_proxy_test.rb +328 -0
  59. data/test/unit/eval_helpers_test.rb +120 -0
  60. data/test/unit/event_collection_test.rb +326 -0
  61. data/test/unit/event_test.rb +743 -0
  62. data/test/unit/guard_test.rb +908 -0
  63. data/test/unit/integrations/active_record_test.rb +1367 -0
  64. data/test/unit/integrations/data_mapper_test.rb +962 -0
  65. data/test/unit/integrations/sequel_test.rb +859 -0
  66. data/test/unit/integrations_test.rb +42 -0
  67. data/test/unit/invalid_event_test.rb +7 -0
  68. data/test/unit/invalid_transition_test.rb +7 -0
  69. data/test/unit/machine_collection_test.rb +938 -0
  70. data/test/unit/machine_test.rb +2004 -0
  71. data/test/unit/matcher_helpers_test.rb +37 -0
  72. data/test/unit/matcher_test.rb +155 -0
  73. data/test/unit/node_collection_test.rb +207 -0
  74. data/test/unit/state_collection_test.rb +280 -0
  75. data/test/unit/state_machine_test.rb +31 -0
  76. data/test/unit/state_test.rb +795 -0
  77. data/test/unit/transition_test.rb +1212 -0
  78. metadata +155 -0
@@ -0,0 +1,1212 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class TransitionTest < Test::Unit::TestCase
4
+ def setup
5
+ @klass = Class.new
6
+ @machine = StateMachine::Machine.new(@klass)
7
+ @machine.state :parked, :idling
8
+ @machine.event :ignite
9
+
10
+ @object = @klass.new
11
+ @object.state = 'parked'
12
+
13
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
14
+ end
15
+
16
+ def test_should_have_an_object
17
+ assert_equal @object, @transition.object
18
+ end
19
+
20
+ def test_should_have_a_machine
21
+ assert_equal @machine, @transition.machine
22
+ end
23
+
24
+ def test_should_have_an_event
25
+ assert_equal :ignite, @transition.event
26
+ end
27
+
28
+ def test_should_have_a_qualified_event
29
+ assert_equal :ignite, @transition.qualified_event
30
+ end
31
+
32
+ def test_should_have_a_from_value
33
+ assert_equal 'parked', @transition.from
34
+ end
35
+
36
+ def test_should_have_a_from_name
37
+ assert_equal :parked, @transition.from_name
38
+ end
39
+
40
+ def test_should_have_a_qualified_from_name
41
+ assert_equal :parked, @transition.qualified_from_name
42
+ end
43
+
44
+ def test_should_have_a_to_value
45
+ assert_equal 'idling', @transition.to
46
+ end
47
+
48
+ def test_should_have_a_to_name
49
+ assert_equal :idling, @transition.to_name
50
+ end
51
+
52
+ def test_should_have_a_qualified_to_name
53
+ assert_equal :idling, @transition.qualified_to_name
54
+ end
55
+
56
+ def test_should_have_an_attribute
57
+ assert_equal :state, @transition.attribute
58
+ end
59
+
60
+ def test_should_not_have_an_action
61
+ assert_nil @transition.action
62
+ end
63
+
64
+ def test_should_generate_attributes
65
+ expected = {:object => @object, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}
66
+ assert_equal expected, @transition.attributes
67
+ end
68
+
69
+ def test_should_have_empty_args
70
+ assert_equal [], @transition.args
71
+ end
72
+
73
+ def test_should_not_have_a_result
74
+ assert_nil @transition.result
75
+ end
76
+
77
+ def test_should_use_pretty_inspect
78
+ assert_equal '#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>', @transition.inspect
79
+ end
80
+ end
81
+
82
+ class TransitionWithInvalidNodesTest < Test::Unit::TestCase
83
+ def setup
84
+ @klass = Class.new
85
+ @machine = StateMachine::Machine.new(@klass)
86
+ @machine.state :parked, :idling
87
+ @machine.event :ignite
88
+
89
+ @object = @klass.new
90
+ @object.state = 'parked'
91
+ end
92
+
93
+ def test_should_raise_exception_without_event
94
+ assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, nil, :parked, :idling) }
95
+ end
96
+
97
+ def test_should_raise_exception_with_invalid_event
98
+ assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :invalid, :parked, :idling) }
99
+ end
100
+
101
+ def test_should_raise_exception_with_invalid_from_state
102
+ assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :ignite, :invalid, :idling) }
103
+ end
104
+
105
+ def test_should_raise_exception_with_invalid_to_state
106
+ assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :ignite, :parked, :invalid) }
107
+ end
108
+ end
109
+
110
+ class TransitionWithDynamicToValueTest < Test::Unit::TestCase
111
+ def setup
112
+ @klass = Class.new
113
+ @machine = StateMachine::Machine.new(@klass)
114
+ @machine.state :parked
115
+ @machine.state :idling, :value => lambda {1}
116
+ @machine.event :ignite
117
+
118
+ @object = @klass.new
119
+ @object.state = 'parked'
120
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
121
+ end
122
+
123
+ def test_should_evaluate_to_value
124
+ assert_equal 1, @transition.to
125
+ end
126
+ end
127
+
128
+ class TransitionLoopbackTest < Test::Unit::TestCase
129
+ def setup
130
+ @klass = Class.new
131
+ @machine = StateMachine::Machine.new(@klass)
132
+ @machine.state :parked
133
+ @machine.event :park
134
+
135
+ @object = @klass.new
136
+ @object.state = 'parked'
137
+ @transition = StateMachine::Transition.new(@object, @machine, :park, :parked, :parked)
138
+ end
139
+
140
+ def test_should_be_loopback
141
+ assert @transition.loopback?
142
+ end
143
+ end
144
+
145
+ class TransitionWithDifferentStatesTest < Test::Unit::TestCase
146
+ def setup
147
+ @klass = Class.new
148
+ @machine = StateMachine::Machine.new(@klass)
149
+ @machine.state :parked, :idling
150
+ @machine.event :ignite
151
+
152
+ @object = @klass.new
153
+ @object.state = 'parked'
154
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
155
+ end
156
+
157
+ def test_should_not_be_loopback
158
+ assert !@transition.loopback?
159
+ end
160
+ end
161
+
162
+ class TransitionWithNamespaceTest < Test::Unit::TestCase
163
+ def setup
164
+ @klass = Class.new
165
+ @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm')
166
+ @machine.state :off, :active
167
+ @machine.event :activate
168
+
169
+ @object = @klass.new
170
+ @object.state = 'off'
171
+
172
+ @transition = StateMachine::Transition.new(@object, @machine, :activate, :off, :active)
173
+ end
174
+
175
+ def test_should_have_an_event
176
+ assert_equal :activate, @transition.event
177
+ end
178
+
179
+ def test_should_have_a_qualified_event
180
+ assert_equal :activate_alarm, @transition.qualified_event
181
+ end
182
+
183
+ def test_should_have_a_from_name
184
+ assert_equal :off, @transition.from_name
185
+ end
186
+
187
+ def test_should_have_a_qualified_from_name
188
+ assert_equal :alarm_off, @transition.qualified_from_name
189
+ end
190
+
191
+ def test_should_have_a_to_name
192
+ assert_equal :active, @transition.to_name
193
+ end
194
+
195
+ def test_should_have_a_qualified_to_name
196
+ assert_equal :alarm_active, @transition.qualified_to_name
197
+ end
198
+ end
199
+
200
+ class TransitionWithCustomMachineAttributeTest < Test::Unit::TestCase
201
+ def setup
202
+ @klass = Class.new
203
+ @machine = StateMachine::Machine.new(@klass, :state, :attribute => :state_id)
204
+ @machine.state :off, :value => 1
205
+ @machine.state :active, :value => 2
206
+ @machine.event :activate
207
+
208
+ @object = @klass.new
209
+ @object.state_id = 1
210
+
211
+ @transition = StateMachine::Transition.new(@object, @machine, :activate, :off, :active)
212
+ end
213
+
214
+ def test_should_persist
215
+ @transition.persist
216
+ assert_equal 2, @object.state_id
217
+ end
218
+
219
+ def test_should_rollback
220
+ @object.state_id = 2
221
+ @transition.rollback
222
+
223
+ assert_equal 1, @object.state_id
224
+ end
225
+ end
226
+
227
+ class TransitionWithoutReadingStateTest < Test::Unit::TestCase
228
+ def setup
229
+ @klass = Class.new
230
+ @machine = StateMachine::Machine.new(@klass)
231
+ @machine.state :parked, :idling
232
+ @machine.event :ignite
233
+
234
+ @object = @klass.new
235
+ @object.state = 'idling'
236
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling, false)
237
+ end
238
+
239
+ def test_should_not_read_from_value_from_object
240
+ assert_equal 'parked', @transition.from
241
+ end
242
+
243
+ def test_should_have_to_value
244
+ assert_equal 'idling', @transition.to
245
+ end
246
+ end
247
+
248
+ class TransitionWithActionTest < Test::Unit::TestCase
249
+ def setup
250
+ @klass = Class.new do
251
+ def save
252
+ end
253
+ end
254
+
255
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
256
+ @machine.state :parked, :idling
257
+ @machine.event :ignite
258
+
259
+ @object = @klass.new
260
+ @object.state = 'parked'
261
+
262
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
263
+ end
264
+
265
+ def test_should_have_an_action
266
+ assert_equal :save, @transition.action
267
+ end
268
+
269
+ def test_should_not_have_a_result
270
+ assert_nil @transition.result
271
+ end
272
+ end
273
+
274
+ class TransitionAfterBeingPersistedTest < Test::Unit::TestCase
275
+ def setup
276
+ @klass = Class.new
277
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
278
+ @machine.state :parked, :idling
279
+ @machine.event :ignite
280
+
281
+ @object = @klass.new
282
+ @object.state = 'parked'
283
+
284
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
285
+ @transition.persist
286
+ end
287
+
288
+ def test_should_update_state_value
289
+ assert_equal 'idling', @object.state
290
+ end
291
+
292
+ def test_should_not_change_from_state
293
+ assert_equal 'parked', @transition.from
294
+ end
295
+
296
+ def test_should_not_change_to_state
297
+ assert_equal 'idling', @transition.to
298
+ end
299
+
300
+ def test_should_not_be_able_to_persist_twice
301
+ @object.state = 'parked'
302
+ @transition.persist
303
+ assert_equal 'parked', @object.state
304
+ end
305
+
306
+ def test_should_be_able_to_persist_again_after_resetting
307
+ @object.state = 'parked'
308
+ @transition.reset
309
+ @transition.persist
310
+ assert_equal 'idling', @object.state
311
+ end
312
+
313
+ def test_should_revert_to_from_state_on_rollback
314
+ @transition.rollback
315
+ assert_equal 'parked', @object.state
316
+ end
317
+ end
318
+
319
+ class TransitionAfterBeingRolledBackTest < Test::Unit::TestCase
320
+ def setup
321
+ @klass = Class.new
322
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
323
+ @machine.state :parked, :idling
324
+ @machine.event :ignite
325
+
326
+ @object = @klass.new
327
+ @object.state = 'parked'
328
+
329
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
330
+ @object.state = 'idling'
331
+
332
+ @transition.rollback
333
+ end
334
+
335
+ def test_should_update_state_value_to_from_state
336
+ assert_equal 'parked', @object.state
337
+ end
338
+
339
+ def test_should_not_change_from_state
340
+ assert_equal 'parked', @transition.from
341
+ end
342
+
343
+ def test_should_not_change_to_state
344
+ assert_equal 'idling', @transition.to
345
+ end
346
+
347
+ def test_should_still_be_able_to_persist
348
+ @transition.persist
349
+ assert_equal 'idling', @object.state
350
+ end
351
+ end
352
+
353
+ class TransitionWithCallbacksTest < Test::Unit::TestCase
354
+ def setup
355
+ @klass = Class.new do
356
+ attr_reader :saved, :save_state
357
+
358
+ def save
359
+ @save_state = state
360
+ @saved = true
361
+ end
362
+ end
363
+
364
+ @machine = StateMachine::Machine.new(@klass)
365
+ @machine.state :parked, :idling
366
+ @machine.event :ignite
367
+
368
+ @object = @klass.new
369
+ @object.state = 'parked'
370
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
371
+ end
372
+
373
+ def test_should_run_before_callbacks_on_before
374
+ @machine.before_transition(lambda {|object| @run = true})
375
+ result = @transition.before
376
+
377
+ assert_equal true, result
378
+ assert_equal true, @run
379
+ end
380
+
381
+ def test_should_run_before_callbacks_in_the_order_they_were_defined
382
+ @callbacks = []
383
+ @machine.before_transition(lambda {@callbacks << 1})
384
+ @machine.before_transition(lambda {@callbacks << 2})
385
+ @transition.before
386
+
387
+ assert_equal [1, 2], @callbacks
388
+ end
389
+
390
+ def test_should_only_run_before_callbacks_that_match_transition_context
391
+ @count = 0
392
+ callback = lambda {@count += 1}
393
+
394
+ @machine.before_transition :from => :parked, :to => :idling, :on => :park, :do => callback
395
+ @machine.before_transition :from => :parked, :to => :parked, :on => :park, :do => callback
396
+ @machine.before_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback
397
+ @machine.before_transition :from => :idling, :to => :idling, :on => :park, :do => callback
398
+ @transition.before
399
+
400
+ assert_equal 1, @count
401
+ end
402
+
403
+ def test_should_pass_transition_to_before_callbacks
404
+ @machine.before_transition(lambda {|*args| @args = args})
405
+ @transition.before
406
+
407
+ assert_equal [@object, @transition], @args
408
+ end
409
+
410
+ def test_should_catch_halted_before_callbacks
411
+ @machine.before_transition(lambda {throw :halt})
412
+
413
+ result = nil
414
+ assert_nothing_thrown { result = @transition.before }
415
+ assert_equal false, result
416
+ end
417
+
418
+ def test_should_run_before_callbacks_on_perform_before_changing_the_state
419
+ @machine.before_transition(lambda {|object| @state = object.state})
420
+ @transition.perform
421
+
422
+ assert_equal 'parked', @state
423
+ end
424
+
425
+ def test_should_not_be_able_to_run_before_callbacks_twice
426
+ @count = 0
427
+ @machine.before_transition(lambda {@count += 1})
428
+ @transition.before
429
+ @transition.before
430
+ assert_equal 1, @count
431
+ end
432
+
433
+ def test_should_be_able_to_run_before_callbacks_again_after_resetting
434
+ @count = 0
435
+ @machine.before_transition(lambda {@count += 1})
436
+ @transition.before
437
+ @transition.reset
438
+ @transition.before
439
+ assert_equal 2, @count
440
+ end
441
+
442
+ def test_should_run_after_callbacks_on_after
443
+ @machine.after_transition(lambda {|object| @run = true})
444
+ result = @transition.after(true)
445
+
446
+ assert_equal true, result
447
+ assert_equal true, @run
448
+ end
449
+
450
+ def test_should_set_result_on_after
451
+ @transition.after
452
+ assert_nil @transition.result
453
+
454
+ @transition.after(1)
455
+ assert_equal 1, @transition.result
456
+ end
457
+
458
+ def test_should_run_after_callbacks_in_the_order_they_were_defined
459
+ @callbacks = []
460
+ @machine.after_transition(lambda {@callbacks << 1})
461
+ @machine.after_transition(lambda {@callbacks << 2})
462
+ @transition.after(true)
463
+
464
+ assert_equal [1, 2], @callbacks
465
+ end
466
+
467
+ def test_should_only_run_after_callbacks_that_match_transition_context
468
+ @count = 0
469
+ callback = lambda {@count += 1}
470
+
471
+ @machine.after_transition :from => :parked, :to => :idling, :on => :park, :do => callback
472
+ @machine.after_transition :from => :parked, :to => :parked, :on => :park, :do => callback
473
+ @machine.after_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback
474
+ @machine.after_transition :from => :idling, :to => :idling, :on => :park, :do => callback
475
+ @transition.after(true)
476
+
477
+ assert_equal 1, @count
478
+ end
479
+
480
+ def test_should_not_run_after_callbacks_if_not_successful
481
+ @machine.after_transition(lambda {|object| @run = true})
482
+ @transition.after(nil, false)
483
+ assert !@run
484
+ end
485
+
486
+ def test_should_pass_transition_to_after_callbacks
487
+ @machine.after_transition(lambda {|*args| @args = args})
488
+
489
+ @transition.after(true)
490
+ assert_equal [@object, @transition], @args
491
+ assert_equal true, @transition.result
492
+
493
+ @transition.after(false)
494
+ assert_equal [@object, @transition], @args
495
+ assert_equal false, @transition.result
496
+ end
497
+
498
+ def test_should_catch_halted_after_callbacks
499
+ @machine.after_transition(lambda {throw :halt})
500
+
501
+ result = nil
502
+ assert_nothing_thrown { result = @transition.after(true) }
503
+ assert_equal true, result
504
+ end
505
+
506
+ def test_should_run_after_callbacks_on_perform_after_running_the_action
507
+ @machine.after_transition(lambda {|object| @state = object.state})
508
+ @transition.perform(true)
509
+
510
+ assert_equal 'idling', @state
511
+ end
512
+
513
+ def test_should_not_be_able_to_run_after_callbacks_twice
514
+ @count = 0
515
+ @machine.after_transition(lambda {@count += 1})
516
+ @transition.after
517
+ @transition.after
518
+ assert_equal 1, @count
519
+ end
520
+
521
+ def test_should_be_able_to_run_after_callbacks_again_after_resetting
522
+ @count = 0
523
+ @machine.after_transition(lambda {@count += 1})
524
+ @transition.after
525
+ @transition.reset
526
+ @transition.after
527
+ assert_equal 2, @count
528
+ end
529
+ end
530
+
531
+ class TransitionAfterBeingPerformedTest < Test::Unit::TestCase
532
+ def setup
533
+ @klass = Class.new do
534
+ attr_reader :saved, :save_state
535
+
536
+ def save
537
+ @save_state = state
538
+ @saved = true
539
+ end
540
+ end
541
+
542
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
543
+ @machine.state :parked, :idling
544
+ @machine.event :ignite
545
+
546
+ @object = @klass.new
547
+ @object.state = 'parked'
548
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
549
+ @result = @transition.perform
550
+ end
551
+
552
+ def test_should_have_empty_args
553
+ assert_equal [], @transition.args
554
+ end
555
+
556
+ def test_should_have_a_result
557
+ assert_equal true, @transition.result
558
+ end
559
+
560
+ def test_should_be_successful
561
+ assert_equal true, @result
562
+ end
563
+
564
+ def test_should_change_the_current_state
565
+ assert_equal 'idling', @object.state
566
+ end
567
+
568
+ def test_should_run_the_action
569
+ assert @object.saved
570
+ end
571
+
572
+ def test_should_run_the_action_after_saving_the_state
573
+ assert_equal 'idling', @object.save_state
574
+ end
575
+ end
576
+
577
+ class TransitionWithPerformArgumentsTest < Test::Unit::TestCase
578
+ def setup
579
+ @klass = Class.new do
580
+ attr_reader :saved
581
+
582
+ def save
583
+ @saved = true
584
+ end
585
+ end
586
+
587
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
588
+ @machine.state :parked, :idling
589
+ @machine.event :ignite
590
+
591
+ @object = @klass.new
592
+ @object.state = 'parked'
593
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
594
+ end
595
+
596
+ def test_should_have_arguments
597
+ @transition.perform(1, 2)
598
+
599
+ assert_equal [1, 2], @transition.args
600
+ assert @object.saved
601
+ end
602
+
603
+ def test_should_not_include_run_action_in_arguments
604
+ @transition.perform(1, 2, false)
605
+
606
+ assert_equal [1, 2], @transition.args
607
+ assert !@object.saved
608
+ end
609
+ end
610
+
611
+ class TransitionWithoutRunningActionTest < Test::Unit::TestCase
612
+ def setup
613
+ @klass = Class.new do
614
+ attr_reader :saved
615
+
616
+ def save
617
+ @saved = true
618
+ end
619
+ end
620
+
621
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
622
+ @machine.state :parked, :idling
623
+ @machine.event :ignite
624
+ @machine.after_transition(lambda {|object| @run_after = true})
625
+
626
+ @object = @klass.new
627
+ @object.state = 'parked'
628
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
629
+ @result = @transition.perform(false)
630
+ end
631
+
632
+ def test_should_have_empty_args
633
+ assert_equal [], @transition.args
634
+ end
635
+
636
+ def test_should_not_have_a_result
637
+ assert_nil @transition.result
638
+ end
639
+
640
+ def test_should_be_successful
641
+ assert_equal true, @result
642
+ end
643
+
644
+ def test_should_change_the_current_state
645
+ assert_equal 'idling', @object.state
646
+ end
647
+
648
+ def test_should_not_run_the_action
649
+ assert !@object.saved
650
+ end
651
+
652
+ def test_should_run_after_callbacks
653
+ assert @run_after
654
+ end
655
+ end
656
+
657
+ class TransitionWithTransactionsTest < Test::Unit::TestCase
658
+ def setup
659
+ @klass = Class.new do
660
+ class << self
661
+ attr_accessor :running_transaction
662
+ end
663
+
664
+ attr_accessor :result
665
+
666
+ def save
667
+ @result = self.class.running_transaction
668
+ true
669
+ end
670
+ end
671
+
672
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
673
+ @machine.state :parked, :idling
674
+ @machine.event :ignite
675
+
676
+ @object = @klass.new
677
+ @object.state = 'parked'
678
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
679
+
680
+ class << @machine
681
+ def within_transaction(object)
682
+ owner_class.running_transaction = object
683
+ yield
684
+ owner_class.running_transaction = false
685
+ end
686
+ end
687
+ end
688
+
689
+ def test_should_run_blocks_within_transaction_for_object
690
+ @transition.within_transaction do
691
+ @result = @klass.running_transaction
692
+ end
693
+
694
+ assert_equal @object, @result
695
+ end
696
+
697
+ def test_should_run_before_callbacks_within_transaction
698
+ @machine.before_transition(lambda {|object| @result = @klass.running_transaction})
699
+ @transition.perform
700
+
701
+ assert_equal @object, @result
702
+ end
703
+
704
+ def test_should_run_action_within_transaction
705
+ @transition.perform
706
+
707
+ assert_equal @object, @object.result
708
+ end
709
+
710
+ def test_should_run_after_callbacks_within_transaction
711
+ @machine.after_transition(lambda {|object| @result = @klass.running_transaction})
712
+ @transition.perform
713
+
714
+ assert_equal @object, @result
715
+ end
716
+ end
717
+
718
+ class TransitionHaltedDuringBeforeCallbacksTest < Test::Unit::TestCase
719
+ def setup
720
+ @klass = Class.new do
721
+ class << self; attr_accessor :cancelled_transaction; end
722
+ attr_reader :saved
723
+
724
+ def save
725
+ @saved = true
726
+ end
727
+ end
728
+ @before_count = 0
729
+ @after_count = 0
730
+
731
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
732
+ @machine.state :parked, :idling
733
+ @machine.event :ignite
734
+ class << @machine
735
+ def within_transaction(object)
736
+ owner_class.cancelled_transaction = yield == false
737
+ end
738
+ end
739
+
740
+ @machine.before_transition lambda {@before_count += 1; throw :halt}
741
+ @machine.before_transition lambda {@before_count += 1}
742
+ @machine.after_transition lambda {@after_count += 1}
743
+
744
+ @object = @klass.new
745
+ @object.state = 'parked'
746
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
747
+ @result = @transition.perform
748
+ end
749
+
750
+ def test_should_not_be_successful
751
+ assert_equal false, @result
752
+ end
753
+
754
+ def test_should_not_change_current_state
755
+ assert_equal 'parked', @object.state
756
+ end
757
+
758
+ def test_should_not_run_action
759
+ assert !@object.saved
760
+ end
761
+
762
+ def test_should_not_run_further_before_callbacks
763
+ assert_equal 1, @before_count
764
+ end
765
+
766
+ def test_should_not_run_after_callbacks
767
+ assert_equal 0, @after_count
768
+ end
769
+
770
+ def test_should_cancel_the_transaction
771
+ assert @klass.cancelled_transaction
772
+ end
773
+ end
774
+
775
+ class TransitionHaltedAfterCallbackTest < Test::Unit::TestCase
776
+ def setup
777
+ @klass = Class.new do
778
+ class << self; attr_accessor :cancelled_transaction; end
779
+ attr_reader :saved
780
+
781
+ def save
782
+ @saved = true
783
+ end
784
+ end
785
+ @before_count = 0
786
+ @after_count = 0
787
+
788
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
789
+ @machine.state :parked, :idling
790
+ @machine.event :ignite
791
+ class << @machine
792
+ def within_transaction(object)
793
+ owner_class.cancelled_transaction = yield == false
794
+ end
795
+ end
796
+
797
+ @machine.before_transition lambda {@before_count += 1}
798
+ @machine.after_transition lambda {@after_count += 1; throw :halt}
799
+ @machine.after_transition lambda {@after_count += 1}
800
+
801
+ @object = @klass.new
802
+ @object.state = 'parked'
803
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
804
+ @result = @transition.perform
805
+ end
806
+
807
+ def test_should_be_successful
808
+ assert_equal true, @result
809
+ end
810
+
811
+ def test_should_change_current_state
812
+ assert_equal 'idling', @object.state
813
+ end
814
+
815
+ def test_should_run_before_callbacks
816
+ assert_equal 1, @before_count
817
+ end
818
+
819
+ def test_should_not_run_further_after_callbacks
820
+ assert_equal 1, @after_count
821
+ end
822
+
823
+ def test_should_not_cancel_the_transaction
824
+ assert !@klass.cancelled_transaction
825
+ end
826
+ end
827
+
828
+ class TransitionWithActionFailedTest < Test::Unit::TestCase
829
+ def setup
830
+ @klass = Class.new do
831
+ class << self; attr_accessor :cancelled_transaction; end
832
+ attr_reader :saved
833
+
834
+ def save
835
+ false
836
+ end
837
+ end
838
+ @before_count = 0
839
+ @after_count = 0
840
+
841
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
842
+ @machine.state :parked, :idling
843
+ @machine.event :ignite
844
+ class << @machine
845
+ def within_transaction(object)
846
+ owner_class.cancelled_transaction = yield == false
847
+ end
848
+ end
849
+
850
+ @machine.before_transition lambda {@before_count += 1}
851
+ @machine.after_transition lambda {@after_count += 1}
852
+ @machine.after_transition lambda {@after_count += 1}, :include_failures => true
853
+
854
+ @object = @klass.new
855
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
856
+ end
857
+
858
+ def test_should_not_be_successful
859
+ assert_equal false, @transition.perform
860
+ end
861
+
862
+ def test_should_not_change_current_state
863
+ @transition.perform
864
+ assert_nil @object.state
865
+ end
866
+
867
+ def test_should_run_before_callbacks
868
+ @transition.perform
869
+ assert_equal 1, @before_count
870
+ end
871
+
872
+ def test_should_only_run_after_callbacks_that_include_failures
873
+ @transition.perform
874
+ assert_equal 1, @after_count
875
+ end
876
+
877
+ def test_should_cancel_the_transaction
878
+ @transition.perform
879
+ assert @klass.cancelled_transaction
880
+ end
881
+
882
+ def test_should_interpret_nil_as_failure
883
+ @klass.class_eval do
884
+ def save
885
+ nil
886
+ end
887
+ end
888
+
889
+ assert_equal false, @transition.perform
890
+ end
891
+ end
892
+
893
+ class TransitionWithActionErrorTest < Test::Unit::TestCase
894
+ def setup
895
+ @klass = Class.new do
896
+ def save
897
+ raise ArgumentError
898
+ end
899
+ end
900
+
901
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
902
+ @machine.state :parked, :idling
903
+ @machine.event :ignite
904
+
905
+ @object = @klass.new
906
+ @object.state = 'parked'
907
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
908
+
909
+ @raised = true
910
+ begin
911
+ @transition.perform
912
+ @raised = false
913
+ rescue ArgumentError
914
+ end
915
+ end
916
+
917
+ def test_should_not_catch_exception
918
+ assert @raised
919
+ end
920
+
921
+ def test_should_not_change_current_state
922
+ assert_equal 'parked', @object.state
923
+ end
924
+ end
925
+
926
+ class TransitionsInParallelTest < Test::Unit::TestCase
927
+ def setup
928
+ @klass = Class.new do
929
+ attr_reader :persisted
930
+
931
+ def initialize
932
+ @persisted = []
933
+ @state = 'parked'
934
+ @status = 'first_gear'
935
+ super
936
+ end
937
+
938
+ def state=(value)
939
+ @persisted << value
940
+ @state = value
941
+ end
942
+
943
+ def status=(value)
944
+ @persisted << value
945
+ @status = value
946
+ end
947
+ end
948
+
949
+ @state = StateMachine::Machine.new(@klass, :state)
950
+ @state.event :ignite
951
+ @state.state :parked, :idling
952
+
953
+ @status = StateMachine::Machine.new(@klass, :status)
954
+ @status.event :shift_up
955
+ @status.state :first_gear, :second_gear
956
+
957
+ @object = @klass.new
958
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
959
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
960
+
961
+ @result = StateMachine::Transition.perform([@state_transition, @status_transition])
962
+ end
963
+
964
+ def test_should_raise_exception_if_attempted_on_the_same_state_machine
965
+ exception = assert_raise(ArgumentError) { StateMachine::Transition.perform([@state_transition, @state_transition]) }
966
+ assert_equal 'Cannot perform multiple transitions in parallel for the same state machine attribute', exception.message
967
+ end
968
+
969
+ def test_should_perform
970
+ assert_equal true, @result
971
+ end
972
+
973
+ def test_should_persist_first_state
974
+ assert_equal 'idling', @object.state
975
+ end
976
+
977
+ def test_should_persist_second_state
978
+ assert_equal 'second_gear', @object.status
979
+ end
980
+
981
+ def test_should_persist_in_order
982
+ assert_equal ['idling', 'second_gear'], @object.persisted
983
+ end
984
+
985
+ def test_should_have_args_in_transitions
986
+ assert_equal [], @state_transition.args
987
+ assert_equal [], @status_transition.args
988
+ end
989
+ end
990
+
991
+ class TransitionsInParallelWithCallbacksTest < Test::Unit::TestCase
992
+ def setup
993
+ @klass = Class.new
994
+
995
+ @before_callbacks = []
996
+ @after_callbacks = []
997
+
998
+ @state = StateMachine::Machine.new(@klass, :state, :initial => :parked)
999
+ @state.event :ignite
1000
+ @state.state :parked, :idling
1001
+ @state.before_transition lambda {@before_callbacks << :state}
1002
+ @state.after_transition lambda {@after_callbacks << :state}
1003
+
1004
+ @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear)
1005
+ @status.event :shift_up
1006
+ @status.state :first_gear, :second_gear
1007
+ @status.before_transition lambda {@before_callbacks << :status}
1008
+ @status.after_transition lambda {@after_callbacks << :status}
1009
+
1010
+ @object = @klass.new
1011
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
1012
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
1013
+ end
1014
+
1015
+ def test_should_run_before_callbacks_in_order
1016
+ perform
1017
+ assert_equal [:state, :status], @before_callbacks
1018
+ end
1019
+
1020
+ def test_should_run_after_callbacks_in_order
1021
+ perform
1022
+ assert_equal [:state, :status], @after_callbacks
1023
+ end
1024
+
1025
+ def test_should_not_run_after_callbacks_if_disabled
1026
+ perform(:after => false)
1027
+ assert_equal [], @after_callbacks
1028
+ end
1029
+
1030
+ def test_should_halt_if_before_callback_halted_for_first_transition
1031
+ @state.before_transition lambda {throw :halt}
1032
+
1033
+ assert_equal false, perform
1034
+ assert_equal [:state], @before_callbacks
1035
+ assert_equal 'parked', @object.state
1036
+ assert_equal 'first_gear', @object.status
1037
+ assert_equal [], @after_callbacks
1038
+ end
1039
+
1040
+ def test_should_halt_if_before_callback_halted_for_second_transition
1041
+ @status.before_transition lambda {throw :halt}
1042
+
1043
+ assert_equal false, perform
1044
+ assert_equal [:state, :status], @before_callbacks
1045
+ assert_equal 'parked', @object.state
1046
+ assert_equal 'first_gear', @object.status
1047
+ assert_equal [], @after_callbacks
1048
+ end
1049
+
1050
+ def test_should_perform_if_after_callback_halted_for_first_transition
1051
+ @state.after_transition lambda {throw :halt}
1052
+ @state.after_transition lambda {@after_callbacks << :invalid}
1053
+
1054
+ assert_equal true, perform
1055
+ assert_equal [:state, :status], @before_callbacks
1056
+ assert_equal 'idling', @object.state
1057
+ assert_equal 'second_gear', @object.status
1058
+ assert_equal [:state, :status], @after_callbacks
1059
+ end
1060
+
1061
+ def test_should_perform_if_after_callback_halted_for_second_transition
1062
+ @status.after_transition lambda {throw :halt}
1063
+ @status.after_transition lambda {@after_callbacks << :invalid}
1064
+
1065
+ assert_equal true, perform
1066
+ assert_equal [:state, :status], @before_callbacks
1067
+ assert_equal 'idling', @object.state
1068
+ assert_equal 'second_gear', @object.status
1069
+ assert_equal [:state, :status], @after_callbacks
1070
+ end
1071
+
1072
+ private
1073
+ def perform(options = {})
1074
+ StateMachine::Transition.perform([@state_transition, @status_transition], options)
1075
+ end
1076
+ end
1077
+
1078
+ class TransitionsInParallelWithActionsTest < Test::Unit::TestCase
1079
+ def setup
1080
+ @klass = Class.new do
1081
+ attr_reader :actions
1082
+
1083
+ def save_state
1084
+ (@actions ||= []) << :save_state
1085
+ :save_state
1086
+ end
1087
+
1088
+ def save_status
1089
+ (@actions ||= []) << :save_status
1090
+ :save_status
1091
+ end
1092
+ end
1093
+
1094
+ @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save_state)
1095
+ @state.event :ignite
1096
+ @state.state :parked, :idling
1097
+
1098
+ @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save_status)
1099
+ @status.event :shift_up
1100
+ @status.state :first_gear, :second_gear
1101
+
1102
+ @object = @klass.new
1103
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
1104
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
1105
+ end
1106
+
1107
+ def test_should_run_actions_in_order
1108
+ perform
1109
+ assert_equal [:save_state, :save_status], @object.actions
1110
+ end
1111
+
1112
+ def test_should_store_action_specific_results
1113
+ perform
1114
+ assert_equal :save_state, @state_transition.result
1115
+ assert_equal :save_status, @status_transition.result
1116
+ end
1117
+
1118
+ def test_should_not_perform_if_action_fails_for_first_transition
1119
+ @klass.class_eval do
1120
+ def save_state
1121
+ false
1122
+ end
1123
+ end
1124
+
1125
+ assert_equal false, perform
1126
+ assert_equal 'parked', @object.state
1127
+ assert_equal 'first_gear', @object.status
1128
+ end
1129
+
1130
+ def test_should_not_perform_if_action_fails_for_second_transition
1131
+ @klass.class_eval do
1132
+ def save_status
1133
+ false
1134
+ end
1135
+ end
1136
+
1137
+ assert_equal false, perform
1138
+ assert_equal 'parked', @object.state
1139
+ assert_equal 'first_gear', @object.status
1140
+ end
1141
+
1142
+ def test_should_rollback_if_action_errors_for_first_transition
1143
+ @klass.class_eval do
1144
+ def save_state
1145
+ raise ArgumentError
1146
+ end
1147
+ end
1148
+
1149
+ begin; perform; rescue; end
1150
+ assert_equal 'parked', @object.state
1151
+ assert_equal 'first_gear', @object.status
1152
+ end
1153
+
1154
+ def test_should_rollback_if_action_errors_for_second_transition
1155
+ @klass.class_eval do
1156
+ def save_status
1157
+ raise ArgumentError
1158
+ end
1159
+ end
1160
+
1161
+ begin; perform; rescue; end
1162
+ assert_equal 'parked', @object.state
1163
+ assert_equal 'first_gear', @object.status
1164
+ end
1165
+
1166
+ private
1167
+ def perform(options = {})
1168
+ StateMachine::Transition.perform([@state_transition, @status_transition], options)
1169
+ end
1170
+ end
1171
+
1172
+ class TransitionsWithPerformBlockTest < Test::Unit::TestCase
1173
+ def setup
1174
+ @klass = Class.new
1175
+
1176
+ @state = StateMachine::Machine.new(@klass, :state, :initial => :parked)
1177
+ @state.event :ignite
1178
+ @state.state :parked, :idling
1179
+
1180
+ @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear)
1181
+ @status.event :shift_up
1182
+ @status.state :first_gear, :second_gear
1183
+
1184
+ @object = @klass.new
1185
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
1186
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
1187
+ end
1188
+
1189
+ def test_should_be_perform_if_result_is_not_false
1190
+ assert_equal true, StateMachine::Transition.perform([@state_transition, @status_transition]) { true }
1191
+ assert_equal 'idling', @object.state
1192
+ assert_equal 'second_gear', @object.status
1193
+ end
1194
+
1195
+ def test_should_not_perform_if_result_is_false
1196
+ assert_equal false, StateMachine::Transition.perform([@state_transition, @status_transition]) { false }
1197
+ assert_equal 'parked', @object.state
1198
+ assert_equal 'first_gear', @object.status
1199
+ end
1200
+
1201
+ def test_should_not_perform_if_result_is_nil
1202
+ assert_equal false, StateMachine::Transition.perform([@state_transition, @status_transition]) { nil }
1203
+ assert_equal 'parked', @object.state
1204
+ assert_equal 'first_gear', @object.status
1205
+ end
1206
+
1207
+ def test_should_use_result_as_transition_result
1208
+ StateMachine::Transition.perform([@state_transition, @status_transition]) { 1 }
1209
+ assert_equal 1, @state_transition.result
1210
+ assert_equal 1, @status_transition.result
1211
+ end
1212
+ end