state_machine 0.6.3 → 0.7.0

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 (56) hide show
  1. data/CHANGELOG.rdoc +31 -1
  2. data/README.rdoc +33 -21
  3. data/Rakefile +2 -2
  4. data/examples/merb-rest/controller.rb +51 -0
  5. data/examples/merb-rest/model.rb +28 -0
  6. data/examples/merb-rest/view_edit.html.erb +24 -0
  7. data/examples/merb-rest/view_index.html.erb +23 -0
  8. data/examples/merb-rest/view_new.html.erb +13 -0
  9. data/examples/merb-rest/view_show.html.erb +17 -0
  10. data/examples/rails-rest/controller.rb +43 -0
  11. data/examples/rails-rest/migration.rb +11 -0
  12. data/examples/rails-rest/model.rb +23 -0
  13. data/examples/rails-rest/view_edit.html.erb +25 -0
  14. data/examples/rails-rest/view_index.html.erb +23 -0
  15. data/examples/rails-rest/view_new.html.erb +14 -0
  16. data/examples/rails-rest/view_show.html.erb +17 -0
  17. data/lib/state_machine/assertions.rb +2 -2
  18. data/lib/state_machine/callback.rb +14 -8
  19. data/lib/state_machine/condition_proxy.rb +3 -3
  20. data/lib/state_machine/event.rb +19 -21
  21. data/lib/state_machine/event_collection.rb +114 -0
  22. data/lib/state_machine/extensions.rb +127 -11
  23. data/lib/state_machine/guard.rb +1 -1
  24. data/lib/state_machine/integrations/active_record/locale.rb +2 -1
  25. data/lib/state_machine/integrations/active_record.rb +117 -39
  26. data/lib/state_machine/integrations/data_mapper/observer.rb +20 -64
  27. data/lib/state_machine/integrations/data_mapper.rb +71 -26
  28. data/lib/state_machine/integrations/sequel.rb +69 -21
  29. data/lib/state_machine/machine.rb +267 -139
  30. data/lib/state_machine/machine_collection.rb +145 -0
  31. data/lib/state_machine/matcher.rb +2 -2
  32. data/lib/state_machine/node_collection.rb +9 -4
  33. data/lib/state_machine/state.rb +22 -32
  34. data/lib/state_machine/state_collection.rb +66 -17
  35. data/lib/state_machine/transition.rb +259 -28
  36. data/lib/state_machine.rb +121 -56
  37. data/tasks/state_machine.rake +1 -0
  38. data/tasks/state_machine.rb +26 -0
  39. data/test/active_record.log +116877 -0
  40. data/test/functional/state_machine_test.rb +118 -12
  41. data/test/sequel.log +28542 -0
  42. data/test/unit/callback_test.rb +46 -1
  43. data/test/unit/condition_proxy_test.rb +55 -28
  44. data/test/unit/event_collection_test.rb +228 -0
  45. data/test/unit/event_test.rb +51 -46
  46. data/test/unit/integrations/active_record_test.rb +128 -70
  47. data/test/unit/integrations/data_mapper_test.rb +150 -58
  48. data/test/unit/integrations/sequel_test.rb +63 -6
  49. data/test/unit/invalid_event_test.rb +7 -0
  50. data/test/unit/machine_collection_test.rb +678 -0
  51. data/test/unit/machine_test.rb +198 -91
  52. data/test/unit/node_collection_test.rb +33 -30
  53. data/test/unit/state_collection_test.rb +112 -5
  54. data/test/unit/state_test.rb +23 -3
  55. data/test/unit/transition_test.rb +750 -89
  56. metadata +28 -3
@@ -39,6 +39,10 @@ class MachineByDefaultTest < Test::Unit::TestCase
39
39
  assert_nil @machine.action
40
40
  end
41
41
 
42
+ def test_should_use_tranactions
43
+ assert_equal true, @machine.use_transactions
44
+ end
45
+
42
46
  def test_should_not_have_a_namespace
43
47
  assert_nil @machine.namespace
44
48
  end
@@ -79,6 +83,30 @@ class MachineByDefaultTest < Test::Unit::TestCase
79
83
  assert @object.respond_to?(:state_name)
80
84
  end
81
85
 
86
+ def test_should_define_an_event_reader_for_the_attribute
87
+ assert @object.respond_to?(:state_events)
88
+ end
89
+
90
+ def test_should_define_a_transition_reader_for_the_attribute
91
+ assert @object.respond_to?(:state_transitions)
92
+ end
93
+
94
+ def test_should_not_define_an_event_attribute_reader
95
+ assert !@object.respond_to?(:state_event)
96
+ end
97
+
98
+ def test_should_not_define_an_event_attribute_writer
99
+ assert !@object.respond_to?(:state_event=)
100
+ end
101
+
102
+ def test_should_not_define_an_event_transition_attribute_reader
103
+ assert !@object.respond_to?(:state_event_transition)
104
+ end
105
+
106
+ def test_should_not_define_an_event_transition_attribute_writer
107
+ assert !@object.respond_to?(:state_event_transition=)
108
+ end
109
+
82
110
  def test_should_not_define_singular_with_scope
83
111
  assert !@klass.respond_to?(:with_state)
84
112
  end
@@ -135,6 +163,14 @@ class MachineWithCustomAttributeTest < Test::Unit::TestCase
135
163
  def test_should_define_a_name_reader_for_the_attribute
136
164
  assert @object.respond_to?(:status_name)
137
165
  end
166
+
167
+ def test_should_define_an_event_reader_for_the_attribute
168
+ assert @object.respond_to?(:status_events)
169
+ end
170
+
171
+ def test_should_define_a_transition_reader_for_the_attribute
172
+ assert @object.respond_to?(:status_transitions)
173
+ end
138
174
  end
139
175
 
140
176
  class MachineWithStaticInitialStateTest < Test::Unit::TestCase
@@ -218,9 +254,8 @@ end
218
254
  class MachineWithNilActionTest < Test::Unit::TestCase
219
255
  def setup
220
256
  integration = Module.new do
221
- def default_action
222
- :save
223
- end
257
+ class << self; attr_reader :defaults; end
258
+ @defaults = {:action => :save}
224
259
  end
225
260
  StateMachine::Integrations.const_set('Custom', integration)
226
261
  @machine = StateMachine::Machine.new(Class.new, :action => nil, :integration => :custom)
@@ -252,7 +287,7 @@ class MachineWithoutIntegrationTest < Test::Unit::TestCase
252
287
  end
253
288
 
254
289
  def test_invalidation_should_do_nothing
255
- assert_nil @machine.invalidate(@object, StateMachine::Event.new(@machine, :park))
290
+ assert_nil @machine.invalidate(@object, :state, :invalid_transition, [[:event, :park]])
256
291
  end
257
292
 
258
293
  def test_reset_should_do_nothing
@@ -277,37 +312,37 @@ end
277
312
 
278
313
  class MachineWithIntegrationTest < Test::Unit::TestCase
279
314
  def setup
280
- @integration = Module.new do
281
- class << self; attr_accessor :initialized, :with_scopes, :without_scopes; end
282
- @initialized = false
283
- @with_scopes = []
284
- @without_scopes = []
315
+ StateMachine::Integrations.const_set('Custom', Module.new do
316
+ class << self; attr_reader :defaults; end
317
+ @defaults = {:action => :save, :use_transactions => false}
318
+
319
+ attr_reader :initialized, :with_scopes, :without_scopes, :ran_transaction
285
320
 
286
321
  def after_initialize
287
- StateMachine::Integrations::Custom.initialized = true
288
- end
289
-
290
- def default_action
291
- :save
322
+ @initialized = true
292
323
  end
293
324
 
294
325
  def create_with_scope(name)
295
- StateMachine::Integrations::Custom.with_scopes << name
326
+ (@with_scopes ||= []) << name
296
327
  lambda {}
297
328
  end
298
329
 
299
330
  def create_without_scope(name)
300
- StateMachine::Integrations::Custom.without_scopes << name
331
+ (@without_scopes ||= []) << name
301
332
  lambda {}
302
333
  end
303
- end
334
+
335
+ def transaction(object)
336
+ @ran_transaction = true
337
+ yield
338
+ end
339
+ end)
304
340
 
305
- StateMachine::Integrations.const_set('Custom', @integration)
306
341
  @machine = StateMachine::Machine.new(Class.new, :integration => :custom)
307
342
  end
308
343
 
309
344
  def test_should_call_after_initialize_hook
310
- assert @integration.initialized
345
+ assert @machine.initialized
311
346
  end
312
347
 
313
348
  def test_should_use_the_default_action
@@ -319,12 +354,21 @@ class MachineWithIntegrationTest < Test::Unit::TestCase
319
354
  assert_equal :save!, machine.action
320
355
  end
321
356
 
357
+ def test_should_use_the_default_use_transactions
358
+ assert_equal false, @machine.use_transactions
359
+ end
360
+
361
+ def test_should_use_the_custom_use_transactions_if_specified
362
+ machine = StateMachine::Machine.new(Class.new, :integration => :custom, :use_transactions => true)
363
+ assert_equal true, machine.use_transactions
364
+ end
365
+
322
366
  def test_should_define_a_singular_and_plural_with_scope
323
- assert_equal %w(with_state with_states), @integration.with_scopes
367
+ assert_equal %w(with_state with_states), @machine.with_scopes
324
368
  end
325
369
 
326
370
  def test_should_define_a_singular_and_plural_without_scope
327
- assert_equal %w(without_state without_states), @integration.without_scopes
371
+ assert_equal %w(without_state without_states), @machine.without_scopes
328
372
  end
329
373
 
330
374
  def teardown
@@ -332,6 +376,62 @@ class MachineWithIntegrationTest < Test::Unit::TestCase
332
376
  end
333
377
  end
334
378
 
379
+ class MachineWithActionTest < Test::Unit::TestCase
380
+ def setup
381
+ @klass = Class.new do
382
+ def save
383
+ end
384
+ end
385
+
386
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
387
+ @object = @klass.new
388
+ end
389
+
390
+ def test_should_define_an_event_attribute_reader
391
+ assert @object.respond_to?(:state_event)
392
+ end
393
+
394
+ def test_should_define_an_event_attribute_writer
395
+ assert @object.respond_to?(:state_event=)
396
+ end
397
+
398
+ def test_should_define_an_event_transition_attribute_reader
399
+ assert @object.respond_to?(:state_event_transition)
400
+ end
401
+
402
+ def test_should_define_an_event_transition_attribute_writer
403
+ assert @object.respond_to?(:state_event_transition=)
404
+ end
405
+ end
406
+
407
+ class MachineWithActionUndefinedTest < Test::Unit::TestCase
408
+ def setup
409
+ @klass = Class.new
410
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
411
+ @object = @klass.new
412
+ end
413
+
414
+ def test_should_define_an_event_attribute_reader
415
+ assert @object.respond_to?(:state_event)
416
+ end
417
+
418
+ def test_should_define_an_event_attribute_writer
419
+ assert @object.respond_to?(:state_event=)
420
+ end
421
+
422
+ def test_should_define_an_event_transition_attribute_reader
423
+ assert @object.respond_to?(:state_event_transition)
424
+ end
425
+
426
+ def test_should_define_an_event_transition_attribute_writer
427
+ assert @object.respond_to?(:state_event_transition=)
428
+ end
429
+
430
+ def test_should_not_define_action
431
+ assert !@object.respond_to?(:save)
432
+ end
433
+ end
434
+
335
435
  class MachineWithCustomPluralTest < Test::Unit::TestCase
336
436
  def setup
337
437
  @integration = Module.new do
@@ -370,8 +470,8 @@ end
370
470
  class MachineWithCustomInvalidationTest < Test::Unit::TestCase
371
471
  def setup
372
472
  @integration = Module.new do
373
- def invalidate(object, event)
374
- object.error = invalid_message(object, event)
473
+ def invalidate(object, attribute, message, values = [])
474
+ object.error = generate_message(message, values)
375
475
  end
376
476
  end
377
477
  StateMachine::Integrations.const_set('Custom', @integration)
@@ -380,7 +480,7 @@ class MachineWithCustomInvalidationTest < Test::Unit::TestCase
380
480
  attr_accessor :error
381
481
  end
382
482
 
383
- @machine = StateMachine::Machine.new(@klass, :integration => :custom, :invalid_message => 'cannot %s when %s')
483
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom, :messages => {:invalid_transition => 'cannot %s'})
384
484
  @machine.state :parked
385
485
 
386
486
  @object = @klass.new
@@ -388,8 +488,8 @@ class MachineWithCustomInvalidationTest < Test::Unit::TestCase
388
488
  end
389
489
 
390
490
  def test_use_custom_message
391
- @machine.invalidate(@object, StateMachine::Event.new(@machine, :park))
392
- assert_equal 'cannot park when parked', @object.error
491
+ @machine.invalidate(@object, :state, :invalid_transition, [[:event, :park]])
492
+ assert_equal 'cannot park', @object.error
393
493
  end
394
494
 
395
495
  def teardown
@@ -402,8 +502,8 @@ class MachineTest < Test::Unit::TestCase
402
502
  assert_raise(ArgumentError) {StateMachine::Machine.new(Class.new, :invalid => true)}
403
503
  end
404
504
 
405
- def test_should_not_raise_exception_if_custom_invalid_message_specified
406
- assert_nothing_raised {StateMachine::Machine.new(Class.new, :invalid_message => 'custom')}
505
+ def test_should_not_raise_exception_if_custom_messages_specified
506
+ assert_nothing_raised {StateMachine::Machine.new(Class.new, :messages => {:invalid_transition => 'custom'})}
407
507
  end
408
508
 
409
509
  def test_should_evaluate_a_block_during_initialization
@@ -667,6 +767,14 @@ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
667
767
  def state_name
668
768
  :parked
669
769
  end
770
+
771
+ def state_events
772
+ [:ignite]
773
+ end
774
+
775
+ def state_transitions
776
+ [{:parked => :idling}]
777
+ end
670
778
  end
671
779
 
672
780
  StateMachine::Integrations.const_set('Custom', Module.new do
@@ -717,6 +825,14 @@ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
717
825
  assert_equal :parked, @object.state_name
718
826
  end
719
827
 
828
+ def test_should_not_redefine_attribute_events_reader
829
+ assert_equal [:ignite], @object.state_events
830
+ end
831
+
832
+ def test_should_not_redefine_attribute_transitions_reader
833
+ assert_equal [{:parked => :idling}], @object.state_transitions
834
+ end
835
+
720
836
  def test_should_allow_super_chaining
721
837
  @klass.class_eval do
722
838
  def self.with_state(*states)
@@ -753,6 +869,14 @@ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
753
869
  def state_name
754
870
  super == :parked ? 1 : 0
755
871
  end
872
+
873
+ def state_events
874
+ super == []
875
+ end
876
+
877
+ def state_transitions
878
+ super == []
879
+ end
756
880
  end
757
881
 
758
882
  assert_equal true, @klass.with_state
@@ -765,6 +889,8 @@ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
765
889
  assert_equal 'idling', @object.status
766
890
  assert_equal 0, @object.state?(:parked)
767
891
  assert_equal 0, @object.state_name
892
+ assert_equal true, @object.state_events
893
+ assert_equal true, @object.state_transitions
768
894
  end
769
895
 
770
896
  def teardown
@@ -860,6 +986,23 @@ class MachineWithCustomInitializeTest < Test::Unit::TestCase
860
986
  end
861
987
  end
862
988
 
989
+ class MachinePersistenceTest < Test::Unit::TestCase
990
+ def setup
991
+ @klass = Class.new
992
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
993
+ @object = @klass.new
994
+ end
995
+
996
+ def test_should_allow_reading_state
997
+ assert_equal 'parked', @machine.read(@object)
998
+ end
999
+
1000
+ def test_should_allow_writing_state
1001
+ @machine.write(@object, 'idling')
1002
+ assert_equal 'idling', @object.state
1003
+ end
1004
+ end
1005
+
863
1006
  class MachineWithStatesTest < Test::Unit::TestCase
864
1007
  def setup
865
1008
  @klass = Class.new
@@ -893,30 +1036,6 @@ class MachineWithStatesTest < Test::Unit::TestCase
893
1036
  exception = assert_raise(ArgumentError) {@machine.state(:first_gear, :invalid => true)}
894
1037
  assert_equal 'Invalid key(s): invalid', exception.message
895
1038
  end
896
-
897
- def test_should_not_be_in_state_if_value_does_not_match
898
- assert !@machine.state?(@object, :parked)
899
- assert !@machine.state?(@object, :idling)
900
- end
901
-
902
- def test_should_be_in_state_if_value_matches
903
- assert @machine.state?(@object, nil)
904
- end
905
-
906
- def test_raise_exception_if_checking_invalid_state
907
- assert_raise(ArgumentError) { @machine.state?(@object, :invalid) }
908
- end
909
-
910
- def test_should_find_state_for_object_if_value_is_known
911
- @object.state = 'parked'
912
- assert_equal @parked, @machine.state_for(@object)
913
- end
914
-
915
- def test_should_raise_exception_if_finding_state_for_object_with_unknown_value
916
- @object.state = 'invalid'
917
- exception = assert_raise(ArgumentError) { @machine.state_for(@object) }
918
- assert_equal '"invalid" is not a known state value', exception.message
919
- end
920
1039
  end
921
1040
 
922
1041
  class MachineWithStatesWithCustomValuesTest < Test::Unit::TestCase
@@ -936,19 +1055,6 @@ class MachineWithStatesWithCustomValuesTest < Test::Unit::TestCase
936
1055
  def test_should_allow_lookup_by_custom_value
937
1056
  assert_equal @state, @machine.states[1, :value]
938
1057
  end
939
-
940
- def test_should_be_in_state_if_value_matches
941
- assert @machine.state?(@object, :parked)
942
- end
943
-
944
- def test_should_not_be_in_state_if_value_does_not_match
945
- @object.state = 2
946
- assert !@machine.state?(@object, :parked)
947
- end
948
-
949
- def test_should_find_state_for_object_if_value_is_known
950
- assert_equal @state, @machine.state_for(@object)
951
- end
952
1058
  end
953
1059
 
954
1060
  class MachineWithStateWithMatchersTest < Test::Unit::TestCase
@@ -966,19 +1072,6 @@ class MachineWithStateWithMatchersTest < Test::Unit::TestCase
966
1072
  assert @state.matches?(1)
967
1073
  assert !@state.matches?(nil)
968
1074
  end
969
-
970
- def test_should_be_in_state_if_value_matches
971
- assert @machine.state?(@object, :parked)
972
- end
973
-
974
- def test_should_not_be_in_state_if_value_does_not_match
975
- @object.state = nil
976
- assert !@machine.state?(@object, :parked)
977
- end
978
-
979
- def test_should_find_state_for_object_if_value_is_known
980
- assert_equal @state, @machine.state_for(@object)
981
- end
982
1075
  end
983
1076
 
984
1077
  class MachineWithStatesWithBehaviorsTest < Test::Unit::TestCase
@@ -1050,7 +1143,8 @@ end
1050
1143
 
1051
1144
  class MachineWithEventsTest < Test::Unit::TestCase
1052
1145
  def setup
1053
- @machine = StateMachine::Machine.new(Class.new)
1146
+ @klass = Class.new
1147
+ @machine = StateMachine::Machine.new(@klass)
1054
1148
  end
1055
1149
 
1056
1150
  def test_should_return_the_created_event
@@ -1309,44 +1403,44 @@ end
1309
1403
  class MachineWithNamespaceTest < Test::Unit::TestCase
1310
1404
  def setup
1311
1405
  @klass = Class.new
1312
- @machine = StateMachine::Machine.new(@klass, :namespace => 'car', :initial => :parked) do
1313
- event :ignite do
1314
- transition :parked => :idling
1406
+ @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm', :initial => :active) do
1407
+ event :enable do
1408
+ transition :off => :active
1315
1409
  end
1316
1410
 
1317
- event :park do
1318
- transition :idling => :parked
1411
+ event :disable do
1412
+ transition :active => :off
1319
1413
  end
1320
1414
  end
1321
1415
  @object = @klass.new
1322
1416
  end
1323
1417
 
1324
1418
  def test_should_namespace_state_predicates
1325
- [:car_parked?, :car_idling?].each do |name|
1419
+ [:alarm_active?, :alarm_off?].each do |name|
1326
1420
  assert @object.respond_to?(name)
1327
1421
  end
1328
1422
  end
1329
1423
 
1330
1424
  def test_should_namespace_event_checks
1331
- [:can_ignite_car?, :can_park_car?].each do |name|
1425
+ [:can_enable_alarm?, :can_disable_alarm?].each do |name|
1332
1426
  assert @object.respond_to?(name)
1333
1427
  end
1334
1428
  end
1335
1429
 
1336
1430
  def test_should_namespace_event_transition_readers
1337
- [:next_ignite_car_transition, :next_park_car_transition].each do |name|
1431
+ [:enable_alarm_transition, :disable_alarm_transition].each do |name|
1338
1432
  assert @object.respond_to?(name)
1339
1433
  end
1340
1434
  end
1341
1435
 
1342
1436
  def test_should_namespace_events
1343
- [:ignite_car, :park_car].each do |name|
1437
+ [:enable_alarm, :disable_alarm].each do |name|
1344
1438
  assert @object.respond_to?(name)
1345
1439
  end
1346
1440
  end
1347
1441
 
1348
1442
  def test_should_namespace_bang_events
1349
- [:ignite_car!, :park_car!].each do |name|
1443
+ [:enable_alarm!, :disable_alarm!].each do |name|
1350
1444
  assert @object.respond_to?(name)
1351
1445
  end
1352
1446
  end
@@ -1413,7 +1507,7 @@ class MachineFinderWithExistingMachineOnSuperclassTest < Test::Unit::TestCase
1413
1507
  @base_machine.after_transition(lambda {})
1414
1508
 
1415
1509
  @klass = Class.new(@base_class)
1416
- @machine = StateMachine::Machine.find_or_create(@klass, :status)
1510
+ @machine = StateMachine::Machine.find_or_create(@klass, :status) {}
1417
1511
  end
1418
1512
 
1419
1513
  def test_should_accept_a_block
@@ -1425,7 +1519,20 @@ class MachineFinderWithExistingMachineOnSuperclassTest < Test::Unit::TestCase
1425
1519
  assert called
1426
1520
  end
1427
1521
 
1428
- def test_should_create_a_new_machine
1522
+ def test_should_not_create_a_new_machine_if_no_block_or_options
1523
+ machine = StateMachine::Machine.find_or_create(Class.new(@base_class), :status)
1524
+
1525
+ assert_same machine, @base_machine
1526
+ end
1527
+
1528
+ def test_should_create_a_new_machine_if_given_options
1529
+ machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked)
1530
+
1531
+ assert_not_nil machine
1532
+ assert_not_same machine, @base_machine
1533
+ end
1534
+
1535
+ def test_should_create_a_new_machine_if_given_block
1429
1536
  assert_not_nil @machine
1430
1537
  assert_not_same @machine, @base_machine
1431
1538
  end