state_machine 0.7.6 → 0.8.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.
@@ -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}