joelind-state_machine 0.8.1

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 +297 -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 +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/transition.rb +394 -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 +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 +1374 -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 +163 -0
@@ -0,0 +1,42 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class IntegrationMatcherTest < Test::Unit::TestCase
4
+ def setup
5
+ @klass = Class.new
6
+ end
7
+
8
+ def test_should_return_nil_if_no_match_found
9
+ assert_nil StateMachine::Integrations.match(@klass)
10
+ end
11
+
12
+ def test_should_return_integration_class_if_match_found
13
+ integration = Module.new do
14
+ def self.matches?(klass)
15
+ true
16
+ end
17
+ end
18
+ StateMachine::Integrations.const_set('Custom', integration)
19
+
20
+ assert_equal integration, StateMachine::Integrations.match(@klass)
21
+ ensure
22
+ StateMachine::Integrations.send(:remove_const, 'Custom')
23
+ end
24
+ end
25
+
26
+ class IntegrationFinderTest < Test::Unit::TestCase
27
+ def test_should_find_active_record
28
+ assert_equal StateMachine::Integrations::ActiveRecord, StateMachine::Integrations.find(:active_record)
29
+ end
30
+
31
+ def test_should_find_data_mapper
32
+ assert_equal StateMachine::Integrations::DataMapper, StateMachine::Integrations.find(:data_mapper)
33
+ end
34
+
35
+ def test_should_find_sequel
36
+ assert_equal StateMachine::Integrations::Sequel, StateMachine::Integrations.find(:sequel)
37
+ end
38
+
39
+ def test_should_raise_an_exception_if_invalid
40
+ assert_raise(NameError) { StateMachine::Integrations.find(:invalid) }
41
+ end
42
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class InvalidEventTest < Test::Unit::TestCase
4
+ def test_should_exist
5
+ assert_not_nil StateMachine::InvalidEvent
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class InvalidTransitionTest < Test::Unit::TestCase
4
+ def test_should_exist
5
+ assert_not_nil StateMachine::InvalidTransition
6
+ end
7
+ end
@@ -0,0 +1,938 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class MachineCollectionByDefaultTest < Test::Unit::TestCase
4
+ def setup
5
+ @machines = StateMachine::MachineCollection.new
6
+ end
7
+
8
+ def test_should_not_have_any_machines
9
+ assert @machines.empty?
10
+ end
11
+ end
12
+
13
+ class MachineCollectionStateInitializationTest < Test::Unit::TestCase
14
+ def setup
15
+ @machines = StateMachine::MachineCollection.new
16
+
17
+ @klass = Class.new
18
+
19
+ @machines[:state] = StateMachine::Machine.new(@klass, :state, :initial => :parked)
20
+ @machines[:alarm_state] = StateMachine::Machine.new(@klass, :alarm_state, :initial => lambda {|object| :active})
21
+ @machines[:alarm_state].state :active, :value => lambda {'active'}
22
+
23
+ # Prevent the auto-initialization hook from firing
24
+ @klass.class_eval do
25
+ def initialize
26
+ end
27
+ end
28
+
29
+ @object = @klass.new
30
+ @object.state = nil
31
+ @object.alarm_state = nil
32
+ end
33
+
34
+ def test_should_set_states_if_nil
35
+ @machines.initialize_states(@object)
36
+
37
+ assert_equal 'parked', @object.state
38
+ assert_equal 'active', @object.alarm_state
39
+ end
40
+
41
+ def test_should_set_states_if_empty
42
+ @object.state = ''
43
+ @object.alarm_state = ''
44
+ @machines.initialize_states(@object)
45
+
46
+ assert_equal 'parked', @object.state
47
+ assert_equal 'active', @object.alarm_state
48
+ end
49
+
50
+ def test_should_not_set_states_if_not_empty
51
+ @object.state = 'idling'
52
+ @object.alarm_state = 'off'
53
+ @machines.initialize_states(@object)
54
+
55
+ assert_equal 'idling', @object.state
56
+ assert_equal 'off', @object.alarm_state
57
+ end
58
+
59
+ def test_should_only_initialize_static_states_if_dynamic_disabled
60
+ @machines.initialize_states(@object, :dynamic => false)
61
+
62
+ assert_equal 'parked', @object.state
63
+ assert_nil @object.alarm_state
64
+ end
65
+
66
+ def test_should_only_initialize_dynamic_states_if_dynamic_enabled
67
+ @machines.initialize_states(@object, :dynamic => true)
68
+
69
+ assert_nil @object.state
70
+ assert_equal 'active', @object.alarm_state
71
+ end
72
+
73
+ def test_should_not_set_states_if_ignored
74
+ @machines.initialize_states(@object, :ignore => [:state, :alarm_state])
75
+
76
+ assert_nil @object.state
77
+ assert_nil @object.alarm_state
78
+ end
79
+
80
+ def test_should_set_states_if_not_ignored_and_nil
81
+ @machines.initialize_states(@object, :ignore => [])
82
+
83
+ assert_equal 'parked', @object.state
84
+ assert_equal 'active', @object.alarm_state
85
+ end
86
+
87
+ def test_should_set_states_if_not_ignored_and_empty
88
+ @object.state = ''
89
+ @object.alarm_state = ''
90
+ @machines.initialize_states(@object, :ignore => [])
91
+
92
+ assert_equal 'parked', @object.state
93
+ assert_equal 'active', @object.alarm_state
94
+ end
95
+
96
+ def test_should_set_states_if_not_ignored_and_not_empty
97
+ @object.state = 'idling'
98
+ @object.alarm_state = 'inactive'
99
+ @machines.initialize_states(@object, :ignore => [])
100
+
101
+ assert_equal 'parked', @object.state
102
+ assert_equal 'active', @object.alarm_state
103
+ end
104
+ end
105
+
106
+ class MachineCollectionFireExplicitTest < Test::Unit::TestCase
107
+ def setup
108
+ @machines = StateMachine::MachineCollection.new
109
+
110
+ @klass = Class.new do
111
+ attr_reader :saved
112
+
113
+ def save
114
+ @saved = true
115
+ end
116
+ end
117
+
118
+ # First machine
119
+ @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
120
+ @state.event :ignite do
121
+ transition :parked => :idling
122
+ end
123
+ @state.event :park do
124
+ transition :idling => :parked
125
+ end
126
+
127
+ # Second machine
128
+ @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :action => :save, :namespace => 'alarm')
129
+ @alarm_state.event :enable do
130
+ transition :off => :active
131
+ end
132
+ @alarm_state.event :disable do
133
+ transition :active => :off
134
+ end
135
+
136
+ @object = @klass.new
137
+ end
138
+
139
+ def test_should_raise_exception_if_invalid_event_specified
140
+ exception = assert_raise(StateMachine::InvalidEvent) { @machines.fire_events(@object, :invalid) }
141
+ assert_equal ':invalid is an unknown state machine event', exception.message
142
+
143
+ exception = assert_raise(StateMachine::InvalidEvent) { @machines.fire_events(@object, :ignite, :invalid) }
144
+ assert_equal ':invalid is an unknown state machine event', exception.message
145
+ end
146
+
147
+ def test_should_fail_if_any_event_cannot_transition
148
+ assert !@machines.fire_events(@object, :park, :disable_alarm)
149
+ assert_equal 'parked', @object.state
150
+ assert_equal 'active', @object.alarm_state
151
+ assert !@object.saved
152
+
153
+ assert !@machines.fire_events(@object, :ignite, :enable_alarm)
154
+ assert_equal 'parked', @object.state
155
+ assert_equal 'active', @object.alarm_state
156
+ assert !@object.saved
157
+ end
158
+
159
+ def test_should_be_successful_if_all_events_transition
160
+ assert @machines.fire_events(@object, :ignite, :disable_alarm)
161
+ assert_equal 'idling', @object.state
162
+ assert_equal 'off', @object.alarm_state
163
+ assert @object.saved
164
+ end
165
+
166
+ def test_should_not_save_if_skipping_action
167
+ assert @machines.fire_events(@object, :ignite, :disable_alarm, false)
168
+ assert_equal 'idling', @object.state
169
+ assert_equal 'off', @object.alarm_state
170
+ assert !@object.saved
171
+ end
172
+ end
173
+
174
+ class MachineCollectionFireExplicitWithTransactionsTest < Test::Unit::TestCase
175
+ def setup
176
+ @machines = StateMachine::MachineCollection.new
177
+
178
+ @klass = Class.new do
179
+ attr_accessor :allow_save
180
+
181
+ def save
182
+ @allow_save
183
+ end
184
+ end
185
+
186
+ StateMachine::Integrations.const_set('Custom', Module.new do
187
+ attr_reader :rolled_back
188
+
189
+ def transaction(object)
190
+ @rolled_back = yield
191
+ end
192
+ end)
193
+
194
+ # First machine
195
+ @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save, :integration => :custom)
196
+ @state.event :ignite do
197
+ transition :parked => :idling
198
+ end
199
+
200
+ # Second machine
201
+ @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :action => :save, :namespace => 'alarm', :integration => :custom)
202
+ @alarm_state.event :disable do
203
+ transition :active => :off
204
+ end
205
+
206
+ @object = @klass.new
207
+ end
208
+
209
+ def test_should_not_rollback_if_successful
210
+ @object.allow_save = true
211
+
212
+ assert @machines.fire_events(@object, :ignite, :disable_alarm)
213
+ assert_equal true, @state.rolled_back
214
+ assert_nil @alarm_state.rolled_back
215
+ assert_equal 'idling', @object.state
216
+ assert_equal 'off', @object.alarm_state
217
+ end
218
+
219
+ def test_should_rollback_if_not_successful
220
+ @object.allow_save = false
221
+
222
+ assert !@machines.fire_events(@object, :ignite, :disable_alarm)
223
+ assert_equal false, @state.rolled_back
224
+ assert_nil @alarm_state.rolled_back
225
+ assert_equal 'parked', @object.state
226
+ assert_equal 'active', @object.alarm_state
227
+ end
228
+
229
+ def teardown
230
+ StateMachine::Integrations.send(:remove_const, 'Custom')
231
+ end
232
+ end
233
+
234
+ class MachineCollectionFireExplicitWithValidationsTest < Test::Unit::TestCase
235
+ def setup
236
+ StateMachine::Integrations.const_set('Custom', Module.new do
237
+ def invalidate(object, attribute, message, values = [])
238
+ (object.errors ||= []) << generate_message(message, values)
239
+ end
240
+
241
+ def reset(object)
242
+ object.errors = []
243
+ end
244
+ end)
245
+
246
+ @klass = Class.new do
247
+ attr_accessor :errors
248
+
249
+ def initialize
250
+ @errors = []
251
+ super
252
+ end
253
+ end
254
+
255
+ @machines = StateMachine::MachineCollection.new
256
+ @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :integration => :custom)
257
+ @state.event :ignite do
258
+ transition :parked => :idling
259
+ end
260
+
261
+ @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :namespace => 'alarm', :integration => :custom)
262
+ @alarm_state.event :disable do
263
+ transition :active => :off
264
+ end
265
+
266
+ @object = @klass.new
267
+ end
268
+
269
+ def test_should_not_invalidate_if_transitions_exist
270
+ assert @machines.fire_events(@object, :ignite, :disable_alarm)
271
+ assert_equal [], @object.errors
272
+ end
273
+
274
+ def test_should_invalidate_if_no_transitions_exist
275
+ @object.state = 'idling'
276
+ @object.alarm_state = 'off'
277
+
278
+ assert !@machines.fire_events(@object, :ignite, :disable_alarm)
279
+ assert_equal ['cannot transition via "ignite"', 'cannot transition via "disable_alarm"'], @object.errors
280
+ end
281
+
282
+ def teardown
283
+ StateMachine::Integrations.send(:remove_const, 'Custom')
284
+ end
285
+ end
286
+
287
+ class MachineCollectionFireImplicitTest < Test::Unit::TestCase
288
+ def setup
289
+ @klass = Class.new
290
+
291
+ @machines = StateMachine::MachineCollection.new
292
+ @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
293
+ @machine.event :ignite do
294
+ transition :parked => :idling
295
+ end
296
+
297
+ @saved = false
298
+ @object = @klass.new
299
+ end
300
+
301
+ def default_test
302
+ end
303
+ end
304
+
305
+ class MachineCollectionFireImplicitWithoutEventTest < MachineCollectionFireImplicitTest
306
+ def setup
307
+ super
308
+
309
+ @object.state_event = nil
310
+ @result = @machines.fire_event_attributes(@object, :save) { @saved = true }
311
+ end
312
+
313
+ def test_should_be_successful
314
+ assert_equal true, @result
315
+ end
316
+
317
+ def test_should_run_action
318
+ assert @saved
319
+ end
320
+
321
+ def test_should_not_transition_state
322
+ assert_equal 'parked', @object.state
323
+ end
324
+
325
+ def test_should_not_change_event_attribute
326
+ assert_nil @object.state_event
327
+ end
328
+
329
+ def test_should_not_have_event_transition
330
+ assert_nil @object.send(:state_event_transition)
331
+ end
332
+ end
333
+
334
+ class MachineCollectionFireImplicitWithBlankEventTest < MachineCollectionFireImplicitTest
335
+ def setup
336
+ super
337
+
338
+ @object.state_event = ''
339
+ @result = @machines.fire_event_attributes(@object, :save) { @saved = true }
340
+ end
341
+
342
+ def test_should_be_successful
343
+ assert_equal true, @result
344
+ end
345
+
346
+ def test_should_run_action
347
+ assert @saved
348
+ end
349
+
350
+ def test_should_not_transition_state
351
+ assert_equal 'parked', @object.state
352
+ end
353
+
354
+ def test_should_not_change_event_attribute
355
+ assert_nil @object.state_event
356
+ end
357
+
358
+ def test_should_not_have_event_transition
359
+ assert_nil @object.send(:state_event_transition)
360
+ end
361
+ end
362
+
363
+ class MachineCollectionFireImplicitWithInvalidEventTest < MachineCollectionFireImplicitTest
364
+ def setup
365
+ super
366
+
367
+ @object.state_event = 'invalid'
368
+ @result = @machines.fire_event_attributes(@object, :save) { @saved = true }
369
+ end
370
+
371
+ def test_should_not_be_successful
372
+ assert_equal false, @result
373
+ end
374
+
375
+ def test_should_not_run_action
376
+ assert !@saved
377
+ end
378
+
379
+ def test_should_not_transition_state
380
+ assert_equal 'parked', @object.state
381
+ end
382
+
383
+ def test_should_not_reset_event_attribute
384
+ assert_equal :invalid, @object.state_event
385
+ end
386
+
387
+ def test_should_not_have_event_transition
388
+ assert_nil @object.send(:state_event_transition)
389
+ end
390
+ end
391
+
392
+ class MachineCollectionFireImplicitWithoutTransitionTest < MachineCollectionFireImplicitTest
393
+ def setup
394
+ super
395
+
396
+ @object.state = 'idling'
397
+ @object.state_event = 'ignite'
398
+ @result = @machines.fire_event_attributes(@object, :save) { @saved = true }
399
+ end
400
+
401
+ def test_should_not_be_successful
402
+ assert_equal false, @result
403
+ end
404
+
405
+ def test_should_not_run_action
406
+ assert !@saved
407
+ end
408
+
409
+ def test_should_not_transition_state
410
+ assert_equal 'idling', @object.state
411
+ end
412
+
413
+ def test_should_not_reset_event_attribute
414
+ assert_equal :ignite, @object.state_event
415
+ end
416
+
417
+ def test_should_not_have_event_transition
418
+ assert_nil @object.send(:state_event_transition)
419
+ end
420
+ end
421
+
422
+ class MachineCollectionFireImplicitWithTransitionTest < MachineCollectionFireImplicitTest
423
+ def setup
424
+ super
425
+
426
+ @state_event = nil
427
+
428
+ @object.state_event = 'ignite'
429
+ @result = @machines.fire_event_attributes(@object, :save) do
430
+ @state_event = @object.state_event
431
+ @saved = true
432
+ end
433
+ end
434
+
435
+ def test_should_be_successful
436
+ assert_equal true, @result
437
+ end
438
+
439
+ def test_should_run_action
440
+ assert @saved
441
+ end
442
+
443
+ def test_should_not_have_event_while_running_action
444
+ assert_nil @state_event
445
+ end
446
+
447
+ def test_should_transition_state
448
+ assert_equal 'idling', @object.state
449
+ end
450
+
451
+ def test_should_reset_event_attribute
452
+ assert_nil @object.state_event
453
+ end
454
+
455
+ def test_should_not_have_event_transition
456
+ assert_nil @object.send(:state_event_transition)
457
+ end
458
+
459
+ def test_should_not_be_successful_if_fired_again
460
+ @object.state_event = 'ignite'
461
+ assert !@machines.fire_event_attributes(@object, :save) { true }
462
+ end
463
+ end
464
+
465
+ class MachineCollectionFireImplicitWithNonBooleanResultTest < MachineCollectionFireImplicitTest
466
+ def setup
467
+ super
468
+
469
+ @action_value = Object.new
470
+
471
+ @object.state_event = 'ignite'
472
+ @result = @machines.fire_event_attributes(@object, :save) do
473
+ @saved = true
474
+ @action_value
475
+ end
476
+ end
477
+
478
+ def test_should_be_successful
479
+ assert_equal @action_value, @result
480
+ end
481
+
482
+ def test_should_run_action
483
+ assert @saved
484
+ end
485
+
486
+ def test_should_transition_state
487
+ assert_equal 'idling', @object.state
488
+ end
489
+ end
490
+
491
+ class MachineCollectionFireImplicitWithActionFailureTest < MachineCollectionFireImplicitTest
492
+ def setup
493
+ super
494
+
495
+ @object.state_event = 'ignite'
496
+ @result = @machines.fire_event_attributes(@object, :save) { false }
497
+ end
498
+
499
+ def test_should_not_be_successful
500
+ assert_equal false, @result
501
+ end
502
+
503
+ def test_should_not_transition_state
504
+ assert_equal 'parked', @object.state
505
+ end
506
+
507
+ def test_should_not_reset_event_attribute
508
+ assert_equal :ignite, @object.state_event
509
+ end
510
+
511
+ def test_should_not_have_event_transition
512
+ assert_nil @object.send(:state_event_transition)
513
+ end
514
+ end
515
+
516
+ class MachineCollectionFireImplicitWithActionErrorTest < MachineCollectionFireImplicitTest
517
+ def setup
518
+ super
519
+
520
+ @object.state_event = 'ignite'
521
+ assert_raise(ArgumentError) { @machines.fire_event_attributes(@object, :save) { raise ArgumentError } }
522
+ end
523
+
524
+ def test_should_not_transition_state
525
+ assert_equal 'parked', @object.state
526
+ end
527
+
528
+ def test_should_not_reset_event_attribute
529
+ assert_equal :ignite, @object.state_event
530
+ end
531
+
532
+ def test_should_not_have_event_transition
533
+ assert_nil @object.send(:state_event_transition)
534
+ end
535
+ end
536
+
537
+ class MachineCollectionFireImplicitPartialTest < MachineCollectionFireImplicitTest
538
+ def setup
539
+ super
540
+
541
+ @state_event = nil
542
+ @state_event_transition = nil
543
+
544
+ @object.state_event = 'ignite'
545
+ @result = @machines.fire_event_attributes(@object, :save, false) do
546
+ @state_event = @object.state_event
547
+ @state_event_transition = @object.send(:state_event_transition)
548
+ true
549
+ end
550
+ end
551
+
552
+ def test_should_be_successful
553
+ assert @result
554
+ end
555
+
556
+ def test_should_not_have_event_while_running_action
557
+ assert_nil @state_event
558
+ end
559
+
560
+ def test_should_not_have_event_transition_while_running_action
561
+ assert_nil @state_event_transition
562
+ end
563
+
564
+ def test_should_transition_state
565
+ assert_equal 'idling', @object.state
566
+ end
567
+
568
+ def test_should_reset_event_attribute
569
+ assert_nil @object.state_event
570
+ end
571
+
572
+ def test_should_have_event_transition
573
+ assert_not_nil @object.send(:state_event_transition)
574
+ end
575
+
576
+ def test_should_reset_event_after_next_fire_on_success
577
+ assert @machines.fire_event_attributes(@object, :save) { true }
578
+ assert_equal 'idling', @object.state
579
+ assert_nil @object.state_event
580
+ end
581
+
582
+ def test_should_reset_event_transition_after_next_fire_on_success
583
+ assert @machines.fire_event_attributes(@object, :save) { true }
584
+ assert_nil @object.send(:state_event_transition)
585
+ end
586
+
587
+ def test_should_guard_transition_after_next_fire_on_success
588
+ @machines.fire_event_attributes(@object, :save) { true }
589
+
590
+ @object.state = 'idling'
591
+ @object.state_event = 'ignite'
592
+ assert !@machines.fire_event_attributes(@object, :save) { true }
593
+ end
594
+
595
+ def test_should_rollback_all_attributes_after_next_fire_on_failure
596
+ assert !@machines.fire_event_attributes(@object, :save) { false }
597
+ assert_equal 'parked', @object.state
598
+ assert_equal :ignite, @object.state_event
599
+ assert_nil @object.send(:state_event_transition)
600
+
601
+ @object.state = 'idling'
602
+ assert !@machines.fire_event_attributes(@object, :save) { false }
603
+ end
604
+
605
+ def test_should_guard_transition_after_next_fire_on_failure
606
+ @machines.fire_event_attributes(@object, :save) { false }
607
+
608
+ @object.state = 'idling'
609
+ assert !@machines.fire_event_attributes(@object, :save) { true }
610
+ end
611
+
612
+ def test_should_rollback_all_attributes_after_next_fire_on_error
613
+ assert_raise(ArgumentError) { @machines.fire_event_attributes(@object, :save) { raise ArgumentError } }
614
+ assert_equal 'parked', @object.state
615
+ assert_equal :ignite, @object.state_event
616
+ assert_nil @object.send(:state_event_transition)
617
+ end
618
+
619
+ def test_should_guard_transition_after_next_fire_on_error
620
+ begin
621
+ @machines.fire_event_attributes(@object, :save) { raise ArgumentError }
622
+ rescue ArgumentError
623
+ end
624
+
625
+ @object.state = 'idling'
626
+ assert !@machines.fire_event_attributes(@object, :save) { true }
627
+ end
628
+ end
629
+
630
+ class MachineCollectionFireImplicitPartialWithCallbacksTest < MachineCollectionFireImplicitTest
631
+ def setup
632
+ super
633
+
634
+ @object.state_event = 'ignite'
635
+ end
636
+
637
+ def test_should_run_before_callbacks
638
+ ran_callback = false
639
+ @machine.before_transition { ran_callback = true }
640
+ @machines.fire_event_attributes(@object, :save, false) { true }
641
+
642
+ assert ran_callback
643
+ end
644
+
645
+ def test_should_not_have_event_during_before_callbacks
646
+ state_event = nil
647
+ @machine.before_transition {|object, transition| state_event = object.state_event }
648
+ @machines.fire_event_attributes(@object, :save, false) { true }
649
+
650
+ assert_nil state_event
651
+ end
652
+
653
+ def test_should_not_have_event_transition_during_before_callbacks
654
+ state_event_transition = nil
655
+ @machine.before_transition {|object, transition| state_event_transition = object.send(:state_event_transition) }
656
+ @machines.fire_event_attributes(@object, :save, false) { true }
657
+
658
+ assert_nil state_event_transition
659
+ end
660
+
661
+ def test_should_not_run_after_callbacks
662
+ ran_callback = false
663
+ @machine.after_transition { ran_callback = true }
664
+ @machines.fire_event_attributes(@object, :save, false) { true }
665
+
666
+ assert !ran_callback
667
+ end
668
+
669
+ def test_should_not_have_event_during_after_callbacks
670
+ state_event = nil
671
+ @machine.after_transition {|object, transition| state_event = object.state_event }
672
+ @machines.fire_event_attributes(@object, :save, false) { true }
673
+
674
+ assert_nil state_event
675
+ end
676
+
677
+ def test_should_not_have_event_transition_during_after_callbacks
678
+ state_event_transition = nil
679
+ @machine.after_transition {|object, transition| state_event_transition = object.send(:state_event_transition) }
680
+ @machines.fire_event_attributes(@object, :save, false) { true }
681
+
682
+ assert_nil state_event_transition
683
+ end
684
+ end
685
+
686
+ class MachineCollectionFireImplicitNestedPartialTest < MachineCollectionFireImplicitTest
687
+ def setup
688
+ super
689
+
690
+ @partial_result = nil
691
+
692
+ @object.state_event = 'ignite'
693
+ @result = @machines.fire_event_attributes(@object, :save) do
694
+ @partial_result = @machines.fire_event_attributes(@object, :save, false) { true }
695
+ true
696
+ end
697
+ end
698
+
699
+ def test_should_be_successful
700
+ assert @result
701
+ end
702
+
703
+ def test_should_have_successful_partial_fire
704
+ assert @partial_result
705
+ end
706
+
707
+ def test_should_transition_state
708
+ assert_equal 'idling', @object.state
709
+ end
710
+
711
+ def test_should_reset_event_attribute
712
+ assert_nil @object.state_event
713
+ end
714
+
715
+ def test_should_reset_event_transition_attribute
716
+ assert_nil @object.send(:state_event_transition)
717
+ end
718
+ end
719
+
720
+ class MachineCollectionFireImplicitWithDifferentActionsTest < MachineCollectionFireImplicitTest
721
+ def setup
722
+ super
723
+
724
+ @machines[:alarm_state] = @alarm_machine = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :action => :save_alarm)
725
+ @alarm_machine.event :disable do
726
+ transition :active => :off
727
+ end
728
+
729
+ @saved = false
730
+ @object = @klass.new
731
+ @object.state_event = 'ignite'
732
+ @object.alarm_state_event = 'disable'
733
+
734
+ @machines.fire_event_attributes(@object, :save) { true }
735
+ end
736
+
737
+ def test_should_transition_states_for_action
738
+ assert_equal 'idling', @object.state
739
+ end
740
+
741
+ def test_should_reset_event_attribute_for_action
742
+ assert_nil @object.state_event
743
+ end
744
+
745
+ def test_should_reset_event_transition_attribute_for_action
746
+ assert_nil @object.send(:state_event_transition)
747
+ end
748
+
749
+ def test_should_not_transition_states_for_other_actions
750
+ assert_equal 'active', @object.alarm_state
751
+ end
752
+
753
+ def test_should_not_reset_event_attributes_for_other_actions
754
+ assert_equal :disable, @object.alarm_state_event
755
+ end
756
+ end
757
+
758
+ class MachineCollectionFireImplicitWithSameActionsTest < MachineCollectionFireImplicitTest
759
+ def setup
760
+ super
761
+
762
+ @machines[:alarm_state] = @alarm_machine = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :action => :save)
763
+ @alarm_machine.event :disable do
764
+ transition :active => :off
765
+ end
766
+
767
+ @saved = false
768
+ @object = @klass.new
769
+ @object.state_event = 'ignite'
770
+ @object.alarm_state_event = 'disable'
771
+
772
+ @machines.fire_event_attributes(@object, :save) { true }
773
+ end
774
+
775
+ def test_should_transition_all_states_for_action
776
+ assert_equal 'idling', @object.state
777
+ assert_equal 'off', @object.alarm_state
778
+ end
779
+
780
+ def test_should_reset_all_event_attributes_for_action
781
+ assert_nil @object.state_event
782
+ assert_nil @object.alarm_state_event
783
+ end
784
+ end
785
+
786
+ class MachineCollectionFireImplicitWithValidationsTest < Test::Unit::TestCase
787
+ def setup
788
+ StateMachine::Integrations.const_set('Custom', Module.new do
789
+ def invalidate(object, attribute, message, values = [])
790
+ (object.errors ||= []) << generate_message(message, values)
791
+ end
792
+
793
+ def reset(object)
794
+ object.errors = []
795
+ end
796
+ end)
797
+
798
+ @klass = Class.new do
799
+ attr_accessor :errors
800
+
801
+ def initialize
802
+ @errors = []
803
+ super
804
+ end
805
+ end
806
+
807
+ @machines = StateMachine::MachineCollection.new
808
+ @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save, :integration => :custom)
809
+ @machine.event :ignite do
810
+ transition :parked => :idling
811
+ end
812
+
813
+ @object = @klass.new
814
+ end
815
+
816
+ def test_should_invalidate_if_event_is_invalid
817
+ @object.state_event = 'invalid'
818
+ @machines.fire_event_attributes(@object, :save) { true }
819
+
820
+ assert !@object.errors.empty?
821
+ end
822
+
823
+ def test_should_invalidate_if_no_transition_exists
824
+ @object.state = 'idling'
825
+ @object.state_event = 'ignite'
826
+ @machines.fire_event_attributes(@object, :save) { true }
827
+
828
+ assert !@object.errors.empty?
829
+ end
830
+
831
+ def test_should_not_invalidate_if_transition_exists
832
+ @object.state_event = 'ignite'
833
+ @machines.fire_event_attributes(@object, :save) { true }
834
+
835
+ assert @object.errors.empty?
836
+ end
837
+
838
+ def teardown
839
+ StateMachine::Integrations.send(:remove_const, 'Custom')
840
+ end
841
+ end
842
+
843
+ class MachineCollectionFireImplicitWithCustomMachineNameTest < MachineCollectionFireImplicitTest
844
+ def setup
845
+ super
846
+
847
+ @object.state_event = 'ignite'
848
+ end
849
+
850
+ def test_should_be_successful_on_complete_file
851
+ assert @machines.fire_event_attributes(@object, :save) { true }
852
+ assert_equal 'idling', @object.state
853
+ assert_nil @object.state_event
854
+ assert_nil @object.send(:state_event_transition)
855
+ end
856
+
857
+ def test_should_be_successful_on_partial_fire
858
+ @machines.fire_event_attributes(@object, :save, false) { true }
859
+ assert_equal 'idling', @object.state
860
+ assert_nil @object.state_event
861
+ assert_not_nil @object.send(:state_event_transition)
862
+ end
863
+ end
864
+
865
+ class MachineFireImplicitWithMarshallingTest < MachineCollectionFireImplicitTest
866
+ def setup
867
+ super
868
+ self.class.const_set('Example', @klass)
869
+
870
+ @object.state_event = 'ignite'
871
+ end
872
+
873
+ def test_should_marshal_during_before_callbacks
874
+ @machine.before_transition {|object, transition| Marshal.dump(object)}
875
+ assert_nothing_raised { @machines.fire_event_attributes(@object, :save) { true } }
876
+ end
877
+
878
+ def test_should_marshal_during_action
879
+ assert_nothing_raised do
880
+ @machines.fire_event_attributes(@object, :save) do
881
+ Marshal.dump(@object)
882
+ true
883
+ end
884
+ end
885
+ end
886
+
887
+ def test_should_marshal_during_after_callbacks
888
+ @machine.after_transition {|object, transition| Marshal.dump(object)}
889
+ assert_nothing_raised { @machines.fire_event_attributes(@object, :save) { true } }
890
+ end
891
+
892
+ def teardown
893
+ self.class.send(:remove_const, 'Example')
894
+ end
895
+ end
896
+
897
+ class MachineFireImplicitPartialWithMarshallingTest < MachineCollectionFireImplicitTest
898
+ def setup
899
+ super
900
+ self.class.const_set('Example', @klass)
901
+
902
+ @object.state_event = 'ignite'
903
+ end
904
+
905
+ def test_should_marshal_during_before_callbacks
906
+ @machine.before_transition {|object, transition| Marshal.dump(object)}
907
+ assert_nothing_raised do
908
+ @machines.fire_event_attributes(@object, :save, false) { true }
909
+ @machines.fire_event_attributes(@object, :save) { true }
910
+ end
911
+ end
912
+
913
+ def test_should_marshal_during_action
914
+ assert_nothing_raised do
915
+ @machines.fire_event_attributes(@object, :save, false) do
916
+ Marshal.dump(@object)
917
+ true
918
+ end
919
+
920
+ @machines.fire_event_attributes(@object, :save) do
921
+ Marshal.dump(@object)
922
+ true
923
+ end
924
+ end
925
+ end
926
+
927
+ def test_should_marshal_during_after_callbacks
928
+ @machine.after_transition {|object, transition| Marshal.dump(object)}
929
+ assert_nothing_raised do
930
+ @machines.fire_event_attributes(@object, :save, false) { true }
931
+ @machines.fire_event_attributes(@object, :save) { true }
932
+ end
933
+ end
934
+
935
+ def teardown
936
+ self.class.send(:remove_const, 'Example')
937
+ end
938
+ end