pluginaweek-state_machine 0.7.6

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 +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