pluginaweek-state_machine 0.7.6

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.
Files changed (78) hide show
  1. data/CHANGELOG.rdoc +273 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +466 -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 +429 -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 +251 -0
  33. data/lib/state_machine/event_collection.rb +113 -0
  34. data/lib/state_machine/extensions.rb +158 -0
  35. data/lib/state_machine/guard.rb +219 -0
  36. data/lib/state_machine/integrations.rb +68 -0
  37. data/lib/state_machine/integrations/active_record.rb +444 -0
  38. data/lib/state_machine/integrations/active_record/locale.rb +10 -0
  39. data/lib/state_machine/integrations/active_record/observer.rb +41 -0
  40. data/lib/state_machine/integrations/data_mapper.rb +325 -0
  41. data/lib/state_machine/integrations/data_mapper/observer.rb +139 -0
  42. data/lib/state_machine/integrations/sequel.rb +292 -0
  43. data/lib/state_machine/machine.rb +1431 -0
  44. data/lib/state_machine/machine_collection.rb +146 -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/transition.rb +367 -0
  51. data/tasks/state_machine.rake +1 -0
  52. data/tasks/state_machine.rb +30 -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 +129 -0
  60. data/test/unit/event_collection_test.rb +293 -0
  61. data/test/unit/event_test.rb +605 -0
  62. data/test/unit/guard_test.rb +862 -0
  63. data/test/unit/integrations/active_record_test.rb +1001 -0
  64. data/test/unit/integrations/data_mapper_test.rb +694 -0
  65. data/test/unit/integrations/sequel_test.rb +486 -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 +710 -0
  70. data/test/unit/machine_test.rb +1910 -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 +1113 -0
  78. metadata +161 -0
@@ -0,0 +1,1113 @@
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 TransitionWithCustomMachineNameTest < Test::Unit::TestCase
201
+ def setup
202
+ @klass = Class.new
203
+ @machine = StateMachine::Machine.new(@klass, :state_id, :as => 'state')
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 TransitionWithActionTest < Test::Unit::TestCase
228
+ def setup
229
+ @klass = Class.new do
230
+ def save
231
+ end
232
+ end
233
+
234
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
235
+ @machine.state :parked, :idling
236
+ @machine.event :ignite
237
+
238
+ @object = @klass.new
239
+ @object.state = 'parked'
240
+
241
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
242
+ end
243
+
244
+ def test_should_have_an_action
245
+ assert_equal :save, @transition.action
246
+ end
247
+
248
+ def test_should_not_have_a_result
249
+ assert_nil @transition.result
250
+ end
251
+ end
252
+
253
+ class TransitionAfterBeingPersistedTest < Test::Unit::TestCase
254
+ def setup
255
+ @klass = Class.new
256
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
257
+ @machine.state :parked, :idling
258
+ @machine.event :ignite
259
+
260
+ @object = @klass.new
261
+ @object.state = 'parked'
262
+
263
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
264
+ @transition.persist
265
+ end
266
+
267
+ def test_should_update_state_value
268
+ assert_equal 'idling', @object.state
269
+ end
270
+
271
+ def test_should_not_change_from_state
272
+ assert_equal 'parked', @transition.from
273
+ end
274
+
275
+ def test_should_not_change_to_state
276
+ assert_equal 'idling', @transition.to
277
+ end
278
+
279
+ def test_should_revert_to_from_state_on_rollback
280
+ @transition.rollback
281
+ assert_equal 'parked', @object.state
282
+ end
283
+ end
284
+
285
+ class TransitionAfterBeingRolledBackTest < Test::Unit::TestCase
286
+ def setup
287
+ @klass = Class.new
288
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
289
+ @machine.state :parked, :idling
290
+ @machine.event :ignite
291
+
292
+ @object = @klass.new
293
+ @object.state = 'parked'
294
+
295
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
296
+ @object.state = 'idling'
297
+
298
+ @transition.rollback
299
+ end
300
+
301
+ def test_should_update_state_value_to_from_state
302
+ assert_equal 'parked', @object.state
303
+ end
304
+
305
+ def test_should_not_change_from_state
306
+ assert_equal 'parked', @transition.from
307
+ end
308
+
309
+ def test_should_not_change_to_state
310
+ assert_equal 'idling', @transition.to
311
+ end
312
+
313
+ def test_should_still_be_able_to_persist
314
+ @transition.persist
315
+ assert_equal 'idling', @object.state
316
+ end
317
+ end
318
+
319
+ class TransitionWithCallbacksTest < Test::Unit::TestCase
320
+ def setup
321
+ @klass = Class.new do
322
+ attr_reader :saved, :save_state
323
+
324
+ def save
325
+ @save_state = state
326
+ @saved = true
327
+ end
328
+ end
329
+
330
+ @machine = StateMachine::Machine.new(@klass)
331
+ @machine.state :parked, :idling
332
+ @machine.event :ignite
333
+
334
+ @object = @klass.new
335
+ @object.state = 'parked'
336
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
337
+ end
338
+
339
+ def test_should_run_before_callbacks_on_before
340
+ @machine.before_transition(lambda {|object| @run = true})
341
+ result = @transition.before
342
+
343
+ assert_equal true, result
344
+ assert_equal true, @run
345
+ end
346
+
347
+ def test_should_run_before_callbacks_in_the_order_they_were_defined
348
+ @callbacks = []
349
+ @machine.before_transition(lambda {@callbacks << 1})
350
+ @machine.before_transition(lambda {@callbacks << 2})
351
+ @transition.before
352
+
353
+ assert_equal [1, 2], @callbacks
354
+ end
355
+
356
+ def test_should_only_run_before_callbacks_that_match_transition_context
357
+ @count = 0
358
+ callback = lambda {@count += 1}
359
+
360
+ @machine.before_transition :from => :parked, :to => :idling, :on => :park, :do => callback
361
+ @machine.before_transition :from => :parked, :to => :parked, :on => :park, :do => callback
362
+ @machine.before_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback
363
+ @machine.before_transition :from => :idling, :to => :idling, :on => :park, :do => callback
364
+ @transition.before
365
+
366
+ assert_equal 1, @count
367
+ end
368
+
369
+ def test_should_pass_transition_to_before_callbacks
370
+ @machine.before_transition(lambda {|*args| @args = args})
371
+ @transition.before
372
+
373
+ assert_equal [@object, @transition], @args
374
+ end
375
+
376
+ def test_should_catch_halted_before_callbacks
377
+ @machine.before_transition(lambda {throw :halt})
378
+
379
+ result = nil
380
+ assert_nothing_thrown { result = @transition.before }
381
+ assert_equal false, result
382
+ end
383
+
384
+ def test_should_run_before_callbacks_on_perform_before_changing_the_state
385
+ @machine.before_transition(lambda {|object| @state = object.state})
386
+ @transition.perform
387
+
388
+ assert_equal 'parked', @state
389
+ end
390
+
391
+ def test_should_run_after_callbacks_on_after
392
+ @machine.after_transition(lambda {|object| @run = true})
393
+ result = @transition.after(true)
394
+
395
+ assert_equal true, result
396
+ assert_equal true, @run
397
+ end
398
+
399
+ def test_should_set_result_on_after
400
+ @transition.after
401
+ assert_nil @transition.result
402
+
403
+ @transition.after(1)
404
+ assert_equal 1, @transition.result
405
+ end
406
+
407
+ def test_should_run_after_callbacks_in_the_order_they_were_defined
408
+ @callbacks = []
409
+ @machine.after_transition(lambda {@callbacks << 1})
410
+ @machine.after_transition(lambda {@callbacks << 2})
411
+ @transition.after(true)
412
+
413
+ assert_equal [1, 2], @callbacks
414
+ end
415
+
416
+ def test_should_only_run_after_callbacks_that_match_transition_context
417
+ @count = 0
418
+ callback = lambda {@count += 1}
419
+
420
+ @machine.after_transition :from => :parked, :to => :idling, :on => :park, :do => callback
421
+ @machine.after_transition :from => :parked, :to => :parked, :on => :park, :do => callback
422
+ @machine.after_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback
423
+ @machine.after_transition :from => :idling, :to => :idling, :on => :park, :do => callback
424
+ @transition.after(true)
425
+
426
+ assert_equal 1, @count
427
+ end
428
+
429
+ def test_should_pass_transition_to_after_callbacks
430
+ @machine.after_transition(lambda {|*args| @args = args})
431
+
432
+ @transition.after(true)
433
+ assert_equal [@object, @transition], @args
434
+ assert_equal true, @transition.result
435
+
436
+ @transition.after(false)
437
+ assert_equal [@object, @transition], @args
438
+ assert_equal false, @transition.result
439
+ end
440
+
441
+ def test_should_catch_halted_after_callbacks
442
+ @machine.after_transition(lambda {throw :halt})
443
+
444
+ result = nil
445
+ assert_nothing_thrown { result = @transition.after(true) }
446
+ assert_equal true, result
447
+ end
448
+
449
+ def test_should_run_after_callbacks_on_perform_after_running_the_action
450
+ @machine.after_transition(lambda {|object| @state = object.state})
451
+ @transition.perform(true)
452
+
453
+ assert_equal 'idling', @state
454
+ end
455
+ end
456
+
457
+ class TransitionAfterBeingPerformedTest < Test::Unit::TestCase
458
+ def setup
459
+ @klass = Class.new do
460
+ attr_reader :saved, :save_state
461
+
462
+ def save
463
+ @save_state = state
464
+ @saved = true
465
+ end
466
+ end
467
+
468
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
469
+ @machine.state :parked, :idling
470
+ @machine.event :ignite
471
+
472
+ @object = @klass.new
473
+ @object.state = 'parked'
474
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
475
+ @result = @transition.perform
476
+ end
477
+
478
+ def test_should_have_empty_args
479
+ assert_equal [], @transition.args
480
+ end
481
+
482
+ def test_should_have_a_result
483
+ assert_equal true, @transition.result
484
+ end
485
+
486
+ def test_should_be_successful
487
+ assert_equal true, @result
488
+ end
489
+
490
+ def test_should_change_the_current_state
491
+ assert_equal 'idling', @object.state
492
+ end
493
+
494
+ def test_should_run_the_action
495
+ assert @object.saved
496
+ end
497
+
498
+ def test_should_run_the_action_after_saving_the_state
499
+ assert_equal 'idling', @object.save_state
500
+ end
501
+ end
502
+
503
+ class TransitionWithPerformArgumentsTest < Test::Unit::TestCase
504
+ def setup
505
+ @klass = Class.new do
506
+ attr_reader :saved
507
+
508
+ def save
509
+ @saved = true
510
+ end
511
+ end
512
+
513
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
514
+ @machine.state :parked, :idling
515
+ @machine.event :ignite
516
+
517
+ @object = @klass.new
518
+ @object.state = 'parked'
519
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
520
+ end
521
+
522
+ def test_should_have_arguments
523
+ @transition.perform(1, 2)
524
+
525
+ assert_equal [1, 2], @transition.args
526
+ assert @object.saved
527
+ end
528
+
529
+ def test_should_not_include_run_action_in_arguments
530
+ @transition.perform(1, 2, false)
531
+
532
+ assert_equal [1, 2], @transition.args
533
+ assert !@object.saved
534
+ end
535
+ end
536
+
537
+ class TransitionWithoutRunningActionTest < Test::Unit::TestCase
538
+ def setup
539
+ @klass = Class.new do
540
+ attr_reader :saved
541
+
542
+ def save
543
+ @saved = true
544
+ end
545
+ end
546
+
547
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
548
+ @machine.state :parked, :idling
549
+ @machine.event :ignite
550
+
551
+ @object = @klass.new
552
+ @object.state = 'parked'
553
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
554
+ @result = @transition.perform(false)
555
+ end
556
+
557
+ def test_should_have_empty_args
558
+ assert_equal [], @transition.args
559
+ end
560
+
561
+ def test_should_not_have_a_result
562
+ assert_nil @transition.result
563
+ end
564
+
565
+ def test_should_be_successful
566
+ assert_equal true, @result
567
+ end
568
+
569
+ def test_should_change_the_current_state
570
+ assert_equal 'idling', @object.state
571
+ end
572
+
573
+ def test_should_not_run_the_action
574
+ assert !@object.saved
575
+ end
576
+ end
577
+
578
+ class TransitionWithTransactionsTest < Test::Unit::TestCase
579
+ def setup
580
+ @klass = Class.new do
581
+ class << self
582
+ attr_accessor :running_transaction
583
+ end
584
+
585
+ attr_accessor :result
586
+
587
+ def save
588
+ @result = self.class.running_transaction
589
+ true
590
+ end
591
+ end
592
+
593
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
594
+ @machine.state :parked, :idling
595
+ @machine.event :ignite
596
+
597
+ @object = @klass.new
598
+ @object.state = 'parked'
599
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
600
+
601
+ class << @machine
602
+ def within_transaction(object)
603
+ owner_class.running_transaction = object
604
+ yield
605
+ owner_class.running_transaction = false
606
+ end
607
+ end
608
+ end
609
+
610
+ def test_should_run_blocks_within_transaction_for_object
611
+ @transition.within_transaction do
612
+ @result = @klass.running_transaction
613
+ end
614
+
615
+ assert_equal @object, @result
616
+ end
617
+
618
+ def test_should_run_before_callbacks_within_transaction
619
+ @machine.before_transition(lambda {|object| @result = @klass.running_transaction})
620
+ @transition.perform
621
+
622
+ assert_equal @object, @result
623
+ end
624
+
625
+ def test_should_run_action_within_transaction
626
+ @transition.perform
627
+
628
+ assert_equal @object, @object.result
629
+ end
630
+
631
+ def test_should_run_after_callbacks_within_transaction
632
+ @machine.after_transition(lambda {|object| @result = @klass.running_transaction})
633
+ @transition.perform
634
+
635
+ assert_equal @object, @result
636
+ end
637
+ end
638
+
639
+ class TransitionHaltedDuringBeforeCallbacksTest < Test::Unit::TestCase
640
+ def setup
641
+ @klass = Class.new do
642
+ class << self; attr_accessor :cancelled_transaction; end
643
+ attr_reader :saved
644
+
645
+ def save
646
+ @saved = true
647
+ end
648
+ end
649
+ @before_count = 0
650
+ @after_count = 0
651
+
652
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
653
+ @machine.state :parked, :idling
654
+ @machine.event :ignite
655
+ class << @machine
656
+ def within_transaction(object)
657
+ owner_class.cancelled_transaction = yield == false
658
+ end
659
+ end
660
+
661
+ @machine.before_transition lambda {@before_count += 1; throw :halt}
662
+ @machine.before_transition lambda {@before_count += 1}
663
+ @machine.after_transition lambda {@after_count += 1}
664
+
665
+ @object = @klass.new
666
+ @object.state = 'parked'
667
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
668
+ @result = @transition.perform
669
+ end
670
+
671
+ def test_should_not_be_successful
672
+ assert !@result
673
+ end
674
+
675
+ def test_should_not_change_current_state
676
+ assert_equal 'parked', @object.state
677
+ end
678
+
679
+ def test_should_not_run_action
680
+ assert !@object.saved
681
+ end
682
+
683
+ def test_should_not_run_further_before_callbacks
684
+ assert_equal 1, @before_count
685
+ end
686
+
687
+ def test_should_not_run_after_callbacks
688
+ assert_equal 0, @after_count
689
+ end
690
+
691
+ def test_should_cancel_the_transaction
692
+ assert @klass.cancelled_transaction
693
+ end
694
+ end
695
+
696
+ class TransitionHaltedAfterCallbackTest < Test::Unit::TestCase
697
+ def setup
698
+ @klass = Class.new do
699
+ class << self; attr_accessor :cancelled_transaction; end
700
+ attr_reader :saved
701
+
702
+ def save
703
+ @saved = true
704
+ end
705
+ end
706
+ @before_count = 0
707
+ @after_count = 0
708
+
709
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
710
+ @machine.state :parked, :idling
711
+ @machine.event :ignite
712
+ class << @machine
713
+ def within_transaction(object)
714
+ owner_class.cancelled_transaction = yield == false
715
+ end
716
+ end
717
+
718
+ @machine.before_transition lambda {@before_count += 1}
719
+ @machine.after_transition lambda {@after_count += 1; throw :halt}
720
+ @machine.after_transition lambda {@after_count += 1}
721
+
722
+ @object = @klass.new
723
+ @object.state = 'parked'
724
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
725
+ @result = @transition.perform
726
+ end
727
+
728
+ def test_should_be_successful
729
+ assert @result
730
+ end
731
+
732
+ def test_should_change_current_state
733
+ assert_equal 'idling', @object.state
734
+ end
735
+
736
+ def test_should_run_before_callbacks
737
+ assert_equal 1, @before_count
738
+ end
739
+
740
+ def test_should_not_run_further_after_callbacks
741
+ assert_equal 1, @after_count
742
+ end
743
+
744
+ def test_should_not_cancel_the_transaction
745
+ assert !@klass.cancelled_transaction
746
+ end
747
+ end
748
+
749
+ class TransitionWithActionFailedTest < Test::Unit::TestCase
750
+ def setup
751
+ @klass = Class.new do
752
+ class << self; attr_accessor :cancelled_transaction; end
753
+ attr_reader :saved
754
+
755
+ def save
756
+ false
757
+ end
758
+ end
759
+ @before_count = 0
760
+ @after_count = 0
761
+
762
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
763
+ @machine.state :parked, :idling
764
+ @machine.event :ignite
765
+ class << @machine
766
+ def within_transaction(object)
767
+ owner_class.cancelled_transaction = yield == false
768
+ end
769
+ end
770
+
771
+ @machine.before_transition lambda {@before_count += 1}
772
+ @machine.after_transition lambda {@after_count += 1}
773
+
774
+ @object = @klass.new
775
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
776
+ @result = @transition.perform
777
+ end
778
+
779
+ def test_should_not_be_successful
780
+ assert !@result
781
+ end
782
+
783
+ def test_should_not_change_current_state
784
+ assert_nil @object.state
785
+ end
786
+
787
+ def test_should_run_before_callbacks
788
+ assert_equal 1, @before_count
789
+ end
790
+
791
+ def test_should_run_after_callbacks
792
+ assert_equal 1, @after_count
793
+ end
794
+
795
+ def test_should_cancel_the_transaction
796
+ assert @klass.cancelled_transaction
797
+ end
798
+ end
799
+
800
+ class TransitionWithActionErrorTest < Test::Unit::TestCase
801
+ def setup
802
+ @klass = Class.new do
803
+ def save
804
+ raise ArgumentError
805
+ end
806
+ end
807
+
808
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
809
+ @machine.state :parked, :idling
810
+ @machine.event :ignite
811
+
812
+ @object = @klass.new
813
+ @object.state = 'parked'
814
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
815
+
816
+ @raised = true
817
+ begin
818
+ @transition.perform
819
+ @raised = false
820
+ rescue ArgumentError
821
+ end
822
+ end
823
+
824
+ def test_should_not_catch_exception
825
+ assert @raised
826
+ end
827
+
828
+ def test_should_not_change_current_state
829
+ assert_equal 'parked', @object.state
830
+ end
831
+ end
832
+
833
+ class TransitionsInParallelTest < Test::Unit::TestCase
834
+ def setup
835
+ @klass = Class.new do
836
+ attr_reader :persisted
837
+
838
+ def initialize
839
+ @persisted = []
840
+ @state = 'parked'
841
+ @status = 'first_gear'
842
+ super
843
+ end
844
+
845
+ def state=(value)
846
+ @persisted << value
847
+ @state = value
848
+ end
849
+
850
+ def status=(value)
851
+ @persisted << value
852
+ @status = value
853
+ end
854
+ end
855
+
856
+ @state = StateMachine::Machine.new(@klass, :state)
857
+ @state.event :ignite
858
+ @state.state :parked, :idling
859
+
860
+ @status = StateMachine::Machine.new(@klass, :status)
861
+ @status.event :shift_up
862
+ @status.state :first_gear, :second_gear
863
+
864
+ @object = @klass.new
865
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
866
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
867
+
868
+ @result = StateMachine::Transition.perform([@state_transition, @status_transition])
869
+ end
870
+
871
+ def test_should_raise_exception_if_attempted_on_the_same_state_machine
872
+ exception = assert_raise(ArgumentError) { StateMachine::Transition.perform([@state_transition, @state_transition]) }
873
+ assert_equal 'Cannot perform multiple transitions in parallel for the same state machine attribute', exception.message
874
+ end
875
+
876
+ def test_should_perform
877
+ assert_equal true, @result
878
+ end
879
+
880
+ def test_should_persist_first_state
881
+ assert_equal 'idling', @object.state
882
+ end
883
+
884
+ def test_should_persist_second_state
885
+ assert_equal 'second_gear', @object.status
886
+ end
887
+
888
+ def test_should_persist_in_order
889
+ assert_equal ['idling', 'second_gear'], @object.persisted
890
+ end
891
+
892
+ def test_should_have_args_in_transitions
893
+ assert_equal [], @state_transition.args
894
+ assert_equal [], @status_transition.args
895
+ end
896
+ end
897
+
898
+ class TransitionsInParallelWithCallbacksTest < Test::Unit::TestCase
899
+ def setup
900
+ @klass = Class.new
901
+
902
+ @before_callbacks = []
903
+ @after_callbacks = []
904
+
905
+ @state = StateMachine::Machine.new(@klass, :state, :initial => :parked)
906
+ @state.event :ignite
907
+ @state.state :parked, :idling
908
+ @state.before_transition lambda {@before_callbacks << :state}
909
+ @state.after_transition lambda {@after_callbacks << :state}
910
+
911
+ @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear)
912
+ @status.event :shift_up
913
+ @status.state :first_gear, :second_gear
914
+ @status.before_transition lambda {@before_callbacks << :status}
915
+ @status.after_transition lambda {@after_callbacks << :status}
916
+
917
+ @object = @klass.new
918
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
919
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
920
+ end
921
+
922
+ def test_should_run_before_callbacks_in_order
923
+ perform
924
+ assert_equal [:state, :status], @before_callbacks
925
+ end
926
+
927
+ def test_should_run_after_callbacks_in_order
928
+ perform
929
+ assert_equal [:state, :status], @after_callbacks
930
+ end
931
+
932
+ def test_should_not_run_after_callbacks_if_disabled
933
+ perform(:after => false)
934
+ assert_equal [], @after_callbacks
935
+ end
936
+
937
+ def test_should_halt_if_before_callback_halted_for_first_transition
938
+ @state.before_transition lambda {throw :halt}
939
+
940
+ assert_equal false, perform
941
+ assert_equal [:state], @before_callbacks
942
+ assert_equal 'parked', @object.state
943
+ assert_equal 'first_gear', @object.status
944
+ assert_equal [], @after_callbacks
945
+ end
946
+
947
+ def test_should_halt_if_before_callback_halted_for_second_transition
948
+ @status.before_transition lambda {throw :halt}
949
+
950
+ assert_equal false, perform
951
+ assert_equal [:state, :status], @before_callbacks
952
+ assert_equal 'parked', @object.state
953
+ assert_equal 'first_gear', @object.status
954
+ assert_equal [], @after_callbacks
955
+ end
956
+
957
+ def test_should_perform_if_after_callback_halted_for_first_transition
958
+ @state.after_transition lambda {throw :halt}
959
+ @state.after_transition lambda {@after_callbacks << :invalid}
960
+
961
+ assert_equal true, perform
962
+ assert_equal [:state, :status], @before_callbacks
963
+ assert_equal 'idling', @object.state
964
+ assert_equal 'second_gear', @object.status
965
+ assert_equal [:state, :status], @after_callbacks
966
+ end
967
+
968
+ def test_should_perform_if_after_callback_halted_for_second_transition
969
+ @status.after_transition lambda {throw :halt}
970
+ @status.after_transition lambda {@after_callbacks << :invalid}
971
+
972
+ assert_equal true, perform
973
+ assert_equal [:state, :status], @before_callbacks
974
+ assert_equal 'idling', @object.state
975
+ assert_equal 'second_gear', @object.status
976
+ assert_equal [:state, :status], @after_callbacks
977
+ end
978
+
979
+ private
980
+ def perform(options = {})
981
+ StateMachine::Transition.perform([@state_transition, @status_transition], options)
982
+ end
983
+ end
984
+
985
+ class TransitionsInParallelWithActionsTest < Test::Unit::TestCase
986
+ def setup
987
+ @klass = Class.new do
988
+ attr_reader :actions
989
+
990
+ def save_state
991
+ (@actions ||= []) << :save_state
992
+ :save_state
993
+ end
994
+
995
+ def save_status
996
+ (@actions ||= []) << :save_status
997
+ :save_status
998
+ end
999
+ end
1000
+
1001
+ @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save_state)
1002
+ @state.event :ignite
1003
+ @state.state :parked, :idling
1004
+
1005
+ @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save_status)
1006
+ @status.event :shift_up
1007
+ @status.state :first_gear, :second_gear
1008
+
1009
+ @object = @klass.new
1010
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
1011
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
1012
+ end
1013
+
1014
+ def test_should_run_actions_in_order
1015
+ perform
1016
+ assert_equal [:save_state, :save_status], @object.actions
1017
+ end
1018
+
1019
+ def test_should_store_action_specific_results
1020
+ perform
1021
+ assert_equal :save_state, @state_transition.result
1022
+ assert_equal :save_status, @status_transition.result
1023
+ end
1024
+
1025
+ def test_should_not_perform_if_action_fails_for_first_transition
1026
+ @klass.class_eval do
1027
+ def save_state
1028
+ false
1029
+ end
1030
+ end
1031
+
1032
+ assert_equal false, perform
1033
+ assert_equal 'parked', @object.state
1034
+ assert_equal 'first_gear', @object.status
1035
+ end
1036
+
1037
+ def test_should_not_perform_if_action_fails_for_second_transition
1038
+ @klass.class_eval do
1039
+ def save_status
1040
+ false
1041
+ end
1042
+ end
1043
+
1044
+ assert_equal false, perform
1045
+ assert_equal 'parked', @object.state
1046
+ assert_equal 'first_gear', @object.status
1047
+ end
1048
+
1049
+ def test_should_rollback_if_action_errors_for_first_transition
1050
+ @klass.class_eval do
1051
+ def save_state
1052
+ raise ArgumentError
1053
+ end
1054
+ end
1055
+
1056
+ begin; perform; rescue; end
1057
+ assert_equal 'parked', @object.state
1058
+ assert_equal 'first_gear', @object.status
1059
+ end
1060
+
1061
+ def test_should_rollback_if_action_errors_for_second_transition
1062
+ @klass.class_eval do
1063
+ def save_status
1064
+ raise ArgumentError
1065
+ end
1066
+ end
1067
+
1068
+ begin; perform; rescue; end
1069
+ assert_equal 'parked', @object.state
1070
+ assert_equal 'first_gear', @object.status
1071
+ end
1072
+
1073
+ private
1074
+ def perform(options = {})
1075
+ StateMachine::Transition.perform([@state_transition, @status_transition], options)
1076
+ end
1077
+ end
1078
+
1079
+ class TransitionsWithPerformBlockTest < Test::Unit::TestCase
1080
+ def setup
1081
+ @klass = Class.new
1082
+
1083
+ @state = StateMachine::Machine.new(@klass, :state, :initial => :parked)
1084
+ @state.event :ignite
1085
+ @state.state :parked, :idling
1086
+
1087
+ @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear)
1088
+ @status.event :shift_up
1089
+ @status.state :first_gear, :second_gear
1090
+
1091
+ @object = @klass.new
1092
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
1093
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
1094
+ end
1095
+
1096
+ def test_should_be_perform_if_result_is_not_false
1097
+ assert StateMachine::Transition.perform([@state_transition, @status_transition]) { true }
1098
+ assert_equal 'idling', @object.state
1099
+ assert_equal 'second_gear', @object.status
1100
+ end
1101
+
1102
+ def test_should_not_perform_if_result_is_false
1103
+ assert !StateMachine::Transition.perform([@state_transition, @status_transition]) { false }
1104
+ assert_equal 'parked', @object.state
1105
+ assert_equal 'first_gear', @object.status
1106
+ end
1107
+
1108
+ def test_should_use_result_as_transition_result
1109
+ StateMachine::Transition.perform([@state_transition, @status_transition]) { 1 }
1110
+ assert_equal 1, @state_transition.result
1111
+ assert_equal 1, @status_transition.result
1112
+ end
1113
+ end