state_machine 0.2.1 → 0.3.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.
@@ -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