state_machine 0.3.1 → 0.4.0

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