pluginaweek-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.
@@ -25,7 +25,7 @@ begin
25
25
  storage_names[:default] = 'foo'
26
26
  def self.name; 'DataMapperTest::Foo'; end
27
27
 
28
- property :id, Integer, :serial => true
28
+ property :id, DataMapper::Types::Serial
29
29
  property :state, String
30
30
 
31
31
  auto_migrate! if auto_migrate
@@ -232,6 +232,57 @@ begin
232
232
  end
233
233
  end
234
234
 
235
+ class MachineWithOwnerSubclassTest < BaseTestCase
236
+ def setup
237
+ @resource = new_resource
238
+ @machine = StateMachine::Machine.new(@resource, :state)
239
+
240
+ @subclass = Class.new(@resource)
241
+ @subclass_machine = @subclass.state_machine(:state) {}
242
+ @subclass_machine.state :parked, :idling, :first_gear
243
+ end
244
+
245
+ def test_should_only_include_records_with_subclass_states_in_with_scope
246
+ parked = @subclass.create :state => 'parked'
247
+ idling = @subclass.create :state => 'idling'
248
+
249
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling)
250
+ end
251
+
252
+ def test_should_only_include_records_without_subclass_states_in_without_scope
253
+ parked = @subclass.create :state => 'parked'
254
+ idling = @subclass.create :state => 'idling'
255
+ first_gear = @subclass.create :state => 'first_gear'
256
+
257
+ assert_equal [parked, idling], @subclass.without_states(:first_gear)
258
+ end
259
+ end
260
+
261
+ class MachineWithTransactionsTest < BaseTestCase
262
+ def setup
263
+ @resource = new_resource
264
+ @machine = StateMachine::Machine.new(@resource, :use_transactions => true)
265
+ end
266
+
267
+ def test_should_rollback_transaction_if_false
268
+ @machine.within_transaction(@resource.new) do
269
+ @resource.create
270
+ false
271
+ end
272
+
273
+ assert_equal 0, @resource.all.size
274
+ end
275
+
276
+ def test_should_not_rollback_transaction_if_true
277
+ @machine.within_transaction(@resource.new) do
278
+ @resource.create
279
+ true
280
+ end
281
+
282
+ assert_equal 1, @resource.all.size
283
+ end
284
+ end
285
+
235
286
  class MachineWithCallbacksTest < BaseTestCase
236
287
  def setup
237
288
  @resource = new_resource
@@ -324,6 +375,45 @@ begin
324
375
  end
325
376
  end
326
377
 
378
+ class MachineWithLoopbackTest < BaseTestCase
379
+ def setup
380
+ dirty_attributes = nil
381
+
382
+ @resource = new_resource do
383
+ property :updated_at, DateTime
384
+ auto_migrate!
385
+
386
+ # Simulate dm-timestamps
387
+ before :update do
388
+ dirty_attributes = self.dirty_attributes.dup
389
+
390
+ return unless dirty?
391
+ self.updated_at = DateTime.now
392
+ end
393
+ end
394
+
395
+ @machine = StateMachine::Machine.new(@resource, :initial => :parked)
396
+ @machine.event :park
397
+
398
+ @record = @resource.create(:updated_at => Time.now - 1)
399
+ @timestamp = @record.updated_at
400
+
401
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
402
+ @transition.perform
403
+
404
+ @dirty_attributes = dirty_attributes
405
+ end
406
+
407
+ def test_should_include_state_in_dirty_attributes
408
+ expected = {@resource.properties[:state] => 'parked'}
409
+ assert_equal expected, @dirty_attributes
410
+ end
411
+
412
+ def test_should_update_record
413
+ assert_not_equal @timestamp, @record.updated_at
414
+ end
415
+ end
416
+
327
417
  begin
328
418
  gem 'dm-observer', ENV['DM_VERSION'] ? "=#{ENV['DM_VERSION']}" : '>=0.9.4'
329
419
  require 'dm-observer'
@@ -572,6 +662,26 @@ begin
572
662
  end
573
663
  end
574
664
 
665
+ class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
666
+ def setup
667
+ @resource = new_resource
668
+ @machine = StateMachine::Machine.new(@resource, :status, :attribute => :state)
669
+ @machine.state :parked
670
+
671
+ @record = @resource.new
672
+ end
673
+
674
+ def test_should_add_validation_errors_to_custom_attribute
675
+ @record.state = 'invalid'
676
+
677
+ assert !@record.valid?
678
+ assert_equal ['is invalid'], @record.errors.on(:state)
679
+
680
+ @record.state = 'parked'
681
+ assert @record.valid?
682
+ end
683
+ end
684
+
575
685
  class MachineWithStateDrivenValidationsTest < BaseTestCase
576
686
  def setup
577
687
  @resource = new_resource do
@@ -650,6 +760,109 @@ begin
650
760
  @record.valid?
651
761
  assert !ran_callback
652
762
  end
763
+
764
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
765
+ @resource.class_eval do
766
+ attr_accessor :seatbelt
767
+ validates_present :seatbelt
768
+ end
769
+
770
+ ran_callback = false
771
+ @machine.after_transition { ran_callback = true }
772
+
773
+ @record.valid?
774
+ assert !ran_callback
775
+ end
776
+
777
+ def test_should_run_after_callbacks_with_failures_enabled_if_validation_fails
778
+ @resource.class_eval do
779
+ attr_accessor :seatbelt
780
+ validates_present :seatbelt
781
+ end
782
+
783
+ ran_callback = false
784
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
785
+
786
+ @record.valid?
787
+ assert ran_callback
788
+ end
789
+ end
790
+
791
+ class MachineWithEventAttributesOnSaveTest < BaseTestCase
792
+ def setup
793
+ @resource = new_resource
794
+ @machine = StateMachine::Machine.new(@resource)
795
+ @machine.event :ignite do
796
+ transition :parked => :idling
797
+ end
798
+
799
+ @record = @resource.new
800
+ @record.state = 'parked'
801
+ @record.state_event = 'ignite'
802
+ end
803
+
804
+ def test_should_fail_if_event_is_invalid
805
+ @record.state_event = 'invalid'
806
+ assert !@record.save
807
+ end
808
+
809
+ def test_should_fail_if_event_has_no_transition
810
+ @record.state = 'idling'
811
+ assert !@record.save
812
+ end
813
+
814
+ def test_should_be_successful_if_event_has_transition
815
+ assert_equal true, @record.save
816
+ end
817
+
818
+ def test_should_run_before_callbacks
819
+ ran_callback = false
820
+ @machine.before_transition { ran_callback = true }
821
+
822
+ @record.save
823
+ assert ran_callback
824
+ end
825
+
826
+ def test_should_run_before_callbacks_once
827
+ before_count = 0
828
+ @machine.before_transition { before_count += 1 }
829
+
830
+ @record.save
831
+ assert_equal 1, before_count
832
+ end
833
+
834
+ def test_should_persist_new_state
835
+ @record.save
836
+ assert_equal 'idling', @record.state
837
+ end
838
+
839
+ def test_should_run_after_callbacks
840
+ ran_callback = false
841
+ @machine.after_transition { ran_callback = true }
842
+
843
+ @record.save
844
+ assert ran_callback
845
+ end
846
+
847
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
848
+ @resource.before(:create) { throw :halt }
849
+
850
+ ran_callback = false
851
+ @machine.after_transition { ran_callback = true }
852
+
853
+ @record.save
854
+ assert !ran_callback
855
+ end
856
+
857
+ def test_should_run_after_callbacks_with_failures_enabled_if_fails
858
+ @resource.before(:create) { throw :halt }
859
+
860
+ ran_callback = false
861
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
862
+
863
+ @record.save
864
+ assert ran_callback
865
+ end
653
866
  end
654
867
 
655
868
  class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
@@ -25,10 +25,10 @@ begin
25
25
  end if auto_migrate
26
26
  model = Class.new(Sequel::Model(:foo)) do
27
27
  self.raise_on_save_failure = false
28
- plugin :validation_class_methods if respond_to?(:plugin)
29
-
30
28
  def self.name; 'SequelTest::Foo'; end
31
29
  end
30
+ model.plugin(:validation_class_methods) if model.respond_to?(:plugin)
31
+ model.plugin(:hook_class_methods) if model.respond_to?(:plugin)
32
32
  model.class_eval(&block) if block_given?
33
33
  model
34
34
  end
@@ -185,15 +185,136 @@ begin
185
185
  end
186
186
  end
187
187
 
188
- class MachineWithInitialStateTest < BaseTestCase
188
+ class MachineWithStaticInitialStateTest < BaseTestCase
189
189
  def setup
190
- @model = new_model
191
- @machine = StateMachine::Machine.new(@model, :initial => 'parked')
192
- @record = @model.new
190
+ @model = new_model do
191
+ attr_accessor :value
192
+ end
193
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
193
194
  end
194
195
 
195
196
  def test_should_set_initial_state_on_created_object
196
- 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
197
318
  end
198
319
  end
199
320
 
@@ -252,6 +373,56 @@ begin
252
373
  end
253
374
  end
254
375
 
376
+ class MachineWithOwnerSubclassTest < BaseTestCase
377
+ def setup
378
+ @model = new_model
379
+ @machine = StateMachine::Machine.new(@model, :state)
380
+
381
+ @subclass = Class.new(@model)
382
+ @subclass_machine = @subclass.state_machine(:state) {}
383
+ @subclass_machine.state :parked, :idling, :first_gear
384
+ end
385
+
386
+ def test_should_only_include_records_with_subclass_states_in_with_scope
387
+ parked = @subclass.create :state => 'parked'
388
+ idling = @subclass.create :state => 'idling'
389
+
390
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling).all
391
+ end
392
+
393
+ def test_should_only_include_records_without_subclass_states_in_without_scope
394
+ parked = @subclass.create :state => 'parked'
395
+ idling = @subclass.create :state => 'idling'
396
+ first_gear = @subclass.create :state => 'first_gear'
397
+
398
+ assert_equal [parked, idling], @subclass.without_states(:first_gear).all
399
+ end
400
+ end
401
+
402
+ class MachineWithCustomAttributeTest < BaseTestCase
403
+ def setup
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)
410
+ @machine.state :parked
411
+
412
+ @record = @model.new
413
+ end
414
+
415
+ def test_should_add_validation_errors_to_custom_attribute
416
+ @record.vehicle_status = 'invalid'
417
+
418
+ assert !@record.valid?
419
+ assert_equal ['is invalid'], @record.errors.on(:vehicle_status)
420
+
421
+ @record.vehicle_status = 'parked'
422
+ assert @record.valid?
423
+ end
424
+ end
425
+
255
426
  class MachineWithCallbacksTest < BaseTestCase
256
427
  def setup
257
428
  @model = new_model
@@ -344,6 +515,46 @@ begin
344
515
  end
345
516
  end
346
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
+
347
558
  class MachineWithValidationsTest < BaseTestCase
348
559
  def setup
349
560
  @model = new_model
@@ -445,6 +656,109 @@ begin
445
656
  @record.valid?
446
657
  assert !ran_callback
447
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
448
762
  end
449
763
 
450
764
  class MachineWithEventAttributesOnCustomActionTest < BaseTestCase