state_machine 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  class AutoShop < ActiveRecord::Base
2
2
  state_machine :state, :initial => 'available' do
3
- after_exit 'available', :increment_customers
4
- after_exit 'busy', :decrement_customers
3
+ after_transition :from => 'available', :do => :increment_customers
4
+ after_transition :from => 'busy', :do => :decrement_customers
5
5
 
6
6
  event :tow_vehicle do
7
7
  transition :to => 'busy', :from => 'available'
@@ -0,0 +1,20 @@
1
+ class SwitchObserver < ActiveRecord::Observer
2
+ cattr_accessor :notifications
3
+ self.notifications = []
4
+
5
+ def before_turn_on(switch, from_state, to_state)
6
+ notifications << ['before_turn_on', switch, from_state, to_state]
7
+ end
8
+
9
+ def after_turn_on(switch, from_state, to_state)
10
+ notifications << ['after_turn_on', switch, from_state, to_state]
11
+ end
12
+
13
+ def before_transition(switch, attribute, event, from_state, to_state)
14
+ notifications << ['before_transition', switch, attribute, event, from_state, to_state]
15
+ end
16
+
17
+ def after_transition(switch, attribute, event, from_state, to_state)
18
+ notifications << ['after_transition', switch, attribute, event, from_state, to_state]
19
+ end
20
+ end
@@ -3,12 +3,19 @@ class Vehicle < ActiveRecord::Base
3
3
  belongs_to :highway
4
4
 
5
5
  attr_accessor :force_idle
6
+ attr_accessor :callbacks
6
7
 
7
- # Defines the state machine for the state of the vehicle
8
+ # Defines the state machine for the state of the vehicled
8
9
  state_machine :state, :initial => Proc.new {|vehicle| vehicle.force_idle ? 'idling' : 'parked'} do
9
- before_exit 'parked', :put_on_seatbelt
10
- after_enter 'parked', Proc.new {|vehicle| vehicle.update_attribute(:seatbelt_on, false)}
11
- before_enter 'stalled', :increase_insurance_premium
10
+ before_transition :from => 'parked', :do => :put_on_seatbelt
11
+ before_transition :to => 'stalled', :do => :increase_insurance_premium
12
+ after_transition :to => 'parked', :do => lambda {|vehicle| vehicle.update_attribute(:seatbelt_on, false)}
13
+ after_transition :on => 'crash', :do => :tow!
14
+ after_transition :on => 'repair', :do => :fix!
15
+
16
+ # Callback tracking for initial state callbacks
17
+ after_transition :to => 'parked', :do => lambda {|vehicle| (vehicle.callbacks ||= []) << 'before_enter_parked'}
18
+ before_transition :to => 'idling', :do => lambda {|vehicle| (vehicle.callbacks ||= []) << 'before_enter_idling'}
12
19
 
13
20
  event :park do
14
21
  transition :to => 'parked', :from => %w(idling first_gear)
@@ -34,11 +41,11 @@ class Vehicle < ActiveRecord::Base
34
41
  transition :to => 'first_gear', :from => 'second_gear'
35
42
  end
36
43
 
37
- event :crash, :after => :tow! do
38
- transition :to => 'stalled', :from => %w(first_gear second_gear third_gear), :if => Proc.new {|vehicle| vehicle.auto_shop.available?}
44
+ event :crash do
45
+ transition :to => 'stalled', :from => %w(first_gear second_gear third_gear), :if => lambda {|vehicle| vehicle.auto_shop.available?}
39
46
  end
40
47
 
41
- event :repair, :after => :fix! do
48
+ event :repair do
42
49
  transition :to => 'parked', :from => 'stalled', :if => :auto_shop_busy?
43
50
  end
44
51
  end
@@ -0,0 +1,7 @@
1
+ require 'config/boot'
2
+
3
+ Rails::Initializer.run do |config|
4
+ config.cache_classes = false
5
+ config.whiny_nils = true
6
+ config.active_record.observers = :switch_observer
7
+ end
data/test/factory.rb CHANGED
@@ -56,6 +56,12 @@ module Factory
56
56
  )
57
57
  end
58
58
 
59
+ build ToggleSwitch do |attributes|
60
+ attributes.reverse_merge!(
61
+ :state => 'off'
62
+ )
63
+ end
64
+
59
65
  build Vehicle do |attributes|
60
66
  attributes[:highway] = create_highway unless attributes.include?(:highway)
61
67
  attributes[:auto_shop] = create_auto_shop unless attributes.include?(:auto_shop)
@@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
2
 
3
3
  class VehicleTest < Test::Unit::TestCase
4
4
  def setup
5
- @vehicle = create_vehicle
5
+ @vehicle = new_vehicle
6
6
  end
7
7
 
8
8
  def test_should_not_allow_access_to_subclass_events
@@ -10,6 +10,58 @@ class VehicleTest < Test::Unit::TestCase
10
10
  end
11
11
  end
12
12
 
13
+ class VehicleUnsavedTest < Test::Unit::TestCase
14
+ def setup
15
+ @vehicle = new_vehicle
16
+ end
17
+
18
+ def test_should_be_in_parked_state
19
+ assert_equal 'parked', @vehicle.state
20
+ end
21
+
22
+ def test_should_not_be_able_to_park
23
+ assert !@vehicle.can_park?
24
+ end
25
+
26
+ def test_should_not_allow_park
27
+ assert !@vehicle.park
28
+ end
29
+
30
+ def test_should_be_able_to_ignite
31
+ assert @vehicle.can_ignite?
32
+ end
33
+
34
+ def test_should_allow_ignite
35
+ assert @vehicle.ignite
36
+ assert_equal 'idling', @vehicle.state
37
+ end
38
+
39
+ def test_should_be_saved_after_successful_event
40
+ @vehicle.ignite
41
+ assert !@vehicle.new_record?
42
+ end
43
+
44
+ def test_should_not_allow_idle
45
+ assert !@vehicle.idle
46
+ end
47
+
48
+ def test_should_not_allow_shift_up
49
+ assert !@vehicle.shift_up
50
+ end
51
+
52
+ def test_should_not_allow_shift_down
53
+ assert !@vehicle.shift_down
54
+ end
55
+
56
+ def test_should_not_allow_crash
57
+ assert !@vehicle.crash
58
+ end
59
+
60
+ def test_should_not_allow_repair
61
+ assert !@vehicle.repair
62
+ end
63
+ end
64
+
13
65
  class VehicleParkedTest < Test::Unit::TestCase
14
66
  def setup
15
67
  @vehicle = create_vehicle
@@ -2,8 +2,10 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
2
 
3
3
  class EventTest < Test::Unit::TestCase
4
4
  def setup
5
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
5
+ @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state')
6
6
  @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
7
+
8
+ @switch = new_switch
7
9
  end
8
10
 
9
11
  def test_should_have_a_machine
@@ -19,43 +21,23 @@ class EventTest < Test::Unit::TestCase
19
21
  end
20
22
 
21
23
  def test_should_define_an_event_action_on_the_owner_class
22
- switch = new_switch
23
- assert switch.respond_to?(:turn_on)
24
+ assert @switch.respond_to?(:turn_on)
24
25
  end
25
26
 
26
27
  def test_should_define_an_event_bang_action_on_the_owner_class
27
- switch = new_switch
28
- assert switch.respond_to?(:turn_on!)
29
- end
30
-
31
- def test_should_define_transition_callbacks
32
- assert Switch.respond_to?(:transition_on_turn_on)
33
- end
34
-
35
- def test_should_define_transition_bang_callbacks
36
- assert Switch.respond_to?(:transition_bang_on_turn_on)
37
- end
38
-
39
- def test_should_define_before_event_callbacks
40
- assert Switch.respond_to?(:before_turn_on)
28
+ assert @switch.respond_to?(:turn_on!)
41
29
  end
42
30
 
43
- def test_should_define_after_event_callbacks
44
- assert Switch.respond_to?(:after_turn_on)
45
- end
46
- end
47
-
48
- class EventWithInvalidOptionsTest < Test::Unit::TestCase
49
- def setup
50
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
31
+ def test_should_define_an_event_predicate_on_the_owner_class
32
+ assert @switch.respond_to?(:can_turn_on?)
51
33
  end
52
34
 
53
- def test_should_raise_exception
35
+ def test_should_raise_exception_if_invalid_option_specified
54
36
  assert_raise(ArgumentError) {PluginAWeek::StateMachine::Event.new(@machine, 'turn_on', :invalid => true)}
55
37
  end
56
38
  end
57
39
 
58
- class EventWithTransitionsTest < Test::Unit::TestCase
40
+ class EventDefiningTransitionsTest < Test::Unit::TestCase
59
41
  def setup
60
42
  @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
61
43
  @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
@@ -73,7 +55,7 @@ class EventWithTransitionsTest < Test::Unit::TestCase
73
55
  assert_nothing_raised {@event.transition(:to => 'on')}
74
56
  end
75
57
 
76
- def test_should_allow_transitioning_without_a_state
58
+ def test_should_allow_transitioning_without_a_from_state
77
59
  assert @event.transition(:to => 'on')
78
60
  end
79
61
 
@@ -86,25 +68,34 @@ class EventWithTransitionsTest < Test::Unit::TestCase
86
68
  end
87
69
 
88
70
  def test_should_have_transitions
89
- @event.transition(:to => 'on')
90
- assert @event.transitions.any?
71
+ transition = @event.transition(:to => 'on')
72
+ assert_equal [transition], @event.transitions
73
+ end
74
+ end
75
+
76
+ class EventAfterBeingCopiedTest < Test::Unit::TestCase
77
+ def setup
78
+ @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
79
+ @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
80
+ @copied_event = @event.dup
91
81
  end
92
82
 
93
- def teardown
94
- Switch.class_eval do
95
- @transition_on_turn_on_callbacks = nil
96
- @transition_bang_on_turn_on_callbacks = nil
97
- end
83
+ def test_should_not_have_the_same_collection_of_transitions
84
+ assert_not_same @copied_event.transitions, @event.transitions
98
85
  end
99
86
  end
100
87
 
101
- class EventAfterBeingFiredWithNoTransitionsTest < Test::Unit::TestCase
88
+ class EventWithoutTransitionsTest < Test::Unit::TestCase
102
89
  def setup
103
90
  @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
104
91
  @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
105
92
  @switch = create_switch(:state => 'off')
106
93
  end
107
94
 
95
+ def test_should_not_be_able_to_fire
96
+ assert !@event.can_fire?(@switch)
97
+ end
98
+
108
99
  def test_should_not_fire
109
100
  assert !@event.fire(@switch)
110
101
  end
@@ -119,7 +110,7 @@ class EventAfterBeingFiredWithNoTransitionsTest < Test::Unit::TestCase
119
110
  end
120
111
  end
121
112
 
122
- class EventAfterBeingFiredWithTransitionsTest < Test::Unit::TestCase
113
+ class EventWithTransitionsTest < Test::Unit::TestCase
123
114
  def setup
124
115
  @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
125
116
  @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
@@ -127,6 +118,10 @@ class EventAfterBeingFiredWithTransitionsTest < Test::Unit::TestCase
127
118
  @switch = create_switch(:state => 'off')
128
119
  end
129
120
 
121
+ def test_should_not_be_able_to_fire_if_no_transitions_are_matched
122
+ assert !@event.can_fire?(@switch)
123
+ end
124
+
130
125
  def test_should_not_fire_if_no_transitions_are_matched
131
126
  assert !@event.fire(@switch)
132
127
  assert_equal 'off', @switch.state
@@ -137,7 +132,12 @@ class EventAfterBeingFiredWithTransitionsTest < Test::Unit::TestCase
137
132
  assert_equal 'off', @switch.state
138
133
  end
139
134
 
140
- def test_should_fire_if_transition_with_no_from_state_is_matched
135
+ def test_should_be_able_to_fire_if_transition_is_matched
136
+ @event.transition :to => 'on'
137
+ assert @event.can_fire?(@switch)
138
+ end
139
+
140
+ def test_should_fire_if_transition_is_matched
141
141
  @event.transition :to => 'on'
142
142
  assert @event.fire(@switch)
143
143
  assert_equal 'on', @switch.state
@@ -190,81 +190,6 @@ class EventAfterBeingFiredWithTransitionsTest < Test::Unit::TestCase
190
190
  assert @event.fire!(@switch)
191
191
  assert_equal 'on', @switch.state
192
192
  end
193
-
194
- def teardown
195
- Switch.class_eval do
196
- @transition_on_turn_on_callbacks = nil
197
- @transition_bang_on_turn_on_callbacks = nil
198
- end
199
- end
200
- end
201
-
202
- class EventAfterBeingFiredWithConditionalTransitionsTest < Test::Unit::TestCase
203
- def setup
204
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
205
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
206
- @switch = create_switch(:state => 'off')
207
- end
208
-
209
- def test_should_fire_if_if_is_true
210
- @event.transition :to => 'on', :from => 'off', :if => Proc.new {true}
211
- assert @event.fire(@switch)
212
- end
213
-
214
- def test_should_not_fire_if_if_is_false
215
- @event.transition :to => 'on', :from => 'off', :if => Proc.new {false}
216
- assert !@event.fire(@switch)
217
- end
218
-
219
- def test_should_fire_if_unless_is_false
220
- @event.transition :to => 'on', :from => 'off', :unless => Proc.new {false}
221
- assert @event.fire(@switch)
222
- end
223
-
224
- def test_should_not_fire_if_unless_is_true
225
- @event.transition :to => 'on', :from => 'off', :unless => Proc.new {true}
226
- assert !@event.fire(@switch)
227
- end
228
-
229
- def test_should_pass_in_record_as_argument
230
- @event.transition :to => 'on', :from => 'off', :if => Proc.new {|record, value| !record.nil?}
231
- assert @event.fire(@switch)
232
- end
233
-
234
- def test_should_pass_in_value_as_argument
235
- @event.transition :to => 'on', :from => 'off', :if => Proc.new {|record, value| value == 1}
236
- assert @event.fire(@switch, 1)
237
- end
238
-
239
- def test_should_fire_if_method_evaluates_to_true
240
- @switch.data = true
241
- @event.transition :to => 'on', :from => 'off', :if => :data
242
- assert @event.fire(@switch)
243
- end
244
-
245
- def test_should_not_fire_if_method_evaluates_to_false
246
- @switch.data = false
247
- @event.transition :to => 'on', :from => 'off', :if => :data
248
- assert !@event.fire(@switch)
249
- end
250
-
251
- def test_should_raise_exception_if_no_transitions_are_matched
252
- assert_raise(PluginAWeek::StateMachine::InvalidTransition) {@event.fire!(@switch, 1)}
253
- assert_equal 'off', @switch.state
254
- end
255
-
256
- def test_should_not_raise_exception_if_transition_is_matched
257
- @event.transition :to => 'on', :from => 'off', :if => Proc.new {true}
258
- assert @event.fire!(@switch)
259
- assert_equal 'on', @switch.state
260
- end
261
-
262
- def teardown
263
- Switch.class_eval do
264
- @transition_on_turn_on_callbacks = nil
265
- @transition_bang_on_turn_on_callbacks = nil
266
- end
267
- end
268
193
  end
269
194
 
270
195
  class EventWithinTransactionTest < Test::Unit::TestCase
@@ -274,11 +199,11 @@ class EventWithinTransactionTest < Test::Unit::TestCase
274
199
  @event.transition :to => 'on', :from => 'off'
275
200
  @switch = create_switch(:state => 'off')
276
201
 
277
- Switch.define_callbacks :before_exit_state_off
202
+ Switch.define_callbacks :before_transition_state
278
203
  end
279
204
 
280
205
  def test_should_save_all_records_within_transaction_if_performed
281
- Switch.before_exit_state_off Proc.new {|record| Switch.create(:state => 'pending'); true}
206
+ Switch.before_transition_state lambda {|record| Switch.create(:state => 'pending'); true}, :from => 'off'
282
207
  assert @event.fire(@switch)
283
208
  assert_equal 'on', @switch.state
284
209
  assert_equal 'pending', Switch.find(:all).last.state
@@ -286,7 +211,7 @@ class EventWithinTransactionTest < Test::Unit::TestCase
286
211
 
287
212
  uses_transaction :test_should_rollback_all_records_within_transaction_if_not_performed
288
213
  def test_should_rollback_all_records_within_transaction_if_not_performed
289
- Switch.before_exit_state_off Proc.new {|record| Switch.create(:state => 'pending'); false}
214
+ Switch.before_transition_state lambda {|record| Switch.create(:state => 'pending'); false}, :from => 'off'
290
215
  assert !@event.fire(@switch)
291
216
  assert_equal 1, Switch.count
292
217
  ensure
@@ -295,7 +220,7 @@ class EventWithinTransactionTest < Test::Unit::TestCase
295
220
 
296
221
  uses_transaction :test_should_rollback_all_records_within_transaction_if_not_performed!
297
222
  def test_should_rollback_all_records_within_transaction_if_not_performed!
298
- Switch.before_exit_state_off Proc.new {|record| Switch.create(:state => 'pending'); false}
223
+ Switch.before_transition_state lambda {|record| Switch.create(:state => 'pending'); false}, :from => 'off'
299
224
  assert_raise(PluginAWeek::StateMachine::InvalidTransition) {@event.fire!(@switch)}
300
225
  assert_equal 1, Switch.count
301
226
  ensure
@@ -304,77 +229,7 @@ class EventWithinTransactionTest < Test::Unit::TestCase
304
229
 
305
230
  def teardown
306
231
  Switch.class_eval do
307
- @transition_on_turn_on_callbacks = nil
308
- @transition_bang_on_turn_on_callbacks = nil
309
- @before_exit_state_off_callbacks = nil
310
- end
311
- end
312
- end
313
-
314
- class EventWithCallbacksTest < Test::Unit::TestCase
315
- def setup
316
- @machine = PluginAWeek::StateMachine::Machine.new(Switch, 'state', :initial => 'off')
317
- @event = PluginAWeek::StateMachine::Event.new(@machine, 'turn_on')
318
- @event.transition :from => 'off', :to => 'on'
319
- @record = create_switch(:state => 'off')
320
-
321
- Switch.define_callbacks :before_turn_on, :after_turn_on
322
- end
323
-
324
- def test_should_not_perform_if_before_callback_fails
325
- Switch.before_turn_on Proc.new {|record| false}
326
- Switch.after_turn_on Proc.new {|record| record.callbacks << 'after'; true}
327
-
328
- assert !@event.fire(@record)
329
- assert_equal [], @record.callbacks
330
- end
331
-
332
- def test_should_raise_exception_if_before_callback_fails_during_perform!
333
- Switch.before_turn_on Proc.new {|record| false}
334
- Switch.after_turn_on Proc.new {|record| record.callbacks << 'after'; true}
335
-
336
- assert_raise(PluginAWeek::StateMachine::InvalidTransition) {@event.fire!(@record)}
337
- assert_equal [], @record.callbacks
338
- end
339
-
340
- def test_should_perform_if_after_callback_fails
341
- Switch.before_turn_on Proc.new {|record| record.callbacks << 'before'; true}
342
- Switch.after_turn_on Proc.new {|record| false}
343
-
344
- assert @event.fire(@record)
345
- assert_equal %w(before), @record.callbacks
346
- end
347
-
348
- def test_should_not_raise_exception_if_after_callback_fails_during_perform!
349
- Switch.before_turn_on Proc.new {|record| record.callbacks << 'before'; true}
350
- Switch.after_turn_on Proc.new {|record| false}
351
-
352
- assert @event.fire!(@record)
353
- assert_equal %w(before), @record.callbacks
354
- end
355
-
356
- def test_should_perform_if_all_callbacks_are_successful
357
- Switch.before_turn_on Proc.new {|record| record.callbacks << 'before'; true}
358
- Switch.after_turn_on Proc.new {|record| record.callbacks << 'after'; true}
359
-
360
- assert @event.fire(@record)
361
- assert_equal %w(before after), @record.callbacks
362
- end
363
-
364
- def test_should_pass_additional_arguments_to_callbacks
365
- Switch.before_turn_on Proc.new {|record, value| record.callbacks << "before-#{value}"; true}
366
- Switch.after_turn_on Proc.new {|record, value| record.callbacks << "after-#{value}"; true}
367
-
368
- assert @event.fire(@record, 'light')
369
- assert_equal %w(before-light after-light), @record.callbacks
370
- end
371
-
372
- def teardown
373
- Switch.class_eval do
374
- @before_turn_on_callbacks = nil
375
- @after_turn_on_callbacks = nil
376
- @transition_on_turn_on_callbacks = nil
377
- @transition_bang_on_turn_on_callbacks = nil
232
+ @before_transition_state_callbacks = nil
378
233
  end
379
234
  end
380
235
  end