state_machine 0.9.4 → 0.10.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 (68) hide show
  1. data/CHANGELOG.rdoc +20 -0
  2. data/LICENSE +1 -1
  3. data/README.rdoc +74 -4
  4. data/Rakefile +3 -3
  5. data/lib/state_machine.rb +51 -24
  6. data/lib/state_machine/{guard.rb → branch.rb} +34 -40
  7. data/lib/state_machine/callback.rb +13 -18
  8. data/lib/state_machine/error.rb +13 -0
  9. data/lib/state_machine/eval_helpers.rb +3 -0
  10. data/lib/state_machine/event.rb +67 -30
  11. data/lib/state_machine/event_collection.rb +20 -3
  12. data/lib/state_machine/extensions.rb +3 -3
  13. data/lib/state_machine/integrations.rb +7 -0
  14. data/lib/state_machine/integrations/active_model.rb +149 -59
  15. data/lib/state_machine/integrations/active_model/versions.rb +30 -0
  16. data/lib/state_machine/integrations/active_record.rb +74 -148
  17. data/lib/state_machine/integrations/active_record/locale.rb +0 -7
  18. data/lib/state_machine/integrations/active_record/versions.rb +149 -0
  19. data/lib/state_machine/integrations/base.rb +64 -0
  20. data/lib/state_machine/integrations/data_mapper.rb +50 -39
  21. data/lib/state_machine/integrations/data_mapper/observer.rb +47 -12
  22. data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
  23. data/lib/state_machine/integrations/mongo_mapper.rb +37 -64
  24. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  25. data/lib/state_machine/integrations/mongo_mapper/versions.rb +102 -0
  26. data/lib/state_machine/integrations/mongoid.rb +297 -0
  27. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  28. data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
  29. data/lib/state_machine/integrations/sequel.rb +99 -55
  30. data/lib/state_machine/integrations/sequel/versions.rb +40 -0
  31. data/lib/state_machine/machine.rb +273 -136
  32. data/lib/state_machine/machine_collection.rb +21 -13
  33. data/lib/state_machine/node_collection.rb +6 -1
  34. data/lib/state_machine/path.rb +120 -0
  35. data/lib/state_machine/path_collection.rb +90 -0
  36. data/lib/state_machine/state.rb +28 -9
  37. data/lib/state_machine/state_collection.rb +1 -1
  38. data/lib/state_machine/transition.rb +65 -6
  39. data/lib/state_machine/transition_collection.rb +1 -1
  40. data/test/files/en.yml +8 -0
  41. data/test/functional/state_machine_test.rb +15 -2
  42. data/test/unit/branch_test.rb +890 -0
  43. data/test/unit/callback_test.rb +9 -36
  44. data/test/unit/error_test.rb +43 -0
  45. data/test/unit/event_collection_test.rb +67 -33
  46. data/test/unit/event_test.rb +165 -38
  47. data/test/unit/integrations/active_model_test.rb +103 -3
  48. data/test/unit/integrations/active_record_test.rb +90 -43
  49. data/test/unit/integrations/base_test.rb +87 -0
  50. data/test/unit/integrations/data_mapper_test.rb +105 -44
  51. data/test/unit/integrations/mongo_mapper_test.rb +261 -64
  52. data/test/unit/integrations/mongoid_test.rb +1529 -0
  53. data/test/unit/integrations/sequel_test.rb +33 -49
  54. data/test/unit/integrations_test.rb +4 -0
  55. data/test/unit/invalid_event_test.rb +15 -2
  56. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  57. data/test/unit/invalid_transition_test.rb +72 -2
  58. data/test/unit/machine_collection_test.rb +55 -61
  59. data/test/unit/machine_test.rb +388 -26
  60. data/test/unit/node_collection_test.rb +14 -4
  61. data/test/unit/path_collection_test.rb +266 -0
  62. data/test/unit/path_test.rb +485 -0
  63. data/test/unit/state_collection_test.rb +30 -0
  64. data/test/unit/state_test.rb +82 -35
  65. data/test/unit/transition_collection_test.rb +48 -44
  66. data/test/unit/transition_test.rb +198 -41
  67. metadata +111 -74
  68. data/test/unit/guard_test.rb +0 -909
@@ -43,6 +43,10 @@ class MachineByDefaultTest < Test::Unit::TestCase
43
43
  assert @machine.callbacks[:after].empty?
44
44
  end
45
45
 
46
+ def test_should_not_have_any_failure_callbacks
47
+ assert @machine.callbacks[:failure].empty?
48
+ end
49
+
46
50
  def test_should_not_have_an_action
47
51
  assert_nil @machine.action
48
52
  end
@@ -69,6 +73,10 @@ class MachineByDefaultTest < Test::Unit::TestCase
69
73
  assert_equal 'cannot transition via "park"', @machine.generate_message(:invalid_transition, [[:event, :park]])
70
74
  end
71
75
 
76
+ def test_should_not_be_extended_by_the_base_integration
77
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::Base)
78
+ end
79
+
72
80
  def test_should_not_be_extended_by_the_active_model_integration
73
81
  assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::ActiveModel)
74
82
  end
@@ -113,6 +121,10 @@ class MachineByDefaultTest < Test::Unit::TestCase
113
121
  assert @object.respond_to?(:state_transitions)
114
122
  end
115
123
 
124
+ def test_should_define_a_path_reader_for_the_attribute
125
+ assert @object.respond_to?(:state_paths)
126
+ end
127
+
116
128
  def test_should_not_define_an_event_attribute_reader
117
129
  assert !@object.respond_to?(:state_event)
118
130
  end
@@ -219,6 +231,37 @@ class MachineWithCustomNameTest < Test::Unit::TestCase
219
231
  end
220
232
  end
221
233
 
234
+ class MachineWithoutInitializationTest < Test::Unit::TestCase
235
+ def setup
236
+ @klass = Class.new do
237
+ def initialize(attributes = {})
238
+ attributes.each {|attr, value| send("#{attr}=", value)}
239
+ super()
240
+ end
241
+ end
242
+
243
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked, :initialize => false)
244
+ end
245
+
246
+ def test_should_not_have_an_initial_state
247
+ object = @klass.new
248
+ assert_nil object.state
249
+ end
250
+
251
+ def test_should_still_allow_manual_initialization
252
+ @klass.class_eval do
253
+ def initialize(attributes = {})
254
+ attributes.each {|attr, value| send("#{attr}=", value)}
255
+ super()
256
+ initialize_state_machines
257
+ end
258
+ end
259
+
260
+ object = @klass.new
261
+ assert_equal 'parked', object.state
262
+ end
263
+ end
264
+
222
265
  class MachineWithStaticInitialStateTest < Test::Unit::TestCase
223
266
  def setup
224
267
  @klass = Class.new do
@@ -337,6 +380,42 @@ class MachineWithDynamicInitialStateTest < Test::Unit::TestCase
337
380
  end
338
381
  end
339
382
 
383
+ class MachineStateInitializationTest < Test::Unit::TestCase
384
+ def setup
385
+ @klass = Class.new
386
+ @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked)
387
+
388
+ # Prevent the auto-initialization hook from firing
389
+ @klass.class_eval do
390
+ def initialize
391
+ end
392
+ end
393
+
394
+ @object = @klass.new
395
+ @object.state = nil
396
+ end
397
+
398
+ def test_should_set_states_if_nil
399
+ @machine.initialize_state(@object)
400
+
401
+ assert_equal 'parked', @object.state
402
+ end
403
+
404
+ def test_should_set_states_if_empty
405
+ @object.state = ''
406
+ @machine.initialize_state(@object)
407
+
408
+ assert_equal 'parked', @object.state
409
+ end
410
+
411
+ def test_should_not_set_states_if_not_empty
412
+ @object.state = 'idling'
413
+ @machine.initialize_state(@object)
414
+
415
+ assert_equal 'idling', @object.state
416
+ end
417
+ end
418
+
340
419
  class MachineWithCustomActionTest < Test::Unit::TestCase
341
420
  def setup
342
421
  @machine = StateMachine::Machine.new(Class.new, :action => :save)
@@ -350,7 +429,8 @@ end
350
429
  class MachineWithNilActionTest < Test::Unit::TestCase
351
430
  def setup
352
431
  integration = Module.new do
353
- class << self; attr_reader :defaults; end
432
+ include StateMachine::Integrations::Base
433
+
354
434
  @defaults = {:action => :save}
355
435
  end
356
436
  StateMachine::Integrations.const_set('Custom', integration)
@@ -394,6 +474,8 @@ end
394
474
  class MachineWithCustomIntegrationTest < Test::Unit::TestCase
395
475
  def setup
396
476
  integration = Module.new do
477
+ include StateMachine::Integrations::Base
478
+
397
479
  def self.matches?(klass)
398
480
  true
399
481
  end
@@ -430,7 +512,8 @@ end
430
512
  class MachineWithIntegrationTest < Test::Unit::TestCase
431
513
  def setup
432
514
  StateMachine::Integrations.const_set('Custom', Module.new do
433
- class << self; attr_reader :defaults; end
515
+ include StateMachine::Integrations::Base
516
+
434
517
  @defaults = {:action => :save, :use_transactions => false}
435
518
 
436
519
  attr_reader :initialized, :with_scopes, :without_scopes, :ran_transaction
@@ -520,8 +603,8 @@ class MachineWithActionUndefinedTest < Test::Unit::TestCase
520
603
  assert !@object.respond_to?(:save)
521
604
  end
522
605
 
523
- def test_should_not_mark_action_helper_as_defined
524
- assert !@machine.action_helper_defined?
606
+ def test_should_not_mark_action_hook_as_defined
607
+ assert !@machine.action_hook?
525
608
  end
526
609
  end
527
610
 
@@ -556,8 +639,8 @@ class MachineWithActionDefinedInClassTest < Test::Unit::TestCase
556
639
  assert !@klass.ancestors.any? {|ancestor| ancestor != @klass && ancestor.method_defined?(:save)}
557
640
  end
558
641
 
559
- def test_should_not_mark_action_helper_as_defined
560
- assert !@machine.action_helper_defined?
642
+ def test_should_not_mark_action_hook_as_defined
643
+ assert !@machine.action_hook?
561
644
  end
562
645
  end
563
646
 
@@ -600,8 +683,8 @@ class MachineWithActionDefinedInIncludedModuleTest < Test::Unit::TestCase
600
683
  assert @klass.public_method_defined?(:save)
601
684
  end
602
685
 
603
- def test_should_mark_action_helper_as_defined
604
- assert @machine.action_helper_defined?
686
+ def test_should_mark_action_hook_as_defined
687
+ assert @machine.action_hook?
605
688
  end
606
689
  end
607
690
 
@@ -641,8 +724,8 @@ class MachineWithActionDefinedInSuperclassTest < Test::Unit::TestCase
641
724
  assert @klass.public_method_defined?(:save)
642
725
  end
643
726
 
644
- def test_should_mark_action_helper_as_defined
645
- assert @machine.action_helper_defined?
727
+ def test_should_mark_action_hook_as_defined
728
+ assert @machine.action_hook?
646
729
  end
647
730
  end
648
731
 
@@ -683,8 +766,8 @@ class MachineWithPrivateActionTest < Test::Unit::TestCase
683
766
  assert @klass.private_method_defined?(:save)
684
767
  end
685
768
 
686
- def test_should_mark_action_helper_as_defined
687
- assert @machine.action_helper_defined?
769
+ def test_should_mark_action_hook_as_defined
770
+ assert @machine.action_hook?
688
771
  end
689
772
  end
690
773
 
@@ -705,14 +788,16 @@ class MachineWithActionAlreadyOverriddenTest < Test::Unit::TestCase
705
788
  assert_equal 1, @klass.ancestors.select {|ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.method_defined?(:save)}.length
706
789
  end
707
790
 
708
- def test_should_mark_action_helper_as_defined
709
- assert @machine.action_helper_defined?
791
+ def test_should_mark_action_hook_as_defined
792
+ assert @machine.action_hook?
710
793
  end
711
794
  end
712
795
 
713
796
  class MachineWithCustomPluralTest < Test::Unit::TestCase
714
797
  def setup
715
798
  @integration = Module.new do
799
+ include StateMachine::Integrations::Base
800
+
716
801
  class << self; attr_accessor :with_scopes, :without_scopes; end
717
802
  @with_scopes = []
718
803
  @without_scopes = []
@@ -748,6 +833,8 @@ end
748
833
  class MachineWithCustomInvalidationTest < Test::Unit::TestCase
749
834
  def setup
750
835
  @integration = Module.new do
836
+ include StateMachine::Integrations::Base
837
+
751
838
  def invalidate(object, attribute, message, values = [])
752
839
  object.error = generate_message(message, values)
753
840
  end
@@ -815,6 +902,7 @@ class MachineAfterBeingCopiedTest < Test::Unit::TestCase
815
902
  @machine.before_transition(lambda {})
816
903
  @machine.after_transition(lambda {})
817
904
  @machine.around_transition(lambda {})
905
+ @machine.after_failure(lambda {})
818
906
 
819
907
  @copied_machine = @machine.clone
820
908
  end
@@ -862,6 +950,10 @@ class MachineAfterBeingCopiedTest < Test::Unit::TestCase
862
950
  def test_should_not_have_the_same_after_callbacks
863
951
  assert_not_same @copied_machine.callbacks[:after], @machine.callbacks[:after]
864
952
  end
953
+
954
+ def test_should_not_have_the_same_failure_callbacks
955
+ assert_not_same @copied_machine.callbacks[:failure], @machine.callbacks[:failure]
956
+ end
865
957
  end
866
958
 
867
959
  class MachineAfterChangingOwnerClassTest < Test::Unit::TestCase
@@ -919,6 +1011,23 @@ class MachineAfterChangingInitialState < Test::Unit::TestCase
919
1011
  end
920
1012
  end
921
1013
 
1014
+ class MachineWithHelpersTest < Test::Unit::TestCase
1015
+ def setup
1016
+ @klass = Class.new
1017
+ @machine = StateMachine::Machine.new(@klass)
1018
+ @object = @klass.new
1019
+ end
1020
+
1021
+ def test_should_throw_exception_with_invalid_scope
1022
+ assert_raise(RUBY_VERSION < '1.9' ? IndexError : KeyError) { @machine.define_helper(:invalid, :state) {} }
1023
+ end
1024
+
1025
+ def test_should_throw_exception_if_calling_helper_directly_with_invalid_scope
1026
+ @machine.define_helper(:instance, :state) {}
1027
+ assert_raise(RUBY_VERSION < '1.9' ? IndexError : KeyError) { @machine.call_helper(:invalid, :state, lambda {}, @object) }
1028
+ end
1029
+ end
1030
+
922
1031
  class MachineWithInstanceHelpersTest < Test::Unit::TestCase
923
1032
  def setup
924
1033
  @klass = Class.new
@@ -933,7 +1042,7 @@ class MachineWithInstanceHelpersTest < Test::Unit::TestCase
933
1042
  end
934
1043
  end
935
1044
 
936
- @machine.define_instance_method(:state) {}
1045
+ @machine.define_helper(:instance, :state) {}
937
1046
  assert_equal 'parked', @object.state
938
1047
  end
939
1048
 
@@ -945,7 +1054,7 @@ class MachineWithInstanceHelpersTest < Test::Unit::TestCase
945
1054
  end
946
1055
  end
947
1056
 
948
- @machine.define_instance_method(:state) {}
1057
+ @machine.define_helper(:instance, :state) {}
949
1058
  assert_equal 'parked', @object.send(:state)
950
1059
  end
951
1060
 
@@ -957,14 +1066,75 @@ class MachineWithInstanceHelpersTest < Test::Unit::TestCase
957
1066
  end
958
1067
  end
959
1068
 
960
- @machine.define_instance_method(:state) {}
1069
+ @machine.define_helper(:instance, :state) {}
961
1070
  assert_equal 'parked', @object.send(:state)
962
1071
  end
963
1072
 
964
1073
  def test_should_define_nonexistent_methods
965
- @machine.define_instance_method(:state) {'parked'}
1074
+ @machine.define_helper(:instance, :state) {'parked'}
966
1075
  assert_equal 'parked', @object.state
967
1076
  end
1077
+
1078
+ def test_should_pass_context_as_arguments
1079
+ helper_args = nil
1080
+ @machine.define_helper(:instance, :state) {|*args| helper_args = args}
1081
+ @object.state
1082
+ assert_equal 3, helper_args.length
1083
+ assert_equal [@machine, @object], helper_args[0..1]
1084
+ end
1085
+
1086
+ def test_should_pass_method_arguments_through
1087
+ helper_args = nil
1088
+ @machine.define_helper(:instance, :state) {|*args| helper_args = args}
1089
+ @object.state(1, 2, 3)
1090
+ assert_equal 6, helper_args.length
1091
+ assert_equal [@machine, @object], helper_args[0..1]
1092
+ assert_equal [1, 2, 3], helper_args[3..5]
1093
+ end
1094
+
1095
+ def test_should_allow_super_calls
1096
+ @klass = Class.new
1097
+ @klass.class_eval do
1098
+ include(Module.new {
1099
+ def state
1100
+ 'original'
1101
+ end
1102
+ })
1103
+ end
1104
+ @machine = StateMachine::Machine.new(@klass)
1105
+ @object = @klass.new
1106
+
1107
+ @machine.define_helper(:instance, :state) {|machine, object, _super| _super.call}
1108
+ assert_equal 'original', @object.state
1109
+ end
1110
+
1111
+ def test_should_allow_rewrite_of_super_args
1112
+ @klass = Class.new
1113
+ @klass.class_eval do
1114
+ include(Module.new {
1115
+ def state(value)
1116
+ value
1117
+ end
1118
+ })
1119
+ end
1120
+ @machine = StateMachine::Machine.new(@klass)
1121
+ @object = @klass.new
1122
+
1123
+ @machine.define_helper(:instance, :state) {|machine, object, _super, *args| _super.call('override')}
1124
+ assert_equal 'override', @object.state(1)
1125
+ end
1126
+
1127
+ def test_should_throw_exception_if_calling_helper_directly_with_invalid_method
1128
+ assert_raise(RUBY_VERSION < '1.9' ? IndexError : KeyError) { @machine.call_helper(:instance, :invalid, @object, lambda {}) }
1129
+ end
1130
+
1131
+ def test_should_be_able_to_call_helper_directly
1132
+ helper_args = nil
1133
+ @machine.define_helper(:instance, :state) {|*args| helper_args = args}
1134
+
1135
+ @machine.call_helper(:instance, :state, @object, _super = lambda {}, 1, 2, 3)
1136
+ assert_equal [@machine, @object, _super, 1, 2, 3], helper_args
1137
+ end
968
1138
  end
969
1139
 
970
1140
  class MachineWithClassHelpersTest < Test::Unit::TestCase
@@ -980,7 +1150,7 @@ class MachineWithClassHelpersTest < Test::Unit::TestCase
980
1150
  end
981
1151
  end
982
1152
 
983
- @machine.define_class_method(:states) {}
1153
+ @machine.define_helper(:class, :states) {}
984
1154
  assert_equal [], @klass.states
985
1155
  end
986
1156
 
@@ -992,7 +1162,7 @@ class MachineWithClassHelpersTest < Test::Unit::TestCase
992
1162
  end
993
1163
  end
994
1164
 
995
- @machine.define_class_method(:states) {}
1165
+ @machine.define_helper(:class, :states) {}
996
1166
  assert_equal [], @klass.send(:states)
997
1167
  end
998
1168
 
@@ -1004,14 +1174,73 @@ class MachineWithClassHelpersTest < Test::Unit::TestCase
1004
1174
  end
1005
1175
  end
1006
1176
 
1007
- @machine.define_class_method(:states) {}
1177
+ @machine.define_helper(:class, :states) {}
1008
1178
  assert_equal [], @klass.send(:states)
1009
1179
  end
1010
1180
 
1011
1181
  def test_should_define_nonexistent_methods
1012
- @machine.define_class_method(:states) {[]}
1182
+ @machine.define_helper(:class, :states) {[]}
1013
1183
  assert_equal [], @klass.states
1014
1184
  end
1185
+
1186
+ def test_should_pass_context_as_arguments
1187
+ helper_args = nil
1188
+ @machine.define_helper(:class, :states) {|*args| helper_args = args}
1189
+ @klass.states
1190
+ assert_equal 3, helper_args.length
1191
+ assert_equal [@machine, @klass], helper_args[0..1]
1192
+ end
1193
+
1194
+ def test_should_pass_method_arguments_through
1195
+ helper_args = nil
1196
+ @machine.define_helper(:class, :states) {|*args| helper_args = args}
1197
+ @klass.states(1, 2, 3)
1198
+ assert_equal 6, helper_args.length
1199
+ assert_equal [@machine, @klass], helper_args[0..1]
1200
+ assert_equal [1, 2, 3], helper_args[3..5]
1201
+ end
1202
+
1203
+ def test_should_allow_super_calls
1204
+ @klass = Class.new
1205
+ @klass.class_eval do
1206
+ extend(Module.new {
1207
+ def states
1208
+ 'original'
1209
+ end
1210
+ })
1211
+ end
1212
+ @machine = StateMachine::Machine.new(@klass)
1213
+
1214
+ @machine.define_helper(:class, :states) {|machine, klass, _super| _super.call}
1215
+ assert_equal 'original', @klass.states
1216
+ end
1217
+
1218
+ def test_should_allow_rewrite_of_super_args
1219
+ @klass = Class.new
1220
+ @klass.class_eval do
1221
+ extend(Module.new {
1222
+ def states(value)
1223
+ value
1224
+ end
1225
+ })
1226
+ end
1227
+ @machine = StateMachine::Machine.new(@klass)
1228
+
1229
+ @machine.define_helper(:class, :states) {|machine, klass, _super, *args| _super.call('override')}
1230
+ assert_equal 'override', @klass.states(1)
1231
+ end
1232
+
1233
+ def test_should_throw_exception_if_calling_helper_directly_with_invalid_method
1234
+ assert_raise(RUBY_VERSION < '1.9' ? IndexError : KeyError) { @machine.call_helper(:class, :invalid, @klass, lambda {}) }
1235
+ end
1236
+
1237
+ def test_should_be_able_to_call_helper_directly
1238
+ helper_args = nil
1239
+ @machine.define_helper(:class, :states) {|*args| helper_args = args}
1240
+
1241
+ @machine.call_helper(:class, :states, @klass, _super = lambda {}, 1, 2, 3)
1242
+ assert_equal [@machine, @klass, _super, 1, 2, 3], helper_args
1243
+ end
1015
1244
  end
1016
1245
 
1017
1246
  class MachineWithConflictingHelpersTest < Test::Unit::TestCase
@@ -1070,9 +1299,15 @@ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
1070
1299
  def state_transitions
1071
1300
  [{:parked => :idling}]
1072
1301
  end
1302
+
1303
+ def state_paths
1304
+ [[{:parked => :idling}]]
1305
+ end
1073
1306
  end
1074
1307
 
1075
1308
  StateMachine::Integrations.const_set('Custom', Module.new do
1309
+ include StateMachine::Integrations::Base
1310
+
1076
1311
  def create_with_scope(name)
1077
1312
  lambda {|klass, values| []}
1078
1313
  end
@@ -1141,6 +1376,10 @@ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
1141
1376
  assert_equal [{:parked => :idling}], @object.state_transitions
1142
1377
  end
1143
1378
 
1379
+ def test_should_not_redefine_attribute_paths_reader
1380
+ assert_equal [[{:parked => :idling}]], @object.state_paths
1381
+ end
1382
+
1144
1383
  def test_should_allow_super_chaining
1145
1384
  @klass.class_eval do
1146
1385
  def self.with_state(*states)
@@ -1197,6 +1436,10 @@ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
1197
1436
  def state_transitions
1198
1437
  super == []
1199
1438
  end
1439
+
1440
+ def state_paths
1441
+ super == []
1442
+ end
1200
1443
  end
1201
1444
 
1202
1445
  assert_equal true, @klass.with_state
@@ -1214,6 +1457,7 @@ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
1214
1457
  assert_equal 0, @object.human_state_name
1215
1458
  assert_equal true, @object.state_events
1216
1459
  assert_equal true, @object.state_transitions
1460
+ assert_equal true, @object.state_paths
1217
1461
  end
1218
1462
 
1219
1463
  def teardown
@@ -1348,6 +1592,16 @@ class MachinePersistenceTest < Test::Unit::TestCase
1348
1592
  @machine.write(@object, :event, 'ignite')
1349
1593
  assert_equal 'ignite', @object.state_event
1350
1594
  end
1595
+
1596
+ def test_should_allow_writing_custom_instance_variables
1597
+ @klass.class_eval do
1598
+ attr_reader :state_value
1599
+ end
1600
+
1601
+ assert_raise(NoMethodError) { @machine.write(@object, :value, 1) }
1602
+ assert_equal 1, @machine.write(@object, :value, 1, true)
1603
+ assert_equal 1, @object.state_value
1604
+ end
1351
1605
  end
1352
1606
 
1353
1607
 
@@ -1675,7 +1929,7 @@ class MachineWithMultipleEventsTest < Test::Unit::TestCase
1675
1929
  end
1676
1930
 
1677
1931
  def test_should_define_transitions_for_each_event
1678
- [@park, @shift_down].each {|event| assert_equal 1, event.guards.size}
1932
+ [@park, @shift_down].each {|event| assert_equal 1, event.branches.size}
1679
1933
  end
1680
1934
 
1681
1935
  def test_should_transition_the_same_for_each_event
@@ -1834,6 +2088,76 @@ class MachineWithTransitionCallbacksTest < Test::Unit::TestCase
1834
2088
  end
1835
2089
  end
1836
2090
 
2091
+ class MachineWithFailureCallbacksTest < Test::Unit::TestCase
2092
+ def setup
2093
+ @klass = Class.new do
2094
+ attr_accessor :callbacks
2095
+ end
2096
+
2097
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2098
+ @event = @machine.event :ignite
2099
+
2100
+ @object = @klass.new
2101
+ @object.callbacks = []
2102
+ end
2103
+
2104
+ def test_should_raise_exception_if_implicit_option_specified
2105
+ exception = assert_raise(ArgumentError) {@machine.after_failure :invalid => :valid, :do => lambda {}}
2106
+ assert_equal 'Invalid key(s): invalid', exception.message
2107
+ end
2108
+
2109
+ def test_should_raise_exception_if_method_not_specified
2110
+ exception = assert_raise(ArgumentError) {@machine.after_failure :on => :ignite}
2111
+ assert_equal 'Method(s) for callback must be specified', exception.message
2112
+ end
2113
+
2114
+ def test_should_invoke_callbacks_during_failed_transition
2115
+ @machine.after_failure lambda {|object| object.callbacks << 'failure'}
2116
+
2117
+ @event.fire(@object)
2118
+ assert_equal %w(failure), @object.callbacks
2119
+ end
2120
+
2121
+ def test_should_allow_multiple_callbacks
2122
+ @machine.after_failure lambda {|object| object.callbacks << 'failure1'}, lambda {|object| object.callbacks << 'failure2'}
2123
+
2124
+ @event.fire(@object)
2125
+ assert_equal %w(failure1 failure2), @object.callbacks
2126
+ end
2127
+
2128
+ def test_should_allow_multiple_callbacks_with_requirements
2129
+ @machine.after_failure lambda {|object| object.callbacks << 'failure_ignite1'}, lambda {|object| object.callbacks << 'failure_ignite2'}, :on => :ignite
2130
+ @machine.after_failure lambda {|object| object.callbacks << 'failure_park1'}, lambda {|object| object.callbacks << 'failure_park2'}, :on => :park
2131
+
2132
+ @event.fire(@object)
2133
+ assert_equal %w(failure_ignite1 failure_ignite2), @object.callbacks
2134
+ end
2135
+ end
2136
+
2137
+ class MachineWithPathsTest < Test::Unit::TestCase
2138
+ def setup
2139
+ @klass = Class.new
2140
+ @machine = StateMachine::Machine.new(@klass)
2141
+ @machine.event :ignite do
2142
+ transition :parked => :idling
2143
+ end
2144
+ @machine.event :shift_up do
2145
+ transition :first_gear => :second_gear
2146
+ end
2147
+
2148
+ @object = @klass.new
2149
+ @object.state = 'parked'
2150
+ end
2151
+
2152
+ def test_should_have_paths
2153
+ assert_equal [[StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)]], @machine.paths_for(@object)
2154
+ end
2155
+
2156
+ def test_should_allow_requirement_configuration
2157
+ assert_equal [[StateMachine::Transition.new(@object, @machine, :shift_up, :first_gear, :second_gear)]], @machine.paths_for(@object, :from => :first_gear)
2158
+ end
2159
+ end
2160
+
1837
2161
  class MachineWithOwnerSubclassTest < Test::Unit::TestCase
1838
2162
  def setup
1839
2163
  @klass = Class.new
@@ -1850,6 +2174,37 @@ class MachineWithOwnerSubclassTest < Test::Unit::TestCase
1850
2174
  end
1851
2175
  end
1852
2176
 
2177
+ class MachineWithOwnerSubclassHelpersTest < Test::Unit::TestCase
2178
+ def setup
2179
+ @base = Class.new
2180
+ @base_machine = StateMachine::Machine.new(@base)
2181
+ @base_machine.define_helper(:instance, :transition) { :base }
2182
+
2183
+ @subclass = Class.new(@base)
2184
+ @subclass_machine = @subclass.state_machine {}
2185
+ @subclass_machine.define_helper(:instance, :run) { :subclass }
2186
+
2187
+ @base_object = @base.new
2188
+ @subclass_object = @subclass.new
2189
+ end
2190
+
2191
+ def test_should_be_able_to_call_base_helper_on_base
2192
+ assert_equal :base, @base_machine.call_helper(:instance, :transition, @base_object, lambda {})
2193
+ end
2194
+
2195
+ def test_should_be_able_to_call_base_helper_on_subclass
2196
+ assert_equal :base, @subclass_machine.call_helper(:instance, :transition, @subclass_object, lambda {})
2197
+ end
2198
+
2199
+ def test_should_not_be_able_to_call_subclass_helper_on_base
2200
+ assert_raise(RUBY_VERSION < '1.9' ? IndexError : KeyError) { @base_machine.call_helper(:instance, :run, @base_object, lambda {}) }
2201
+ end
2202
+
2203
+ def test_should_be_able_to_call_subclass_helper_on_base
2204
+ assert_equal :subclass, @subclass_machine.call_helper(:instance, :run, @subclass_object, lambda {})
2205
+ end
2206
+ end
2207
+
1853
2208
  class MachineWithExistingMachinesOnOwnerClassTest < Test::Unit::TestCase
1854
2209
  def setup
1855
2210
  @klass = Class.new
@@ -1952,7 +2307,8 @@ end
1952
2307
  class MachineWithCustomAttributeTest < Test::Unit::TestCase
1953
2308
  def setup
1954
2309
  StateMachine::Integrations.const_set('Custom', Module.new do
1955
- class << self; attr_reader :defaults; end
2310
+ include StateMachine::Integrations::Base
2311
+
1956
2312
  @defaults = {:action => :save, :use_transactions => false}
1957
2313
 
1958
2314
  def create_with_scope(name)
@@ -2001,6 +2357,10 @@ class MachineWithCustomAttributeTest < Test::Unit::TestCase
2001
2357
  assert @object.respond_to?(:state_transitions)
2002
2358
  end
2003
2359
 
2360
+ def test_should_define_a_path_reader_for_the_attribute
2361
+ assert @object.respond_to?(:state_paths)
2362
+ end
2363
+
2004
2364
  def test_should_define_a_human_attribute_name_reader
2005
2365
  assert @klass.respond_to?(:human_state_name)
2006
2366
  end
@@ -2083,6 +2443,8 @@ end
2083
2443
  class MachineFinderWithExistingMachineOnSuperclassTest < Test::Unit::TestCase
2084
2444
  def setup
2085
2445
  integration = Module.new do
2446
+ include StateMachine::Integrations::Base
2447
+
2086
2448
  def self.matches?(klass)
2087
2449
  false
2088
2450
  end
@@ -2331,14 +2693,14 @@ begin
2331
2693
  end
2332
2694
 
2333
2695
  def test_should_load_files
2334
- StateMachine::Machine.draw('Switch', :file => "#{File.dirname(__FILE__)}/../files/switch.rb")
2696
+ StateMachine::Machine.draw('Switch', :file => File.expand_path("#{File.dirname(__FILE__)}/../files/switch.rb"))
2335
2697
  assert defined?(::Switch)
2336
2698
  ensure
2337
2699
  FileUtils.rm('./Switch_state.png')
2338
2700
  end
2339
2701
 
2340
2702
  def test_should_allow_path_and_format_to_be_customized
2341
- StateMachine::Machine.draw('Switch', :file => "#{File.dirname(__FILE__)}/../files/switch.rb", :path => "#{File.dirname(__FILE__)}/", :format => 'jpg')
2703
+ StateMachine::Machine.draw('Switch', :file => File.expand_path("#{File.dirname(__FILE__)}/../files/switch.rb"), :path => "#{File.dirname(__FILE__)}/", :format => 'jpg')
2342
2704
  assert File.exist?("#{File.dirname(__FILE__)}/Switch_state.jpg")
2343
2705
  ensure
2344
2706
  FileUtils.rm("#{File.dirname(__FILE__)}/Switch_state.jpg")