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
@@ -26,7 +26,9 @@ module SequelTest
26
26
  end if create_table
27
27
  model = Class.new(Sequel::Model(table_name)) do
28
28
  self.raise_on_save_failure = false
29
- def self.name; "SequelTest::#{table_name.to_s.capitalize}"; end
29
+ (class << self; self; end).class_eval do
30
+ define_method(:name) { "SequelTest::#{table_name.to_s.capitalize}" }
31
+ end
30
32
  end
31
33
  model.plugin(:validation_class_methods) if model.respond_to?(:plugin)
32
34
  model.plugin(:hook_class_methods) if model.respond_to?(:plugin)
@@ -592,6 +594,28 @@ module SequelTest
592
594
  end
593
595
  end
594
596
 
597
+ class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase
598
+ def setup
599
+ @model = new_model
600
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
601
+ @machine.event :ignite
602
+
603
+ @record = @model.create
604
+ @record.state_event = 'ignite'
605
+ end
606
+
607
+ def test_should_include_state_in_changed_attributes
608
+ assert_equal [:state], @record.changed_columns
609
+ end
610
+
611
+ def test_should_not_include_state_in_changed_attributes_if_nil
612
+ @record = @model.create
613
+ @record.state_event = nil
614
+
615
+ assert_equal [], @record.changed_columns
616
+ end
617
+ end
618
+
595
619
  class MachineWithoutTransactionsTest < BaseTestCase
596
620
  def setup
597
621
  @model = new_model
@@ -805,13 +829,8 @@ module SequelTest
805
829
  callbacks = []
806
830
  @machine.before_transition {callbacks << :before}
807
831
  @machine.after_transition {callbacks << :after}
808
- @machine.after_transition(:include_failures => true) {callbacks << :after_failure}
832
+ @machine.after_failure {callbacks << :after_failure}
809
833
  @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after}
810
- @machine.around_transition(:include_failures => true) do |block|
811
- callbacks << :around_before_failure
812
- block.call
813
- callbacks << :around_after_failure
814
- end
815
834
 
816
835
  @record = @model.new(:state => 'parked')
817
836
  @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
@@ -833,7 +852,7 @@ module SequelTest
833
852
  end
834
853
 
835
854
  def test_should_run_before_callbacks_and_after_callbacks_with_failures
836
- assert_equal [:before, :around_before, :around_before_failure, :around_after_failure, :after_failure], @callbacks
855
+ assert_equal [:before, :around_before, :after_failure], @callbacks
837
856
  end
838
857
  end
839
858
 
@@ -1041,14 +1060,14 @@ module SequelTest
1041
1060
  assert !ran_callback
1042
1061
  end
1043
1062
 
1044
- def test_should_run_after_callbacks_with_failures_enabled_if_validation_fails
1063
+ def test_should_run_failure_callbacks_if_validation_fails
1045
1064
  @model.class_eval do
1046
1065
  attr_accessor :seatbelt
1047
1066
  validates_presence_of :seatbelt
1048
1067
  end
1049
1068
 
1050
1069
  ran_callback = false
1051
- @machine.after_transition(:include_failures => true) { ran_callback = true }
1070
+ @machine.after_failure { ran_callback = true }
1052
1071
 
1053
1072
  @record.valid?
1054
1073
  assert ran_callback
@@ -1075,19 +1094,6 @@ module SequelTest
1075
1094
  assert !ran_callback[0]
1076
1095
  end
1077
1096
 
1078
- def test_should_run_around_callbacks_after_yield_with_failures_enabled_if_validation_fails
1079
- @model.class_eval do
1080
- attr_accessor :seatbelt
1081
- validates_presence_of :seatbelt
1082
- end
1083
-
1084
- ran_callback = [false]
1085
- @machine.around_transition(:include_failures => true) {|block| block.call; ran_callback[0] = true }
1086
-
1087
- @record.valid?
1088
- assert ran_callback[0]
1089
- end
1090
-
1091
1097
  def test_should_not_run_before_transitions_within_transaction
1092
1098
  @machine.before_transition { self.class.create; raise Sequel::Error::Rollback }
1093
1099
 
@@ -1203,21 +1209,21 @@ module SequelTest
1203
1209
  end
1204
1210
 
1205
1211
  if defined?(Sequel::MAJOR) && Sequel::MAJOR >= 3 && Sequel::MINOR >= 7
1206
- def test_should_not_run_after_callbacks_with_failures_enabled_if_fails
1212
+ def test_should_not_run_failure_callbacks_if_fails
1207
1213
  @model.before_create {|record| false}
1208
1214
 
1209
1215
  ran_callback = false
1210
- @machine.after_transition(:include_failures => true) { ran_callback = true }
1216
+ @machine.after_failure { ran_callback = true }
1211
1217
 
1212
1218
  @record.save
1213
1219
  assert !ran_callback
1214
1220
  end
1215
1221
  else
1216
- def test_should_run_after_callbacks_with_failures_enabled_if_fails
1222
+ def test_should_run_failure_callbacks_if_fails
1217
1223
  @model.before_create {|record| false}
1218
1224
 
1219
1225
  ran_callback = false
1220
- @machine.after_transition(:include_failures => true) { ran_callback = true }
1226
+ @machine.after_failure { ran_callback = true }
1221
1227
 
1222
1228
  @record.save
1223
1229
  assert ran_callback
@@ -1253,28 +1259,6 @@ module SequelTest
1253
1259
  assert ran_callback[0]
1254
1260
  end
1255
1261
 
1256
- if defined?(Sequel::MAJOR) && Sequel::MAJOR >= 3 && Sequel::MINOR >= 7
1257
- def test_should_not_run_around_callbacks_after_yield_with_failures_enabled_if_fails
1258
- @model.before_create {|record| false}
1259
-
1260
- ran_callback = [false]
1261
- @machine.around_transition(:include_failures => true) {|block| block.call; ran_callback[0] = true }
1262
-
1263
- @record.save
1264
- assert !ran_callback[0]
1265
- end
1266
- else
1267
- def test_should_run_around_callbacks_after_yield_with_failures_enabled_if_fails
1268
- @model.before_create {|record| false}
1269
-
1270
- ran_callback = [false]
1271
- @machine.around_transition(:include_failures => true) {|block| block.call; ran_callback[0] = true }
1272
-
1273
- @record.save
1274
- assert ran_callback[0]
1275
- end
1276
- end
1277
-
1278
1262
  if defined?(Sequel::MAJOR) && (Sequel::MAJOR >= 3 || Sequel::MAJOR == 2 && Sequel::MINOR == 12)
1279
1263
  def test_should_run_after_transitions_within_transaction
1280
1264
  @machine.after_transition { self.class.create; raise Sequel::Error::Rollback }
@@ -24,6 +24,10 @@ class IntegrationMatcherTest < Test::Unit::TestCase
24
24
  end
25
25
 
26
26
  class IntegrationFinderTest < Test::Unit::TestCase
27
+ def test_should_find_base
28
+ assert_equal StateMachine::Integrations::Base, StateMachine::Integrations.find(:base)
29
+ end
30
+
27
31
  def test_should_find_active_model
28
32
  assert_equal StateMachine::Integrations::ActiveModel, StateMachine::Integrations.find(:active_model)
29
33
  end
@@ -1,7 +1,20 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
2
 
3
3
  class InvalidEventTest < Test::Unit::TestCase
4
- def test_should_exist
5
- assert_not_nil StateMachine::InvalidEvent
4
+ def setup
5
+ @object = Object.new
6
+ @invalid_event = StateMachine::InvalidEvent.new(@object, :invalid)
7
+ end
8
+
9
+ def test_should_have_an_object
10
+ assert_equal @object, @invalid_event.object
11
+ end
12
+
13
+ def test_should_have_an_event
14
+ assert_equal :invalid, @invalid_event.event
15
+ end
16
+
17
+ def test_should_generate_a_message
18
+ assert_equal ':invalid is an unknown state machine event', @invalid_event.message
6
19
  end
7
20
  end
@@ -0,0 +1,18 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class InvalidParallelTransitionTest < Test::Unit::TestCase
4
+ def setup
5
+ @object = Object.new
6
+ @events = [:ignite, :disable_alarm]
7
+
8
+ @invalid_transition = StateMachine::InvalidParallelTransition.new(@object, @events)
9
+ end
10
+
11
+ def test_should_have_an_object
12
+ assert_equal @object, @invalid_transition.object
13
+ end
14
+
15
+ def test_should_have_events
16
+ assert_equal @events, @invalid_transition.events
17
+ end
18
+ end
@@ -1,7 +1,77 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
2
 
3
3
  class InvalidTransitionTest < Test::Unit::TestCase
4
- def test_should_exist
5
- assert_not_nil StateMachine::InvalidTransition
4
+ def setup
5
+ @klass = Class.new
6
+ @machine = StateMachine::Machine.new(@klass)
7
+ @state = @machine.state :parked
8
+ @machine.event :ignite
9
+
10
+ @object = @klass.new
11
+ @object.state = 'parked'
12
+
13
+ @invalid_transition = StateMachine::InvalidTransition.new(@object, @machine, :ignite)
14
+ end
15
+
16
+ def test_should_have_an_object
17
+ assert_equal @object, @invalid_transition.object
18
+ end
19
+
20
+ def test_should_have_a_machine
21
+ assert_equal @machine, @invalid_transition.machine
22
+ end
23
+
24
+ def test_should_have_an_event
25
+ assert_equal :ignite, @invalid_transition.event
26
+ end
27
+
28
+ def test_should_have_a_qualified_event
29
+ assert_equal :ignite, @invalid_transition.qualified_event
30
+ end
31
+
32
+ def test_should_have_a_from_value
33
+ assert_equal 'parked', @invalid_transition.from
34
+ end
35
+
36
+ def test_should_have_a_from_name
37
+ assert_equal :parked, @invalid_transition.from_name
38
+ end
39
+
40
+ def test_should_have_a_qualified_from_name
41
+ assert_equal :parked, @invalid_transition.qualified_from_name
42
+ end
43
+
44
+ def test_should_generate_a_message
45
+ assert_equal 'Cannot transition state via :ignite from :parked', @invalid_transition.message
46
+ end
47
+ end
48
+
49
+ class InvalidTransitionWithNamespaceTest < Test::Unit::TestCase
50
+ def setup
51
+ @klass = Class.new
52
+ @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm')
53
+ @state = @machine.state :active
54
+ @machine.event :disable
55
+
56
+ @object = @klass.new
57
+ @object.state = 'active'
58
+
59
+ @invalid_transition = StateMachine::InvalidTransition.new(@object, @machine, :disable)
60
+ end
61
+
62
+ def test_should_have_an_event
63
+ assert_equal :disable, @invalid_transition.event
64
+ end
65
+
66
+ def test_should_have_a_qualified_event
67
+ assert_equal :disable_alarm, @invalid_transition.qualified_event
68
+ end
69
+
70
+ def test_should_have_a_from_name
71
+ assert_equal :active, @invalid_transition.from_name
72
+ end
73
+
74
+ def test_should_have_a_qualified_from_name
75
+ assert_equal :alarm_active, @invalid_transition.qualified_from_name
6
76
  end
7
77
  end
@@ -31,84 +31,42 @@ class MachineCollectionStateInitializationTest < Test::Unit::TestCase
31
31
  @object.alarm_state = nil
32
32
  end
33
33
 
34
- def test_should_set_states_if_nil
35
- @machines.initialize_states(@object)
34
+ def test_should_only_initialize_static_states_prior_to_block
35
+ @machines.initialize_states(@object) do
36
+ @state_in_block = @object.state
37
+ @alarm_state_in_block = @object.alarm_state
38
+ end
36
39
 
37
- assert_equal 'parked', @object.state
38
- assert_equal 'active', @object.alarm_state
40
+ assert_equal 'parked', @state_in_block
41
+ assert_nil @alarm_state_in_block
39
42
  end
40
43
 
41
- def test_should_set_states_if_empty
42
- @object.state = ''
43
- @object.alarm_state = ''
44
- @machines.initialize_states(@object)
44
+ def test_should_only_initialize_dynamic_states_after_block
45
+ @machines.initialize_states(@object) do
46
+ @alarm_state_in_block = @object.alarm_state
47
+ end
45
48
 
46
- assert_equal 'parked', @object.state
49
+ assert_nil @alarm_state_in_block
47
50
  assert_equal 'active', @object.alarm_state
48
51
  end
49
52
 
50
- def test_should_not_set_states_if_not_empty
51
- @object.state = 'idling'
52
- @object.alarm_state = 'off'
53
+ def test_should_initialize_all_states_without_block
53
54
  @machines.initialize_states(@object)
54
55
 
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
56
  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
57
  assert_equal 'active', @object.alarm_state
71
58
  end
72
59
 
73
- def test_should_not_set_states_if_ignored
74
- @machines.initialize_states(@object, :ignore => [:state, :alarm_state])
75
-
60
+ def test_should_skip_static_states_if_disabled
61
+ @machines.initialize_states(@object, :static => false)
76
62
  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
63
  assert_equal 'active', @object.alarm_state
94
64
  end
95
65
 
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
-
66
+ def test_should_skip_dynamic_states_if_disabled
67
+ @machines.initialize_states(@object, :dynamic => false)
101
68
  assert_equal 'parked', @object.state
102
- assert_equal 'active', @object.alarm_state
103
- end
104
-
105
- def test_should_not_modify_ignore_option
106
- ignore = ['state', 'alarm_state']
107
- @machines.initialize_states(@object, :ignore => ignore)
108
-
109
- assert_nil @object.state
110
69
  assert_nil @object.alarm_state
111
- assert_equal ['state', 'alarm_state'], ignore
112
70
  end
113
71
  end
114
72
 
@@ -147,10 +105,10 @@ class MachineCollectionFireTest < Test::Unit::TestCase
147
105
 
148
106
  def test_should_raise_exception_if_invalid_event_specified
149
107
  exception = assert_raise(StateMachine::InvalidEvent) { @machines.fire_events(@object, :invalid) }
150
- assert_equal ':invalid is an unknown state machine event', exception.message
108
+ assert_equal :invalid, exception.event
151
109
 
152
110
  exception = assert_raise(StateMachine::InvalidEvent) { @machines.fire_events(@object, :ignite, :invalid) }
153
- assert_equal ':invalid is an unknown state machine event', exception.message
111
+ assert_equal :invalid, exception.event
154
112
  end
155
113
 
156
114
  def test_should_fail_if_any_event_cannot_transition
@@ -165,6 +123,15 @@ class MachineCollectionFireTest < Test::Unit::TestCase
165
123
  assert !@object.saved
166
124
  end
167
125
 
126
+ def test_should_run_failure_callbacks_if_any_event_cannot_transition
127
+ @machines[:state].after_failure {@state_failure_run = true}
128
+ @machines[:alarm_state].after_failure {@alarm_state_failure_run = true}
129
+
130
+ assert !@machines.fire_events(@object, :park, :disable_alarm)
131
+ assert @state_failure_run
132
+ assert !@alarm_state_failure_run
133
+ end
134
+
168
135
  def test_should_be_successful_if_all_events_transition
169
136
  assert @machines.fire_events(@object, :ignite, :disable_alarm)
170
137
  assert_equal 'idling', @object.state
@@ -193,6 +160,8 @@ class MachineCollectionFireWithTransactionsTest < Test::Unit::TestCase
193
160
  end
194
161
 
195
162
  StateMachine::Integrations.const_set('Custom', Module.new do
163
+ include StateMachine::Integrations::Base
164
+
196
165
  attr_reader :rolled_back
197
166
 
198
167
  def transaction(object)
@@ -235,6 +204,17 @@ class MachineCollectionFireWithTransactionsTest < Test::Unit::TestCase
235
204
  assert_equal 'active', @object.alarm_state
236
205
  end
237
206
 
207
+ def test_should_run_failure_callbacks_if_not_successful
208
+ @object.allow_save = false
209
+
210
+ @machines[:state].after_failure {@state_failure_run = true}
211
+ @machines[:alarm_state].after_failure {@alarm_state_failure_run = true}
212
+
213
+ assert !@machines.fire_events(@object, :ignite, :disable_alarm)
214
+ assert @state_failure_run
215
+ assert @alarm_state_failure_run
216
+ end
217
+
238
218
  def teardown
239
219
  StateMachine::Integrations.send(:remove_const, 'Custom')
240
220
  end
@@ -243,6 +223,8 @@ end
243
223
  class MachineCollectionFireWithValidationsTest < Test::Unit::TestCase
244
224
  def setup
245
225
  StateMachine::Integrations.const_set('Custom', Module.new do
226
+ include StateMachine::Integrations::Base
227
+
246
228
  def invalidate(object, attribute, message, values = [])
247
229
  (object.errors ||= []) << generate_message(message, values)
248
230
  end
@@ -288,6 +270,18 @@ class MachineCollectionFireWithValidationsTest < Test::Unit::TestCase
288
270
  assert_equal ['cannot transition via "ignite"', 'cannot transition via "disable"'], @object.errors
289
271
  end
290
272
 
273
+ def test_should_run_failure_callbacks_if_no_transitions_exist
274
+ @object.state = 'idling'
275
+ @object.alarm_state = 'off'
276
+
277
+ @machines[:state].after_failure {@state_failure_run = true}
278
+ @machines[:alarm_state].after_failure {@alarm_state_failure_run = true}
279
+
280
+ assert !@machines.fire_events(@object, :ignite, :disable_alarm)
281
+ assert @state_failure_run
282
+ assert @alarm_state_failure_run
283
+ end
284
+
291
285
  def teardown
292
286
  StateMachine::Integrations.send(:remove_const, 'Custom')
293
287
  end