state_machine 0.3.1 → 0.4.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 (52) hide show
  1. data/CHANGELOG.rdoc +26 -0
  2. data/README.rdoc +254 -46
  3. data/Rakefile +29 -3
  4. data/examples/AutoShop_state.png +0 -0
  5. data/examples/Car_state.jpg +0 -0
  6. data/examples/Vehicle_state.png +0 -0
  7. data/lib/state_machine.rb +161 -116
  8. data/lib/state_machine/assertions.rb +21 -0
  9. data/lib/state_machine/callback.rb +168 -0
  10. data/lib/state_machine/eval_helpers.rb +67 -0
  11. data/lib/state_machine/event.rb +135 -101
  12. data/lib/state_machine/extensions.rb +83 -0
  13. data/lib/state_machine/guard.rb +115 -0
  14. data/lib/state_machine/integrations/active_record.rb +242 -0
  15. data/lib/state_machine/integrations/data_mapper.rb +198 -0
  16. data/lib/state_machine/integrations/data_mapper/observer.rb +153 -0
  17. data/lib/state_machine/integrations/sequel.rb +169 -0
  18. data/lib/state_machine/machine.rb +746 -352
  19. data/lib/state_machine/transition.rb +104 -212
  20. data/test/active_record.log +34865 -0
  21. data/test/classes/switch.rb +11 -0
  22. data/test/data_mapper.log +14015 -0
  23. data/test/functional/state_machine_test.rb +249 -15
  24. data/test/sequel.log +3835 -0
  25. data/test/test_helper.rb +3 -12
  26. data/test/unit/assertions_test.rb +13 -0
  27. data/test/unit/callback_test.rb +189 -0
  28. data/test/unit/eval_helpers_test.rb +92 -0
  29. data/test/unit/event_test.rb +247 -113
  30. data/test/unit/guard_test.rb +420 -0
  31. data/test/unit/integrations/active_record_test.rb +515 -0
  32. data/test/unit/integrations/data_mapper_test.rb +407 -0
  33. data/test/unit/integrations/sequel_test.rb +244 -0
  34. data/test/unit/invalid_transition_test.rb +1 -1
  35. data/test/unit/machine_test.rb +1056 -98
  36. data/test/unit/state_machine_test.rb +14 -113
  37. data/test/unit/transition_test.rb +269 -495
  38. metadata +44 -30
  39. data/test/app_root/app/models/auto_shop.rb +0 -34
  40. data/test/app_root/app/models/car.rb +0 -19
  41. data/test/app_root/app/models/highway.rb +0 -3
  42. data/test/app_root/app/models/motorcycle.rb +0 -3
  43. data/test/app_root/app/models/switch.rb +0 -23
  44. data/test/app_root/app/models/switch_observer.rb +0 -20
  45. data/test/app_root/app/models/toggle_switch.rb +0 -2
  46. data/test/app_root/app/models/vehicle.rb +0 -78
  47. data/test/app_root/config/environment.rb +0 -7
  48. data/test/app_root/db/migrate/001_create_switches.rb +0 -12
  49. data/test/app_root/db/migrate/002_create_auto_shops.rb +0 -13
  50. data/test/app_root/db/migrate/003_create_highways.rb +0 -11
  51. data/test/app_root/db/migrate/004_create_vehicles.rb +0 -16
  52. data/test/factory.rb +0 -77
@@ -1,130 +1,31 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
2
 
3
- class StateMachineTest < Test::Unit::TestCase
4
- def test_should_track_all_state_machines
5
- @machine = Switch.state_machine(:state)
6
- assert_equal @machine, Switch.state_machines['state']
7
- end
8
-
9
- def test_should_allow_multiple_state_machines
10
- @machine = Switch.state_machine(:state)
11
- @second_machine = Switch.state_machine(:kind)
12
- assert_equal 2, Switch.state_machines.size
13
- end
14
-
15
- def test_should_evaluate_block_within_event_context
16
- responded = false
17
- Switch.state_machine(:state) do
18
- responded = respond_to?(:event)
19
- end
20
-
21
- assert responded
22
- end
23
-
24
- def teardown
25
- Switch.write_inheritable_attribute(:state_machines, {})
26
- end
27
- end
28
-
29
- class StateMachineAfterInitializedTest < Test::Unit::TestCase
3
+ class StateMachineByDefaultTest < Test::Unit::TestCase
30
4
  def setup
31
- Switch.state_machine(:state, :initial => 'off')
32
- end
33
-
34
- def test_should_set_the_initial_state
35
- assert_equal 'off', Switch.new.state
5
+ @klass = Class.new
6
+ @machine = @klass.state_machine
36
7
  end
37
8
 
38
- def test_should_not_set_the_initial_state_if_specified
39
- assert_equal 'on', Switch.new(:state => 'on').state
40
- end
41
-
42
- def test_should_not_set_the_initial_state_if_specified_as_string
43
- assert_equal 'on', Switch.new('state' => 'on').state
44
- end
45
-
46
- def test_should_allow_evaluation_block_during_initialization
47
- evaluated = false
48
- Switch.new do
49
- evaluated = true
50
- end
51
-
52
- assert evaluated
53
- end
54
-
55
- def teardown
56
- Switch.write_inheritable_attribute(:state_machines, {})
9
+ def test_should_use_state_attribute
10
+ assert_equal 'state', @machine.attribute
57
11
  end
58
12
  end
59
13
 
60
- class StateMachineAfterInitializedWithDynamicInitialStateTest < Test::Unit::TestCase
14
+ class StateMachineTest < Test::Unit::TestCase
61
15
  def setup
62
- Switch.state_machine(:state, :initial => Proc.new {|record| record.initial_state})
16
+ @klass = Class.new
63
17
  end
64
18
 
65
- def test_should_set_the_initial_state_based_on_the_record
66
- assert_equal 'off', Switch.new(:initial_state => 'off').state
67
- assert_equal 'on', Switch.new(:initial_state => 'on').state
19
+ def test_should_allow_state_machines_on_any_class
20
+ assert @klass.respond_to?(:state_machine)
68
21
  end
69
22
 
70
- def teardown
71
- Switch.write_inheritable_attribute(:state_machines, {})
72
- end
73
- end
74
-
75
- class StateMachineWithSubclassTest < Test::Unit::TestCase
76
- def setup
77
- @machine = Switch.state_machine(:state, :initial => 'on') do
78
- event :turn_on do
79
- transition :to => 'on', :from => 'off'
80
- end
23
+ def test_should_evaluate_block_within_machine_context
24
+ responded = false
25
+ @klass.state_machine(:state) do
26
+ responded = respond_to?(:event)
81
27
  end
82
28
 
83
- # Need to add this since the state machine isn't defined directly within the
84
- # class
85
- ToggleSwitch.write_inheritable_attribute :state_machines, {'state' => @machine}
86
-
87
- @new_machine = ToggleSwitch.state_machine(:state, :initial => 'off') do
88
- event :turn_on do
89
- transition :to => 'off', :from => 'on'
90
- end
91
-
92
- event :replace do
93
- transition :to => 'under_repair', :from => 'off'
94
- end
95
- end
96
- end
97
-
98
- def test_should_not_have_the_same_machine_as_the_superclass
99
- assert_not_same @machine, @new_machine
100
- end
101
-
102
- def test_should_use_new_initial_state
103
- assert_equal 'off', @new_machine.initial_state(new_switch)
104
- end
105
-
106
- def test_should_not_change_original_initial_state
107
- assert_equal 'on', @machine.initial_state(new_switch)
108
- end
109
-
110
- def test_should_define_new_events_on_subclass
111
- assert new_toggle_switch.respond_to?(:replace)
112
- end
113
-
114
- def test_should_not_define_new_events_on_superclass
115
- assert !new_switch.respond_to?(:replace)
116
- end
117
-
118
- def test_should_define_new_transitions_on_subclass
119
- assert_equal 2, @new_machine.events['turn_on'].transitions.length
120
- end
121
-
122
- def test_should_not_define_new_transitions_on_superclass
123
- assert_equal 1, @machine.events['turn_on'].transitions.length
124
- end
125
-
126
- def teardown
127
- Switch.write_inheritable_attribute(:state_machines, {})
128
- ToggleSwitch.write_inheritable_attribute(:state_machines, {})
29
+ assert responded
129
30
  end
130
31
  end
@@ -2,632 +2,406 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
2
 
3
3
  class TransitionTest < Test::Unit::TestCase
4
4
  def setup
5
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state')
6
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
7
- @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on')
5
+ @klass = Class.new
6
+ @machine = StateMachine::Machine.new(@klass)
7
+ @object = @klass.new
8
+ @transition = StateMachine::Transition.new(@object, @machine, 'turn_on', 'off', 'on')
8
9
  end
9
10
 
10
- def test_should_have_an_event
11
- assert_not_nil @transition.event
12
- end
13
-
14
- def test_should_have_options
15
- assert_not_nil @transition.options
16
- end
17
-
18
- def test_should_match_any_from_state
19
- assert @transition.matches?('off')
20
- assert @transition.matches?('on')
21
- end
22
-
23
- def test_should_match_empty_query
24
- assert @transition.matches?('off', {})
25
- end
26
-
27
- def test_should_match_if_from_state_included
28
- assert @transition.matches?('off', :from => 'off')
29
- end
30
-
31
- def test_should_not_match_if_from_state_not_included
32
- assert !@transition.matches?('off', :from => 'on')
33
- end
34
-
35
- def test_should_allow_matching_of_multiple_from_states
36
- assert @transition.matches?('off', :from => %w(on off))
37
- end
38
-
39
- def test_should_match_if_except_from_state_not_included
40
- assert @transition.matches?('off', :except_from => 'on')
41
- end
42
-
43
- def test_should_not_match_if_except_from_state_included
44
- assert !@transition.matches?('off', :except_from => 'off')
45
- end
46
-
47
- def test_should_allow_matching_of_multiple_except_from_states
48
- assert @transition.matches?('off', :except_from => %w(on maybe))
49
- end
50
-
51
- def test_should_match_if_to_state_included
52
- assert @transition.matches?('off', :to => 'on')
53
- end
54
-
55
- def test_should_not_match_if_to_state_not_included
56
- assert !@transition.matches?('off', :to => 'off')
57
- end
58
-
59
- def test_should_allow_matching_of_multiple_to_states
60
- assert @transition.matches?('off', :to => %w(on off))
61
- end
62
-
63
- def test_should_match_if_except_to_state_not_included
64
- assert @transition.matches?('off', :except_to => 'off')
65
- end
66
-
67
- def test_should_not_match_if_except_to_state_included
68
- assert !@transition.matches?('off', :except_to => 'on')
69
- end
70
-
71
- def test_should_allow_matching_of_multiple_except_to_states
72
- assert @transition.matches?('off', :except_to => %w(off maybe))
73
- end
74
-
75
- def test_should_match_if_on_event_included
76
- assert @transition.matches?('off', :on => 'turn_on')
77
- end
78
-
79
- def test_should_not_match_if_on_event_not_included
80
- assert !@transition.matches?('off', :on => 'turn_off')
81
- end
82
-
83
- def test_should_allow_matching_of_multiple_on_events
84
- assert @transition.matches?('off', :on => %w(turn_off turn_on))
85
- end
86
-
87
- def test_should_match_if_except_on_event_not_included
88
- assert @transition.matches?('off', :except_on => 'turn_off')
89
- end
90
-
91
- def test_should_not_match_if_except_on_event_included
92
- assert !@transition.matches?('off', :except_on => 'turn_on')
93
- end
94
-
95
- def test_should_allow_matching_of_multiple_except_on_events
96
- assert @transition.matches?('off', :except_on => %w(turn_off not_sure))
97
- end
98
-
99
- def test_should_match_if_from_state_and_to_state_match
100
- assert @transition.matches?('off', :from => 'off', :to => 'on')
101
- end
102
-
103
- def test_should_not_match_if_from_state_matches_but_not_to_state
104
- assert !@transition.matches?('off', :from => 'off', :to => 'off')
105
- end
106
-
107
- def test_should_not_match_if_to_state_matches_but_not_from_state
108
- assert !@transition.matches?('off', :from => 'on', :to => 'on')
109
- end
110
-
111
- def test_should_match_if_from_state_to_state_and_on_event_match
112
- assert @transition.matches?('off', :from => 'off', :to => 'on', :on => 'turn_on')
113
- end
114
-
115
- def test_should_not_match_if_from_state_and_to_state_match_but_not_on_event
116
- assert !@transition.matches?('off', :from => 'off', :to => 'on', :on => 'turn_off')
117
- end
118
-
119
- def test_should_be_able_to_perform_on_all_states
120
- record = new_switch(:state => 'off')
121
- assert @transition.can_perform?(record)
122
-
123
- record = new_switch(:state => 'on')
124
- assert @transition.can_perform?(record)
125
- end
126
-
127
- def test_should_perform_for_all_states
128
- record = new_switch(:state => 'off')
129
- assert @transition.perform(record)
130
-
131
- record = new_switch(:state => 'on')
132
- assert @transition.perform(record)
133
- end
134
-
135
- def test_should_not_raise_exception_if_not_valid_during_perform
136
- record = new_switch(:state => 'off')
137
- record.fail_validation = true
138
-
139
- assert !@transition.perform(record)
140
- end
141
-
142
- def test_should_raise_exception_if_not_valid_during_perform!
143
- record = new_switch(:state => 'off')
144
- record.fail_validation = true
145
-
146
- assert_raise(ActiveRecord::RecordInvalid) {@transition.perform!(record)}
147
- end
148
-
149
- def test_should_not_raise_exception_if_not_saved_during_perform
150
- record = new_switch(:state => 'off')
151
- record.fail_save = true
152
-
153
- assert !@transition.perform(record)
154
- end
155
-
156
- def test_should_raise_exception_if_not_saved_during_perform!
157
- record = new_switch(:state => 'off')
158
- record.fail_save = true
159
-
160
- assert_raise(ActiveRecord::RecordNotSaved) {@transition.perform!(record)}
161
- end
162
-
163
- def test_should_raise_exception_if_invalid_option_specified
164
- assert_raise(ArgumentError) {PluginAWeek::StateMachine::Transition.new(@event, :invalid => true)}
165
- end
166
-
167
- def test_should_raise_exception_if_to_option_not_specified
168
- assert_raise(ArgumentError) {PluginAWeek::StateMachine::Transition.new(@event, :from => 'off')}
169
- end
170
- end
171
-
172
- class TransitionWithConditionalTest < Test::Unit::TestCase
173
- def setup
174
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state')
175
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
176
- @switch = create_switch(:state => 'off')
177
- end
178
-
179
- def test_should_be_able_to_perform_if_if_is_true
180
- transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :if => lambda {true})
181
- assert transition.can_perform?(@switch)
11
+ def test_should_have_an_object
12
+ assert_equal @object, @transition.object
182
13
  end
183
14
 
184
- def test_should_not_be_able_to_perform_if_if_is_false
185
- transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :if => lambda {false})
186
- assert !transition.can_perform?(@switch)
15
+ def test_should_have_a_machine
16
+ assert_equal @machine, @transition.machine
187
17
  end
188
18
 
189
- def test_should_be_able_to_perform_if_unless_is_false
190
- transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :unless => lambda {false})
191
- assert transition.can_perform?(@switch)
19
+ def test_should_have_an_event
20
+ assert_equal 'turn_on', @transition.event
192
21
  end
193
22
 
194
- def test_should_not_be_able_to_perform_if_unless_is_true
195
- transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :unless => lambda {true})
196
- assert !transition.can_perform?(@switch)
23
+ def test_should_have_a_from_state
24
+ assert_equal 'off', @transition.from
197
25
  end
198
26
 
199
- def test_should_pass_in_record_as_argument
200
- transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :if => lambda {|record| !record.nil?})
201
- assert transition.can_perform?(@switch)
27
+ def test_should_have_a_to_state
28
+ assert_equal 'on', @transition.to
202
29
  end
203
30
 
204
- def test_should_be_able_to_perform_if_method_evaluates_to_true
205
- @switch.data = true
206
- transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :if => :data)
207
- assert transition.can_perform?(@switch)
31
+ def test_should_have_an_attribute
32
+ assert_equal 'state', @transition.attribute
208
33
  end
209
34
 
210
- def test_should_not_be_able_to_perform_if_method_evaluates_to_false
211
- @switch.data = false
212
- transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :if => :data)
213
- assert !transition.can_perform?(@switch)
35
+ def test_should_generate_attributes
36
+ expected = {:object => @object, :attribute => 'state', :event => 'turn_on', :from => 'off', :to => 'on'}
37
+ assert_equal expected, @transition.attributes
214
38
  end
215
39
  end
216
40
 
217
- class TransitionWithLoopbackTest < Test::Unit::TestCase
41
+ class TransitionWithSymbolicValuesTest < Test::Unit::TestCase
218
42
  def setup
219
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state')
220
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
221
- @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'on')
43
+ @klass = Class.new
44
+ @machine = StateMachine::Machine.new(@klass)
45
+ @object = @klass.new
46
+ @transition = StateMachine::Transition.new(@object, @machine, :turn_on, :off, :on)
222
47
  end
223
48
 
224
- def test_should_be_able_to_perform
225
- record = new_switch(:state => 'on')
226
- assert @transition.can_perform?(record)
49
+ def test_should_not_stringify_event
50
+ assert_equal :turn_on, @transition.event
227
51
  end
228
52
 
229
- def test_should_perform_for_valid_from_state
230
- record = new_switch(:state => 'on')
231
- assert @transition.perform(record)
232
- end
233
- end
234
-
235
- class TransitionWithFromStateTest < Test::Unit::TestCase
236
- def setup
237
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state')
238
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
239
- @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'off')
53
+ def test_should_not_stringify_from_state
54
+ assert_equal :off, @transition.from
240
55
  end
241
56
 
242
- def test_should_not_be_able_to_perform_if_record_state_is_not_from_state
243
- record = new_switch(:state => 'on')
244
- assert !@transition.can_perform?(record)
245
- end
246
-
247
- def test_should_be_able_to_perform_if_record_state_is_from_state
248
- record = new_switch(:state => 'off')
249
- assert @transition.can_perform?(record)
250
- end
251
-
252
- def test_should_perform_for_valid_from_state
253
- record = new_switch(:state => 'off')
254
- assert @transition.perform(record)
57
+ def test_should_not_stringify_to_state
58
+ assert_equal :on, @transition.to
255
59
  end
256
60
  end
257
61
 
258
- class TransitionWithMultipleFromStatesTest < Test::Unit::TestCase
62
+ class TransitionAfterBeingPerformedTest < Test::Unit::TestCase
259
63
  def setup
260
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state')
261
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
262
- @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => %w(off on))
263
- end
264
-
265
- def test_should_not_be_able_to_perform_if_record_state_is_not_from_state
266
- record = new_switch(:state => 'unknown')
267
- assert !@transition.can_perform?(record)
268
- end
269
-
270
- def test_should_be_able_to_perform_if_record_state_is_any_from_state
271
- record = new_switch(:state => 'off')
272
- assert @transition.can_perform?(record)
273
-
274
- record = new_switch(:state => 'on')
275
- assert @transition.can_perform?(record)
276
- end
277
-
278
- def test_should_perform_for_any_valid_from_state
279
- record = new_switch(:state => 'off')
280
- assert @transition.perform(record)
64
+ @klass = Class.new do
65
+ attr_reader :saved, :save_state
66
+
67
+ def save
68
+ @save_state = state
69
+ @saved = true
70
+ end
71
+ end
281
72
 
282
- record = new_switch(:state => 'on')
283
- assert @transition.perform(record)
284
- end
285
- end
286
-
287
- class TransitionWithMismatchedFromStatesRequiredTest < Test::Unit::TestCase
288
- def setup
289
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state')
290
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
291
- @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :except_from => 'on')
73
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
74
+ @object = @klass.new
75
+ @transition = StateMachine::Transition.new(@object, @machine, 'turn_on', 'off', 'on')
76
+ @result = @transition.perform
292
77
  end
293
78
 
294
- def test_should_be_able_to_perform_if_record_state_is_not_from_state
295
- record = new_switch(:state => 'off')
296
- assert @transition.can_perform?(record)
79
+ def test_should_be_successful
80
+ assert_equal true, @result
297
81
  end
298
82
 
299
- def test_should_not_be_able_to_perform_if_record_state_is_from_state
300
- record = new_switch(:state => 'on')
301
- assert !@transition.can_perform?(record)
83
+ def test_should_the_current_state
84
+ assert_equal 'on', @object.state
302
85
  end
303
86
 
304
- def test_should_perform_for_valid_from_state
305
- record = new_switch(:state => 'off')
306
- assert @transition.perform(record)
87
+ def test_should_run_the_action
88
+ assert @object.saved
307
89
  end
308
90
 
309
- def test_should_not_perform_for_invalid_from_state
310
- record = new_switch(:state => 'on')
311
- assert !@transition.can_perform?(record)
91
+ def test_should_run_the_action_after_saving_the_state
92
+ assert_equal 'on', @object.save_state
312
93
  end
313
94
  end
314
95
 
315
- class TransitionAfterBeingPerformedTest < Test::Unit::TestCase
96
+ class TransitionWithoutRunningActionTest < Test::Unit::TestCase
316
97
  def setup
317
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state')
318
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
319
- @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'off')
320
-
321
- @record = create_switch(:state => 'off')
322
- @transition.perform(@record)
323
- @record.reload
324
- end
325
-
326
- def test_should_update_the_state_to_the_to_state
327
- assert_equal 'on', @record.state
328
- end
329
-
330
- def test_should_no_longer_be_able_to_perform_on_the_record
331
- assert !@transition.can_perform?(@record)
332
- end
333
- end
334
-
335
- class TransitionWithLoopbackAfterBeingPerformedTest < Test::Unit::TestCase
336
- def setup
337
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state')
338
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
339
- @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'on')
98
+ @klass = Class.new do
99
+ attr_reader :saved
100
+
101
+ def save
102
+ @saved = true
103
+ end
104
+ end
340
105
 
341
- @record = create_switch(:state => 'on')
342
- @record.kind = 'light'
343
- @transition.perform(@record)
344
- @record.reload
106
+ @machine = StateMachine::Machine.new(@klass)
107
+ @object = @klass.new
108
+ @transition = StateMachine::Transition.new(@object, @machine, 'turn_on', 'off', 'on')
109
+ @result = @transition.perform(false)
345
110
  end
346
111
 
347
- def test_should_have_the_same_attribute
348
- assert_equal 'on', @record.state
112
+ def test_should_be_successful
113
+ assert_equal true, @result
349
114
  end
350
115
 
351
- def test_should_save_the_record
352
- assert_equal 'light', @record.kind
116
+ def test_should_the_current_state
117
+ assert_equal 'on', @object.state
353
118
  end
354
119
 
355
- def test_should_still_be_able_to_perform_on_the_record
356
- assert @transition.can_perform?(@record)
120
+ def test_should_not_run_the_action
121
+ assert !@object.saved
357
122
  end
358
123
  end
359
124
 
360
125
  class TransitionWithCallbacksTest < Test::Unit::TestCase
361
126
  def setup
362
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state')
363
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
364
- @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'off')
365
- @record = create_switch(:state => 'off')
127
+ @klass = Class.new do
128
+ attr_reader :saved, :save_state
129
+
130
+ def save
131
+ @save_state = state
132
+ @saved = true
133
+ end
134
+ end
366
135
 
367
- Switch.define_callbacks :before_transition_state, :after_transition_state
136
+ @machine = StateMachine::Machine.new(@klass)
137
+ @object = @klass.new
138
+ @object.state = 'off'
139
+ @transition = StateMachine::Transition.new(@object, @machine, 'turn_on', 'off', 'on')
368
140
  end
369
141
 
370
- def test_should_include_record_in_callback
371
- Switch.before_transition_state lambda {|record| record == @record}
142
+ def test_should_run_before_callbacks_before_changing_the_state
143
+ @machine.before_transition(lambda {|object| @state = object.state})
144
+ @transition.perform
372
145
 
373
- assert @transition.perform(@record)
146
+ assert_equal 'off', @state
374
147
  end
375
148
 
376
- def test_should_not_perform_if_before_callback_fails
377
- Switch.before_transition_state lambda {|record| false}
378
- Switch.after_transition_state lambda {|record| record.callbacks << 'after'; true}
149
+ def test_should_run_after_callbacks_after_running_the_action
150
+ @machine.after_transition(lambda {|object| @state = object.state})
151
+ @transition.perform
379
152
 
380
- assert !@transition.perform(@record)
381
- assert_equal [], @record.callbacks
153
+ assert_equal 'on', @state
382
154
  end
383
155
 
384
- def test_should_raise_exception_if_before_callback_fails_during_perform!
385
- Switch.before_transition_state lambda {|record| false}
156
+ def test_should_run_before_callbacks_in_the_order_they_were_defined
157
+ @callbacks = []
158
+ @machine.before_transition(lambda {@callbacks << 1})
159
+ @machine.before_transition(lambda {@callbacks << 2})
160
+ @transition.perform
386
161
 
387
- assert_raise(PluginAWeek::StateMachine::InvalidTransition) {@transition.perform!(@record)}
162
+ assert_equal [1, 2], @callbacks
388
163
  end
389
164
 
390
- def test_should_perform_if_after_callback_fails
391
- Switch.before_transition_state lambda {|record| record.callbacks << 'before'; true}
392
- Switch.after_transition_state lambda {|record| false}
165
+ def test_should_run_after_callbacks_in_the_order_they_were_defined
166
+ @callbacks = []
167
+ @machine.after_transition(lambda {@callbacks << 1})
168
+ @machine.after_transition(lambda {@callbacks << 2})
169
+ @transition.perform
393
170
 
394
- assert @transition.perform(@record)
395
- assert_equal %w(before), @record.callbacks
171
+ assert_equal [1, 2], @callbacks
396
172
  end
397
173
 
398
- def test_should_not_raise_exception_if_after_callback_fails_during_perform!
399
- Switch.before_transition_state lambda {|record| record.callbacks << 'before'; true}
400
- Switch.after_transition_state lambda {|record| false}
174
+ def test_should_only_run_before_callbacks_that_match_transition_context
175
+ @count = 0
176
+ callback = lambda {@count += 1}
401
177
 
402
- assert @transition.perform!(@record)
403
- end
404
-
405
- def test_should_perform_if_all_callbacks_are_successful
406
- Switch.before_transition_state lambda {|record| record.callbacks << 'before'; true}
407
- Switch.after_transition_state lambda {|record| record.callbacks << 'after'; true}
178
+ @machine.before_transition :from => 'off', :to => 'on', :on => 'turn_off', :do => callback
179
+ @machine.before_transition :from => 'off', :to => 'off', :on => 'turn_off', :do => callback
180
+ @machine.before_transition :from => 'off', :to => 'on', :on => 'turn_on', :do => callback
181
+ @machine.before_transition :from => 'on', :to => 'on', :on => 'turn_off', :do => callback
182
+ @transition.perform
408
183
 
409
- assert @transition.perform(@record)
410
- assert_equal %w(before after), @record.callbacks
184
+ assert_equal 1, @count
411
185
  end
412
186
 
413
- def test_should_stop_before_callbacks_if_any_fail
414
- Switch.before_transition_state lambda {|record| false}
415
- Switch.before_transition_state lambda {|record| record.callbacks << 'before_2'; true}
187
+ def test_should_only_run_after_callbacks_that_match_transition_context
188
+ @count = 0
189
+ callback = lambda {@count += 1}
416
190
 
417
- assert !@transition.perform(@record)
418
- assert_equal [], @record.callbacks
191
+ @machine.after_transition :from => 'off', :to => 'on', :on => 'turn_off', :do => callback
192
+ @machine.after_transition :from => 'off', :to => 'off', :on => 'turn_off', :do => callback
193
+ @machine.after_transition :from => 'off', :to => 'on', :on => 'turn_on', :do => callback
194
+ @machine.after_transition :from => 'on', :to => 'on', :on => 'turn_off', :do => callback
195
+ @transition.perform
196
+
197
+ assert_equal 1, @count
419
198
  end
420
199
 
421
- def test_should_stop_after_callbacks_if_any_fail
422
- Switch.after_transition_state lambda {|record| false}
423
- Switch.after_transition_state lambda {|record| record.callbacks << 'after_2'; true}
200
+ def test_should_pass_transition_to_before_callbacks
201
+ @machine.before_transition(lambda {|*args| @args = args})
202
+ @transition.perform
424
203
 
425
- assert @transition.perform(@record)
426
- assert_equal [], @record.callbacks
204
+ assert_equal [@object, @transition], @args
427
205
  end
428
206
 
429
- def teardown
430
- Switch.class_eval do
431
- @before_transition_state_callbacks = nil
432
- @after_transition_state_callbacks = nil
433
- end
207
+ def test_should_pass_transition_and_action_result_to_after_callbacks
208
+ @machine.after_transition(lambda {|*args| @args = args})
209
+ @transition.perform
210
+
211
+ assert_equal [@object, @transition, true], @args
434
212
  end
435
213
  end
436
214
 
437
- class TransitionWithCallbackConditionalsTest < Test::Unit::TestCase
215
+ class TransitionHaltedDuringBeforeCallbacksTest < Test::Unit::TestCase
438
216
  def setup
439
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state')
440
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
441
- @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'off')
442
- @record = create_switch(:state => 'off')
443
- @invoked = false
217
+ @klass = Class.new do
218
+ class << self; attr_accessor :cancelled_transaction; end
219
+ attr_reader :saved
220
+
221
+ def save
222
+ @saved = true
223
+ end
224
+ end
225
+ @before_count = 0
226
+ @after_count = 0
444
227
 
445
- Switch.define_callbacks :before_transition_state, :after_transition_state
228
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
229
+ class << @machine
230
+ def within_transaction(object)
231
+ owner_class.cancelled_transaction = yield == false
232
+ end
233
+ end
234
+ @machine.before_transition lambda {@before_count += 1; throw :halt}
235
+ @machine.before_transition lambda {@before_count += 1}
236
+ @machine.after_transition lambda {@after_count += 1}
237
+ @object = @klass.new
238
+ @transition = StateMachine::Transition.new(@object, @machine, 'turn_on', 'off', 'on')
239
+ @result = @transition.perform
446
240
  end
447
241
 
448
- def test_should_invoke_callback_if_if_is_true
449
- Switch.before_transition_state lambda {|record| @invoked = true}, :if => lambda {true}
450
- @transition.perform(@record)
451
- assert @invoked
242
+ def test_should_not_be_successful
243
+ assert !@result
452
244
  end
453
245
 
454
- def test_should_not_invoke_callback_if_if_is_false
455
- Switch.before_transition_state lambda {|record| @invoked = true}, :if => lambda {false}
456
- @transition.perform(@record)
457
- assert !@invoked
246
+ def test_should_not_change_current_state
247
+ assert_nil @object.state
458
248
  end
459
249
 
460
- def test_should_invoke_callback_if_unless_is_false
461
- Switch.before_transition_state lambda {|record| @invoked = true}, :unless => lambda {false}
462
- @transition.perform(@record)
463
- assert @invoked
250
+ def test_should_not_run_action
251
+ assert !@object.saved
464
252
  end
465
253
 
466
- def test_should_not_invoke_callback_if_unless_is_true
467
- Switch.before_transition_state lambda {|record| @invoked = true}, :unless => lambda {true}
468
- @transition.perform(@record)
469
- assert !@invoked
254
+ def test_should_not_run_further_before_callbacks
255
+ assert_equal 1, @before_count
470
256
  end
471
257
 
472
- def teardown
473
- Switch.class_eval do
474
- @before_transition_state_callbacks = nil
475
- @after_transition_state_callbacks = nil
476
- end
258
+ def test_should_not_run_after_callbacks
259
+ assert_equal 0, @after_count
260
+ end
261
+
262
+ def test_should_cancel_the_transaction
263
+ assert @klass.cancelled_transaction
477
264
  end
478
265
  end
479
266
 
480
- class TransitionWithCallbackQueryTest < Test::Unit::TestCase
267
+ class TransitionHaltedDuringActionTest < Test::Unit::TestCase
481
268
  def setup
482
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state')
483
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
484
- @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'off')
485
- @record = create_switch(:state => 'off')
269
+ @klass = Class.new do
270
+ class << self; attr_accessor :cancelled_transaction; end
271
+ attr_reader :saved
272
+
273
+ def save
274
+ throw :halt
275
+ end
276
+ end
277
+ @before_count = 0
278
+ @after_count = 0
486
279
 
487
- Switch.define_callbacks :before_transition_state, :after_transition_state
488
- end
489
-
490
- def test_should_invoke_callback_if_from_state_included
491
- Switch.before_transition_state lambda {|record| @invoked = true}, :from => 'off'
492
- @transition.perform(@record)
493
- assert @invoked
494
- end
495
-
496
- def test_should_not_invoke_callback_if_from_state_not_included
497
- Switch.before_transition_state lambda {|record| @invoked = true}, :from => 'on'
498
- @transition.perform(@record)
499
- assert !@invoked
500
- end
501
-
502
- def test_should_invoke_callback_if_except_from_state_not_included
503
- Switch.before_transition_state lambda {|record| @invoked = true}, :except_from => 'on'
504
- @transition.perform(@record)
505
- assert @invoked
280
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
281
+ class << @machine
282
+ def within_transaction(object)
283
+ owner_class.cancelled_transaction = yield == false
284
+ end
285
+ end
286
+ @machine.before_transition lambda {@before_count += 1}
287
+ @machine.after_transition lambda {@after_count += 1}
288
+ @object = @klass.new
289
+ @transition = StateMachine::Transition.new(@object, @machine, 'turn_on', 'off', 'on')
290
+ @result = @transition.perform
506
291
  end
507
292
 
508
- def test_should_not_invoke_callback_if_except_from_state_included
509
- Switch.before_transition_state lambda {|record| @invoked = true}, :except_from => 'off'
510
- @transition.perform(@record)
511
- assert !@invoked
293
+ def test_should_not_be_successful
294
+ assert !@result
512
295
  end
513
296
 
514
- def test_should_invoke_callback_if_to_state_included
515
- Switch.before_transition_state lambda {|record| @invoked = true}, :to => 'on'
516
- @transition.perform(@record)
517
- assert @invoked
297
+ def test_should_change_current_state
298
+ assert_equal 'on', @object.state
518
299
  end
519
300
 
520
- def test_should_not_invoke_callback_if_to_state_not_included
521
- Switch.before_transition_state lambda {|record| @invoked = true}, :to => 'off'
522
- @transition.perform(@record)
523
- assert !@invoked
301
+ def test_should_run_before_callbacks
302
+ assert_equal 1, @before_count
524
303
  end
525
304
 
526
- def test_should_invoke_callback_if_except_to_state_not_included
527
- Switch.before_transition_state lambda {|record| @invoked = true}, :except_to => 'off'
528
- @transition.perform(@record)
529
- assert @invoked
305
+ def test_should_not_run_after_callbacks
306
+ assert_equal 0, @after_count
530
307
  end
531
308
 
532
- def test_should_not_invoke_callback_if_except_to_state_included
533
- Switch.before_transition_state lambda {|record| @invoked = true}, :except_to => 'on'
534
- @transition.perform(@record)
535
- assert !@invoked
309
+ def test_should_cancel_the_transaction
310
+ assert @klass.cancelled_transaction
536
311
  end
537
-
538
- def test_should_invoke_callback_if_on_event_included
539
- Switch.before_transition_state lambda {|record| @invoked = true}, :on => 'turn_on'
540
- @transition.perform(@record)
541
- assert @invoked
312
+ end
313
+
314
+ class TransitionHaltedAfterCallbackTest < Test::Unit::TestCase
315
+ def setup
316
+ @klass = Class.new do
317
+ class << self; attr_accessor :cancelled_transaction; end
318
+ attr_reader :saved
319
+
320
+ def save
321
+ @saved = true
322
+ end
323
+ end
324
+ @before_count = 0
325
+ @after_count = 0
326
+
327
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
328
+ class << @machine
329
+ def within_transaction(object)
330
+ owner_class.cancelled_transaction = yield == false
331
+ end
332
+ end
333
+ @machine.before_transition lambda {@before_count += 1}
334
+ @machine.after_transition lambda {@after_count += 1; throw :halt}
335
+ @machine.after_transition lambda {@after_count += 1}
336
+ @object = @klass.new
337
+ @transition = StateMachine::Transition.new(@object, @machine, 'turn_on', 'off', 'on')
338
+ @result = @transition.perform
542
339
  end
543
340
 
544
- def test_should_not_invoke_callback_if_on_event_not_included
545
- Switch.before_transition_state lambda {|record| @invoked = true}, :on => 'turn_off'
546
- @transition.perform(@record)
547
- assert !@invoked
341
+ def test_should_be_successful
342
+ assert @result
548
343
  end
549
344
 
550
- def test_should_invoke_callback_if_except_on_event_not_included
551
- Switch.before_transition_state lambda {|record| @invoked = true}, :except_on => 'turn_off'
552
- @transition.perform(@record)
553
- assert @invoked
345
+ def test_should_change_current_state
346
+ assert_equal 'on', @object.state
554
347
  end
555
348
 
556
- def test_should_not_invoke_callback_if_except_on_event_included
557
- Switch.before_transition_state lambda {|record| @invoked = true}, :except_on => 'turn_on'
558
- @transition.perform(@record)
559
- assert !@invoked
349
+ def test_should_run_before_callbacks
350
+ assert_equal 1, @before_count
560
351
  end
561
352
 
562
- def test_should_skip_callbacks_that_do_not_match
563
- Switch.before_transition_state lambda {|record| false}, :from => 'on'
564
- Switch.before_transition_state lambda {|record| @invoked = true}, :from => 'off'
565
- @transition.perform(@record)
566
- assert @invoked
353
+ def test_should_not_run_further_after_callbacks
354
+ assert_equal 1, @after_count
567
355
  end
568
356
 
569
- def teardown
570
- Switch.class_eval do
571
- @before_transition_state_callbacks = nil
572
- @after_transition_state_callbacks = nil
573
- end
357
+ def test_should_not_cancel_the_transaction
358
+ assert !@klass.cancelled_transaction
574
359
  end
575
360
  end
576
361
 
577
- class TransitionWithObserversTest < Test::Unit::TestCase
362
+ class TransitionWithFailedActionTest < Test::Unit::TestCase
578
363
  def setup
579
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state')
580
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
581
- @transition = PluginAWeek::StateMachine::Transition.new(@event, :to => 'on', :from => 'off')
582
- @record = create_switch(:state => 'off')
364
+ @klass = Class.new do
365
+ class << self; attr_accessor :cancelled_transaction; end
366
+ attr_reader :saved
367
+
368
+ def save
369
+ false
370
+ end
371
+ end
372
+ @before_count = 0
373
+ @after_count = 0
583
374
 
584
- Switch.define_callbacks :before_transition_state, :after_transition_state
585
- SwitchObserver.notifications = []
375
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
376
+ class << @machine
377
+ def within_transaction(object)
378
+ owner_class.cancelled_transaction = yield == false
379
+ end
380
+ end
381
+ @machine.before_transition lambda {@before_count += 1}
382
+ @machine.after_transition lambda {@after_count += 1}
383
+ @object = @klass.new
384
+ @transition = StateMachine::Transition.new(@object, @machine, 'turn_on', 'off', 'on')
385
+ @result = @transition.perform
586
386
  end
587
387
 
588
- def test_should_notify_all_callbacks_if_successful
589
- @transition.perform(@record)
590
-
591
- expected = [
592
- ['before_turn_on', @record, 'off', 'on'],
593
- ['before_transition', @record, 'state', 'turn_on', 'off', 'on'],
594
- ['after_turn_on', @record, 'off', 'on'],
595
- ['after_transition', @record, 'state', 'turn_on', 'off', 'on']
596
- ]
597
-
598
- assert_equal expected, SwitchObserver.notifications
388
+ def test_should_not_be_successful
389
+ assert !@result
599
390
  end
600
391
 
601
- def test_should_notify_before_callbacks_if_before_callback_fails
602
- Switch.before_transition_state lambda {|record| false}
603
- @transition.perform(@record)
604
-
605
- expected = [
606
- ['before_turn_on', @record, 'off', 'on'],
607
- ['before_transition', @record, 'state', 'turn_on', 'off', 'on']
608
- ]
609
-
610
- assert_equal expected, SwitchObserver.notifications
392
+ def test_should_change_current_state
393
+ assert_equal 'on', @object.state
611
394
  end
612
395
 
613
- def test_should_notify_before_and_after_callbacks_if_after_callback_fails
614
- Switch.after_transition_state lambda {|record| false}
615
- @transition.perform(@record)
616
-
617
- expected = [
618
- ['before_turn_on', @record, 'off', 'on'],
619
- ['before_transition', @record, 'state', 'turn_on', 'off', 'on'],
620
- ['after_turn_on', @record, 'off', 'on'],
621
- ['after_transition', @record, 'state', 'turn_on', 'off', 'on']
622
- ]
623
-
624
- assert_equal expected, SwitchObserver.notifications
396
+ def test_should_run_before_callbacks
397
+ assert_equal 1, @before_count
625
398
  end
626
399
 
627
- def teardown
628
- Switch.class_eval do
629
- @before_transition_state_callbacks = nil
630
- @after_transition_state_callbacks = nil
631
- end
400
+ def test_should_run_after_callbacks
401
+ assert_equal 1, @after_count
402
+ end
403
+
404
+ def test_should_cancel_the_transaction
405
+ assert @klass.cancelled_transaction
632
406
  end
633
407
  end