state_machine 0.9.4 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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")