state_machine 0.7.6 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -28,6 +28,7 @@ begin
28
28
  def self.name; 'SequelTest::Foo'; end
29
29
  end
30
30
  model.plugin(:validation_class_methods) if model.respond_to?(:plugin)
31
+ model.plugin(:hook_class_methods) if model.respond_to?(:plugin)
31
32
  model.class_eval(&block) if block_given?
32
33
  model
33
34
  end
@@ -184,15 +185,136 @@ begin
184
185
  end
185
186
  end
186
187
 
187
- class MachineWithInitialStateTest < BaseTestCase
188
+ class MachineWithStaticInitialStateTest < BaseTestCase
188
189
  def setup
189
- @model = new_model
190
- @machine = StateMachine::Machine.new(@model, :initial => 'parked')
191
- @record = @model.new
190
+ @model = new_model do
191
+ attr_accessor :value
192
+ end
193
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
192
194
  end
193
195
 
194
196
  def test_should_set_initial_state_on_created_object
195
- assert_equal 'parked', @record.state
197
+ record = @model.new
198
+ assert_equal 'parked', record.state
199
+ end
200
+
201
+ def test_should_still_set_attributes
202
+ record = @model.new(:value => 1)
203
+ assert_equal 1, record.value
204
+ end
205
+
206
+ def test_should_still_allow_initialize_blocks
207
+ block_args = nil
208
+ record = @model.new do |*args|
209
+ block_args = args
210
+ end
211
+
212
+ assert_equal [record], block_args
213
+ end
214
+
215
+ def test_should_not_have_any_changed_columns
216
+ record = @model.new
217
+ assert record.changed_columns.empty?
218
+ end
219
+
220
+ def test_should_set_attributes_prior_to_after_initialize_hook
221
+ state = nil
222
+ @model.class_eval do
223
+ define_method(:after_initialize) do
224
+ state = self.state
225
+ end
226
+ end
227
+ @model.new
228
+ assert_equal 'parked', state
229
+ end
230
+
231
+ def test_should_set_initial_state_before_setting_attributes
232
+ @model.class_eval do
233
+ attr_accessor :state_during_setter
234
+
235
+ define_method(:value=) do |value|
236
+ self.state_during_setter = state
237
+ end
238
+ end
239
+
240
+ record = @model.new(:value => 1)
241
+ assert_equal 'parked', record.state_during_setter
242
+ end
243
+
244
+ def test_should_not_set_initial_state_after_already_initialized
245
+ record = @model.new(:value => 1)
246
+ assert_equal 'parked', record.state
247
+
248
+ record.state = 'idling'
249
+ record.set({})
250
+ assert_equal 'idling', record.state
251
+ end
252
+ end
253
+
254
+ class MachineWithDynamicInitialStateTest < BaseTestCase
255
+ def setup
256
+ @model = new_model do
257
+ attr_accessor :value
258
+ end
259
+ @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked})
260
+ @machine.state :parked
261
+ end
262
+
263
+ def test_should_set_initial_state_on_created_object
264
+ record = @model.new
265
+ assert_equal 'parked', record.state
266
+ end
267
+
268
+ def test_should_still_set_attributes
269
+ record = @model.new(:value => 1)
270
+ assert_equal 1, record.value
271
+ end
272
+
273
+ def test_should_still_allow_initialize_blocks
274
+ block_args = nil
275
+ record = @model.new do |*args|
276
+ block_args = args
277
+ end
278
+
279
+ assert_equal [record], block_args
280
+ end
281
+
282
+ def test_should_not_have_any_changed_columns
283
+ record = @model.new
284
+ assert record.changed_columns.empty?
285
+ end
286
+
287
+ def test_should_set_attributes_prior_to_after_initialize_hook
288
+ state = nil
289
+ @model.class_eval do
290
+ define_method(:after_initialize) do
291
+ state = self.state
292
+ end
293
+ end
294
+ @model.new
295
+ assert_equal 'parked', state
296
+ end
297
+
298
+ def test_should_set_initial_state_after_setting_attributes
299
+ @model.class_eval do
300
+ attr_accessor :state_during_setter
301
+
302
+ define_method(:value=) do |value|
303
+ self.state_during_setter = state || 'nil'
304
+ end
305
+ end
306
+
307
+ record = @model.new(:value => 1)
308
+ assert_equal 'nil', record.state_during_setter
309
+ end
310
+
311
+ def test_should_not_set_initial_state_after_already_initialized
312
+ record = @model.new(:value => 1)
313
+ assert_equal 'parked', record.state
314
+
315
+ record.state = 'idling'
316
+ record.set({})
317
+ assert_equal 'idling', record.state
196
318
  end
197
319
  end
198
320
 
@@ -279,20 +401,24 @@ begin
279
401
 
280
402
  class MachineWithCustomAttributeTest < BaseTestCase
281
403
  def setup
282
- @model = new_model
283
- @machine = StateMachine::Machine.new(@model, :status, :attribute => :state)
404
+ @model = new_model do
405
+ alias_method :vehicle_status, :state
406
+ alias_method :vehicle_status=, :state=
407
+ end
408
+
409
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
284
410
  @machine.state :parked
285
411
 
286
412
  @record = @model.new
287
413
  end
288
414
 
289
415
  def test_should_add_validation_errors_to_custom_attribute
290
- @record.state = 'invalid'
416
+ @record.vehicle_status = 'invalid'
291
417
 
292
418
  assert !@record.valid?
293
- assert_equal ['is invalid'], @record.errors.on(:state)
419
+ assert_equal ['is invalid'], @record.errors.on(:vehicle_status)
294
420
 
295
- @record.state = 'parked'
421
+ @record.vehicle_status = 'parked'
296
422
  assert @record.valid?
297
423
  end
298
424
  end
@@ -389,6 +515,46 @@ begin
389
515
  end
390
516
  end
391
517
 
518
+ class MachineWithLoopbackTest < BaseTestCase
519
+ def setup
520
+ changed_columns = nil
521
+
522
+ @model = new_model do
523
+ # Simulate timestamps plugin
524
+ define_method(:before_update) do
525
+ changed_columns = self.changed_columns.dup
526
+
527
+ super()
528
+ self.updated_at = Time.now if changed_columns.any?
529
+ end
530
+ end
531
+
532
+ DB.alter_table :foo do
533
+ add_column :updated_at, :datetime
534
+ end
535
+ @model.class_eval { get_db_schema(true) }
536
+
537
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
538
+ @machine.event :park
539
+
540
+ @record = @model.create(:updated_at => Time.now - 1)
541
+ @timestamp = @record.updated_at
542
+
543
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
544
+ @transition.perform
545
+
546
+ @changed_columns = changed_columns
547
+ end
548
+
549
+ def test_should_include_state_in_changed_columns
550
+ assert_equal [:state], @changed_columns
551
+ end
552
+
553
+ def test_should_update_record
554
+ assert_not_equal @timestamp, @record.updated_at
555
+ end
556
+ end
557
+
392
558
  class MachineWithValidationsTest < BaseTestCase
393
559
  def setup
394
560
  @model = new_model
@@ -490,6 +656,109 @@ begin
490
656
  @record.valid?
491
657
  assert !ran_callback
492
658
  end
659
+
660
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
661
+ @model.class_eval do
662
+ attr_accessor :seatbelt
663
+ validates_presence_of :seatbelt
664
+ end
665
+
666
+ ran_callback = false
667
+ @machine.after_transition { ran_callback = true }
668
+
669
+ @record.valid?
670
+ assert !ran_callback
671
+ end
672
+
673
+ def test_should_run_after_callbacks_with_failures_enabled_if_validation_fails
674
+ @model.class_eval do
675
+ attr_accessor :seatbelt
676
+ validates_presence_of :seatbelt
677
+ end
678
+
679
+ ran_callback = false
680
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
681
+
682
+ @record.valid?
683
+ assert ran_callback
684
+ end
685
+ end
686
+
687
+ class MachineWithEventAttributesOnSaveTest < BaseTestCase
688
+ def setup
689
+ @model = new_model
690
+ @machine = StateMachine::Machine.new(@model)
691
+ @machine.event :ignite do
692
+ transition :parked => :idling
693
+ end
694
+
695
+ @record = @model.new
696
+ @record.state = 'parked'
697
+ @record.state_event = 'ignite'
698
+ end
699
+
700
+ def test_should_fail_if_event_is_invalid
701
+ @record.state_event = 'invalid'
702
+ assert !@record.save
703
+ end
704
+
705
+ def test_should_fail_if_event_has_no_transition
706
+ @record.state = 'idling'
707
+ assert !@record.save
708
+ end
709
+
710
+ def test_should_be_successful_if_event_has_transition
711
+ assert @record.save
712
+ end
713
+
714
+ def test_should_run_before_callbacks
715
+ ran_callback = false
716
+ @machine.before_transition { ran_callback = true }
717
+
718
+ @record.save
719
+ assert ran_callback
720
+ end
721
+
722
+ def test_should_run_before_callbacks_once
723
+ before_count = 0
724
+ @machine.before_transition { before_count += 1 }
725
+
726
+ @record.save
727
+ assert_equal 1, before_count
728
+ end
729
+
730
+ def test_should_persist_new_state
731
+ @record.save
732
+ assert_equal 'idling', @record.state
733
+ end
734
+
735
+ def test_should_run_after_callbacks
736
+ ran_callback = false
737
+ @machine.after_transition { ran_callback = true }
738
+
739
+ @record.save
740
+ assert ran_callback
741
+ end
742
+
743
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
744
+ @model.before_create {|record| false}
745
+
746
+ ran_callback = false
747
+ @machine.after_transition { ran_callback = true }
748
+
749
+ @record.save
750
+ assert !ran_callback
751
+ end
752
+
753
+ def test_should_run_after_callbacks_with_failures_enabled_if_fails
754
+ @model.before_create {|record| false}
755
+
756
+ ran_callback = false
757
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
758
+
759
+ @record.save
760
+ assert ran_callback
761
+ end
493
762
  end
494
763
 
495
764
  class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
@@ -14,37 +14,60 @@ class MachineCollectionStateInitializationTest < Test::Unit::TestCase
14
14
  def setup
15
15
  @machines = StateMachine::MachineCollection.new
16
16
 
17
- @klass = Class.new do
18
- def initialize(attributes = {})
19
- attributes.each do |attribute, value|
20
- self.send("#{attribute}=", value)
21
- end
22
-
23
- super()
24
- end
25
- end
17
+ @klass = Class.new
26
18
 
27
19
  @machines[:state] = StateMachine::Machine.new(@klass, :state, :initial => :parked)
28
- @machines[:alarm_state] = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active)
20
+ @machines[:alarm_state] = StateMachine::Machine.new(@klass, :alarm_state, :initial => lambda {|object| :active})
29
21
  @machines[:alarm_state].state :active, :value => lambda {'active'}
22
+
23
+ # Prevent the auto-initialization hook from firing
24
+ @klass.class_eval do
25
+ def initialize
26
+ end
27
+ end
28
+
29
+ @object = @klass.new
30
+ @object.state = nil
31
+ @object.alarm_state = nil
30
32
  end
31
33
 
32
34
  def test_should_set_states_if_nil
33
- object = @klass.new
34
- assert_equal 'parked', object.state
35
- assert_equal 'active', object.alarm_state
35
+ @machines.initialize_states(@object)
36
+
37
+ assert_equal 'parked', @object.state
38
+ assert_equal 'active', @object.alarm_state
36
39
  end
37
40
 
38
41
  def test_should_set_states_if_empty
39
- object = @klass.new(:state => '', :alarm_state => '')
40
- assert_equal 'parked', object.state
41
- assert_equal 'active', object.alarm_state
42
+ @object.state = ''
43
+ @object.alarm_state = ''
44
+ @machines.initialize_states(@object)
45
+
46
+ assert_equal 'parked', @object.state
47
+ assert_equal 'active', @object.alarm_state
42
48
  end
43
49
 
44
50
  def test_should_not_set_states_if_not_empty
45
- object = @klass.new(:state => 'idling', :alarm_state => 'off')
46
- assert_equal 'idling', object.state
47
- assert_equal 'off', object.alarm_state
51
+ @object.state = 'idling'
52
+ @object.alarm_state = 'off'
53
+ @machines.initialize_states(@object)
54
+
55
+ assert_equal 'idling', @object.state
56
+ assert_equal 'off', @object.alarm_state
57
+ end
58
+
59
+ def test_should_only_initialize_static_states_if_dynamic_disabled
60
+ @machines.initialize_states(@object, :dynamic => false)
61
+
62
+ assert_equal 'parked', @object.state
63
+ assert_nil @object.alarm_state
64
+ end
65
+
66
+ def test_should_only_initialize_dynamic_states_if_dynamic_enabled
67
+ @machines.initialize_states(@object, :dynamic => true)
68
+
69
+ assert_nil @object.state
70
+ assert_equal 'active', @object.alarm_state
48
71
  end
49
72
  end
50
73
 
@@ -207,6 +207,10 @@ class MachineWithStaticInitialStateTest < Test::Unit::TestCase
207
207
  @machine = StateMachine::Machine.new(@klass, :initial => :parked)
208
208
  end
209
209
 
210
+ def test_should_not_have_dynamic_initial_state
211
+ assert !@machine.dynamic_initial_state?
212
+ end
213
+
210
214
  def test_should_have_an_initial_state
211
215
  object = @klass.new
212
216
  assert_equal 'parked', @machine.initial_state(object).value
@@ -231,6 +235,20 @@ class MachineWithStaticInitialStateTest < Test::Unit::TestCase
231
235
  assert_equal 'idling', object.state
232
236
  end
233
237
 
238
+ def test_should_set_initial_state_prior_to_initialization
239
+ base = Class.new do
240
+ attr_accessor :state_on_init
241
+
242
+ def initialize
243
+ self.state_on_init = state
244
+ end
245
+ end
246
+ klass = Class.new(base)
247
+ machine = StateMachine::Machine.new(klass, :initial => :parked)
248
+
249
+ assert_equal 'parked', klass.new.state_on_init
250
+ end
251
+
234
252
  def test_should_be_included_in_known_states
235
253
  assert_equal [:parked], @machine.states.keys
236
254
  end
@@ -246,6 +264,10 @@ class MachineWithDynamicInitialStateTest < Test::Unit::TestCase
246
264
  @object = @klass.new
247
265
  end
248
266
 
267
+ def test_should_have_dynamic_initial_state
268
+ assert @machine.dynamic_initial_state?
269
+ end
270
+
249
271
  def test_should_use_the_record_for_determining_the_initial_state
250
272
  @object.initial_state = :parked
251
273
  assert_equal :parked, @machine.initial_state(@object).name
@@ -258,6 +280,21 @@ class MachineWithDynamicInitialStateTest < Test::Unit::TestCase
258
280
  assert_equal 'default', @object.state
259
281
  end
260
282
 
283
+ def test_should_set_initial_state_after_initialization
284
+ base = Class.new do
285
+ attr_accessor :state_on_init
286
+
287
+ def initialize
288
+ self.state_on_init = state
289
+ end
290
+ end
291
+ klass = Class.new(base)
292
+ machine = StateMachine::Machine.new(klass, :initial => lambda {|object| :parked})
293
+ machine.state :parked
294
+
295
+ assert_nil klass.new.state_on_init
296
+ end
297
+
261
298
  def test_should_not_be_included_in_known_states
262
299
  assert_equal [:parked, :idling, :default], @machine.states.map {|state| state.name}
263
300
  end
@@ -1375,6 +1412,24 @@ class MachineWithTransitionCallbacksTest < Test::Unit::TestCase
1375
1412
  assert_equal %w(before after), @object.callbacks
1376
1413
  end
1377
1414
 
1415
+ def test_should_allow_multiple_callbacks
1416
+ @machine.before_transition lambda {|object| object.callbacks << 'before1'}, lambda {|object| object.callbacks << 'before2'}
1417
+ @machine.after_transition lambda {|object| object.callbacks << 'after1'}, lambda {|object| object.callbacks << 'after2'}
1418
+
1419
+ @event.fire(@object)
1420
+ assert_equal %w(before1 before2 after1 after2), @object.callbacks
1421
+ end
1422
+
1423
+ def test_should_allow_multiple_callbacks_with_requirements
1424
+ @machine.before_transition lambda {|object| object.callbacks << 'before_parked1'}, lambda {|object| object.callbacks << 'before_parked2'}, :from => :parked
1425
+ @machine.before_transition lambda {|object| object.callbacks << 'before_idling1'}, lambda {|object| object.callbacks << 'before_idling2'}, :from => :idling
1426
+ @machine.after_transition lambda {|object| object.callbacks << 'after_parked1'}, lambda {|object| object.callbacks << 'after_parked2'}, :from => :parked
1427
+ @machine.after_transition lambda {|object| object.callbacks << 'after_idling1'}, lambda {|object| object.callbacks << 'after_idling2'}, :from => :idling
1428
+
1429
+ @event.fire(@object)
1430
+ assert_equal %w(before_parked1 before_parked2 after_parked1 after_parked2), @object.callbacks
1431
+ end
1432
+
1378
1433
  def test_should_support_from_requirement
1379
1434
  @machine.before_transition :from => :parked, :do => lambda {|object| object.callbacks << :parked}
1380
1435
  @machine.before_transition :from => :idling, :do => lambda {|object| object.callbacks << :idling}