state_machine 0.8.0 → 0.8.1

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.
@@ -18,14 +18,16 @@ begin
18
18
 
19
19
  protected
20
20
  # Creates a new Sequel model (and the associated table)
21
- def new_model(auto_migrate = true, &block)
22
- DB.create_table! :foo do
21
+ def new_model(create_table = :foo, &block)
22
+ table_name = create_table || :foo
23
+
24
+ DB.create_table!(table_name) do
23
25
  primary_key :id
24
26
  column :state, :string
25
- end if auto_migrate
26
- model = Class.new(Sequel::Model(:foo)) do
27
+ end if create_table
28
+ model = Class.new(Sequel::Model(table_name)) do
27
29
  self.raise_on_save_failure = false
28
- def self.name; 'SequelTest::Foo'; end
30
+ def self.name; "SequelTest::#{table_name.to_s.capitalize}"; end
29
31
  end
30
32
  model.plugin(:validation_class_methods) if model.respond_to?(:plugin)
31
33
  model.plugin(:hook_class_methods) if model.respond_to?(:plugin)
@@ -42,146 +44,52 @@ begin
42
44
  def test_should_not_match_if_class_does_not_inherit_from_sequel
43
45
  assert !StateMachine::Integrations::Sequel.matches?(Class.new)
44
46
  end
47
+
48
+ def test_should_have_defaults
49
+ assert_equal e = {:action => :save}, StateMachine::Integrations::Sequel.defaults
50
+ end
45
51
  end
46
52
 
47
- class MachineByDefaultTest < BaseTestCase
53
+ class MachineWithoutDatabaseTest < BaseTestCase
48
54
  def setup
49
- @model = new_model
50
- @machine = StateMachine::Machine.new(@model)
55
+ @model = new_model(false)
51
56
  end
52
57
 
53
- def test_should_use_save_as_action
54
- assert_equal :save, @machine.action
58
+ def test_should_allow_machine_creation
59
+ assert_nothing_raised { StateMachine::Machine.new(@model) }
60
+ end
61
+ end
62
+
63
+ class MachineUnmigratedTest < BaseTestCase
64
+ def setup
65
+ @model = new_model(false)
55
66
  end
56
67
 
57
- def test_should_use_transactions
58
- assert_equal true, @machine.use_transactions
68
+ def test_should_allow_machine_creation
69
+ assert_nothing_raised { StateMachine::Machine.new(@model) }
59
70
  end
60
71
  end
61
72
 
62
- class MachineTest < BaseTestCase
73
+ class MachineByDefaultTest < BaseTestCase
63
74
  def setup
64
75
  @model = new_model
65
76
  @machine = StateMachine::Machine.new(@model)
66
- @machine.state :parked, :first_gear
67
- @machine.state :idling, :value => lambda {'idling'}
68
- end
69
-
70
- def test_should_create_singular_with_scope
71
- assert @model.respond_to?(:with_state)
72
- end
73
-
74
- def test_should_only_include_records_with_state_in_singular_with_scope
75
- parked = @model.create :state => 'parked'
76
- idling = @model.create :state => 'idling'
77
-
78
- assert_equal [parked], @model.with_state(:parked).all
79
- end
80
-
81
- def test_should_create_plural_with_scope
82
- assert @model.respond_to?(:with_states)
83
- end
84
-
85
- def test_should_only_include_records_with_states_in_plural_with_scope
86
- parked = @model.create :state => 'parked'
87
- idling = @model.create :state => 'idling'
88
-
89
- assert_equal [parked, idling], @model.with_states(:parked, :idling).all
90
- end
91
-
92
- def test_should_create_singular_without_scope
93
- assert @model.respond_to?(:without_state)
94
- end
95
-
96
- def test_should_only_include_records_without_state_in_singular_without_scope
97
- parked = @model.create :state => 'parked'
98
- idling = @model.create :state => 'idling'
99
-
100
- assert_equal [parked], @model.without_state(:idling).all
101
- end
102
-
103
- def test_should_create_plural_without_scope
104
- assert @model.respond_to?(:without_states)
105
- end
106
-
107
- def test_should_only_include_records_without_states_in_plural_without_scope
108
- parked = @model.create :state => 'parked'
109
- idling = @model.create :state => 'idling'
110
- first_gear = @model.create :state => 'first_gear'
111
-
112
- assert_equal [parked, idling], @model.without_states(:first_gear).all
113
- end
114
-
115
- def test_should_allow_chaining_scopes_and_fitlers
116
- parked = @model.create :state => 'parked'
117
- idling = @model.create :state => 'idling'
118
-
119
- assert_equal [idling], @model.without_state(:parked).filter(:state => 'idling').all
120
- end
121
-
122
- def test_should_rollback_transaction_if_false
123
- @machine.within_transaction(@model.new) do
124
- @model.create
125
- false
126
- end
127
-
128
- assert_equal 0, @model.count
129
77
  end
130
78
 
131
- def test_should_not_rollback_transaction_if_true
132
- @machine.within_transaction(@model.new) do
133
- @model.create
134
- true
135
- end
136
-
137
- assert_equal 1, @model.count
138
- end
139
-
140
- def test_should_invalidate_using_errors
141
- record = @model.new
142
- record.state = 'parked'
143
-
144
- @machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
145
-
146
- assert_equal ['cannot transition via "park"'], record.errors.on(:state)
147
- end
148
-
149
- def test_should_auto_prefix_custom_attributes_on_invalidation
150
- record = @model.new
151
- @machine.invalidate(record, :event, :invalid)
152
-
153
- assert_equal ['is invalid'], record.errors.on(:state_event)
154
- end
155
-
156
- def test_should_clear_errors_on_reset
157
- record = @model.new
158
- record.state = 'parked'
159
- record.errors.add(:state, 'is invalid')
160
-
161
- @machine.reset(record)
162
- assert_nil record.errors.on(:id)
79
+ def test_should_use_save_as_action
80
+ assert_equal :save, @machine.action
163
81
  end
164
82
 
165
- def test_should_not_override_the_column_reader
166
- record = @model.new
167
- record[:state] = 'parked'
168
- assert_equal 'parked', record.state
83
+ def test_should_use_transactions
84
+ assert_equal true, @machine.use_transactions
169
85
  end
170
86
 
171
- def test_should_not_override_the_column_writer
172
- record = @model.new
173
- record.state = 'parked'
174
- assert_equal 'parked', record[:state]
175
- end
176
- end
177
-
178
- class MachineUnmigratedTest < BaseTestCase
179
- def setup
180
- @model = new_model(false)
87
+ def test_should_not_have_any_before_callbacks
88
+ assert_equal 0, @machine.callbacks[:before].size
181
89
  end
182
90
 
183
- def test_should_allow_machine_creation
184
- assert_nothing_raised { StateMachine::Machine.new(@model) }
91
+ def test_should_not_have_any_after_callbacks
92
+ assert_equal 0, @machine.callbacks[:after].size
185
93
  end
186
94
  end
187
95
 
@@ -198,6 +106,17 @@ begin
198
106
  assert_equal 'parked', record.state
199
107
  end
200
108
 
109
+ def test_should_set_initial_state_with_nil_attributes
110
+ @model.class_eval do
111
+ def set(hash)
112
+ super(hash || {})
113
+ end
114
+ end
115
+
116
+ record = @model.new(nil)
117
+ assert_equal 'parked', record.state
118
+ end
119
+
201
120
  def test_should_still_set_attributes
202
121
  record = @model.new(:value => 1)
203
122
  assert_equal 1, record.value
@@ -212,11 +131,6 @@ begin
212
131
  assert_equal [record], block_args
213
132
  end
214
133
 
215
- def test_should_not_have_any_changed_columns
216
- record = @model.new
217
- assert record.changed_columns.empty?
218
- end
219
-
220
134
  def test_should_set_attributes_prior_to_after_initialize_hook
221
135
  state = nil
222
136
  @model.class_eval do
@@ -318,15 +232,94 @@ begin
318
232
  end
319
233
  end
320
234
 
235
+ class MachineWithColumnDefaultTest < BaseTestCase
236
+ def setup
237
+ @model = new_model
238
+ DB.alter_table :foo do
239
+ add_column :status, :string, :default => 'idling'
240
+ end
241
+ @model.class_eval { get_db_schema(true) }
242
+
243
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
244
+ @record = @model.new
245
+ end
246
+
247
+ def test_should_use_machine_default
248
+ assert_equal 'parked', @record.status
249
+ end
250
+ end
251
+
252
+ class MachineWithConflictingPredicateTest < BaseTestCase
253
+ def setup
254
+ @model = new_model do
255
+ def state?(*args)
256
+ true
257
+ end
258
+ end
259
+
260
+ @machine = StateMachine::Machine.new(@model)
261
+ @record = @model.new
262
+ end
263
+
264
+ def test_should_not_define_attribute_predicate
265
+ assert @record.state?
266
+ end
267
+ end
268
+
269
+ class MachineWithColumnStateAttributeTest < BaseTestCase
270
+ def setup
271
+ @model = new_model
272
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
273
+ @machine.other_states(:idling)
274
+
275
+ @record = @model.new
276
+ end
277
+
278
+ def test_should_not_override_the_column_reader
279
+ record = @model.new
280
+ record[:state] = 'parked'
281
+ assert_equal 'parked', record.state
282
+ end
283
+
284
+ def test_should_not_override_the_column_writer
285
+ record = @model.new
286
+ record.state = 'parked'
287
+ assert_equal 'parked', record[:state]
288
+ end
289
+
290
+ def test_should_have_an_attribute_predicate
291
+ assert @record.respond_to?(:state?)
292
+ end
293
+
294
+ def test_should_raise_exception_for_predicate_without_parameters
295
+ assert_raise(IndexError) { @record.state? }
296
+ end
297
+
298
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
299
+ assert !@record.state?(:idling)
300
+ end
301
+
302
+ def test_should_return_true_for_predicate_if_matches_current_value
303
+ assert @record.state?(:parked)
304
+ end
305
+
306
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
307
+ assert_raise(IndexError) { @record.state?(:invalid) }
308
+ end
309
+ end
310
+
321
311
  class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
322
312
  def setup
323
313
  @model = new_model do
324
314
  def initialize
325
315
  # Skip attribute initialization
316
+ @initialized_state_machines = true
317
+ super
326
318
  end
327
319
  end
328
320
 
329
- @machine = StateMachine::Machine.new(@model, :status, :initial => 'parked')
321
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
322
+ @machine.other_states(:idling)
330
323
  @record = @model.new
331
324
  end
332
325
 
@@ -349,77 +342,247 @@ begin
349
342
  attr_accessor :status
350
343
  end
351
344
 
352
- @machine = StateMachine::Machine.new(@model, :status, :initial => 'parked')
345
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
346
+ @machine.other_states(:idling)
353
347
  @record = @model.new
354
348
  end
355
349
 
350
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
351
+ assert !@record.status?(:idling)
352
+ end
353
+
354
+ def test_should_return_true_for_predicate_if_matches_current_value
355
+ assert @record.status?(:parked)
356
+ end
357
+
356
358
  def test_should_set_initial_state_on_created_object
357
359
  assert_equal 'parked', @record.status
358
360
  end
359
361
  end
360
362
 
361
- class MachineWithComplexPluralizationTest < BaseTestCase
363
+ class MachineWithInitializedStateTest < BaseTestCase
362
364
  def setup
363
365
  @model = new_model
364
- @machine = StateMachine::Machine.new(@model, :status)
366
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
367
+ @machine.state nil, :idling
365
368
  end
366
369
 
367
- def test_should_create_singular_with_scope
368
- assert @model.respond_to?(:with_status)
370
+ def test_should_allow_nil_initial_state_when_static
371
+ record = @model.new(:state => nil)
372
+ assert_nil record.state
369
373
  end
370
374
 
371
- def test_should_create_plural_with_scope
372
- assert @model.respond_to?(:with_statuses)
375
+ def test_should_allow_nil_initial_state_when_dynamic
376
+ @machine.initial_state = lambda {:parked}
377
+ record = @model.new(:state => nil)
378
+ assert_nil record.state
379
+ end
380
+
381
+ def test_should_allow_different_initial_state_when_static
382
+ record = @model.new(:state => 'idling')
383
+ assert_equal 'idling', record.state
384
+ end
385
+
386
+ def test_should_allow_different_initial_state_when_dynamic
387
+ @machine.initial_state = lambda {:parked}
388
+ record = @model.new(:state => 'idling')
389
+ assert_equal 'idling', record.state
390
+ end
391
+
392
+ def test_should_use_default_state_if_protected
393
+ @model.class_eval do
394
+ self.strict_param_setting = false
395
+ set_restricted_columns :state
396
+ end
397
+
398
+ record = @model.new(:state => 'idling')
399
+ assert_equal 'parked', record.state
400
+ end
401
+ end
402
+
403
+ class MachineWithAliasedAttributeTest < BaseTestCase
404
+ def setup
405
+ @model = new_model do
406
+ alias_method :vehicle_status, :state
407
+ alias_method :vehicle_status=, :state=
408
+ end
409
+
410
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
411
+ @machine.state :parked
412
+
413
+ @record = @model.new
414
+ end
415
+
416
+ def test_should_add_validation_errors_to_custom_attribute
417
+ @record.vehicle_status = 'invalid'
418
+
419
+ assert !@record.valid?
420
+ assert_equal ['is invalid'], @record.errors.on(:vehicle_status)
421
+
422
+ @record.vehicle_status = 'parked'
423
+ assert @record.valid?
424
+ end
425
+ end
426
+
427
+ class MachineWithLoopbackTest < BaseTestCase
428
+ def setup
429
+ @model = new_model do
430
+ # Simulate timestamps plugin
431
+ define_method(:before_update) do
432
+ changed_columns = self.changed_columns.dup
433
+
434
+ super()
435
+ self.updated_at = Time.now if changed_columns.any?
436
+ end
437
+ end
438
+
439
+ DB.alter_table :foo do
440
+ add_column :updated_at, :datetime
441
+ end
442
+ @model.class_eval { get_db_schema(true) }
443
+
444
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
445
+ @machine.event :park
446
+
447
+ @record = @model.create(:updated_at => Time.now - 1)
448
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
449
+
450
+ @timestamp = @record.updated_at
451
+ @transition.perform
452
+ end
453
+
454
+ def test_should_update_record
455
+ assert_not_equal @timestamp, @record.updated_at
373
456
  end
374
457
  end
375
458
 
376
- class MachineWithOwnerSubclassTest < BaseTestCase
459
+ class MachineWithDirtyAttributesTest < BaseTestCase
377
460
  def setup
378
461
  @model = new_model
379
- @machine = StateMachine::Machine.new(@model, :state)
462
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
463
+ @machine.event :ignite
464
+ @machine.state :idling
380
465
 
381
- @subclass = Class.new(@model)
382
- @subclass_machine = @subclass.state_machine(:state) {}
383
- @subclass_machine.state :parked, :idling, :first_gear
466
+ @record = @model.create
467
+
468
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
469
+ @transition.perform(false)
384
470
  end
385
471
 
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'
472
+ def test_should_include_state_in_changed_attributes
473
+ assert_equal [:state], @record.changed_columns
474
+ end
475
+ end
476
+
477
+ class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase
478
+ def setup
479
+ @model = new_model
480
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
481
+ @machine.event :park
389
482
 
390
- assert_equal [parked, idling], @subclass.with_states(:parked, :idling).all
483
+ @record = @model.create
484
+
485
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
486
+ @transition.perform(false)
391
487
  end
392
488
 
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'
489
+ def test_should_include_state_in_changed_attributes
490
+ assert_equal [:state], @record.changed_columns
491
+ end
492
+ end
493
+
494
+ class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase
495
+ def setup
496
+ @model = new_model
497
+ DB.alter_table :foo do
498
+ add_column :status, :string, :default => 'idling'
499
+ end
500
+ @model.class_eval { get_db_schema(true) }
397
501
 
398
- assert_equal [parked, idling], @subclass.without_states(:first_gear).all
502
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
503
+ @machine.event :ignite
504
+ @machine.state :idling
505
+
506
+ @record = @model.create
507
+
508
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
509
+ @transition.perform(false)
510
+ end
511
+
512
+ def test_should_include_state_in_changed_attributes
513
+ assert_equal [:status], @record.changed_columns
514
+ end
515
+ end
516
+
517
+ class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase
518
+ def setup
519
+ @model = new_model
520
+ DB.alter_table :foo do
521
+ add_column :status, :string, :default => 'idling'
522
+ end
523
+ @model.class_eval { get_db_schema(true) }
524
+
525
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
526
+ @machine.event :park
527
+
528
+ @record = @model.create
529
+
530
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
531
+ @transition.perform(false)
532
+ end
533
+
534
+ def test_should_include_state_in_changed_attributes
535
+ assert_equal [:status], @record.changed_columns
536
+ end
537
+ end
538
+
539
+ class MachineWithoutTransactionsTest < BaseTestCase
540
+ def setup
541
+ @model = new_model
542
+ @machine = StateMachine::Machine.new(@model, :use_transactions => false)
543
+ end
544
+
545
+ def test_should_not_rollback_transaction_if_false
546
+ @machine.within_transaction(@model.new) do
547
+ @model.create
548
+ false
549
+ end
550
+
551
+ assert_equal 1, @model.count
552
+ end
553
+
554
+ def test_should_not_rollback_transaction_if_true
555
+ @machine.within_transaction(@model.new) do
556
+ @model.create
557
+ true
558
+ end
559
+
560
+ assert_equal 1, @model.count
399
561
  end
400
562
  end
401
563
 
402
- class MachineWithCustomAttributeTest < BaseTestCase
564
+ class MachineWithTransactionsTest < BaseTestCase
403
565
  def setup
404
- @model = new_model do
405
- alias_method :vehicle_status, :state
406
- alias_method :vehicle_status=, :state=
566
+ @model = new_model
567
+ @machine = StateMachine::Machine.new(@model, :use_transactions => true)
568
+ end
569
+
570
+ def test_should_rollback_transaction_if_false
571
+ @machine.within_transaction(@model.new) do
572
+ @model.create
573
+ false
407
574
  end
408
575
 
409
- @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
410
- @machine.state :parked
411
-
412
- @record = @model.new
576
+ assert_equal 0, @model.count
413
577
  end
414
578
 
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)
579
+ def test_should_not_rollback_transaction_if_true
580
+ @machine.within_transaction(@model.new) do
581
+ @model.create
582
+ true
583
+ end
420
584
 
421
- @record.vehicle_status = 'parked'
422
- assert @record.valid?
585
+ assert_equal 1, @model.count
423
586
  end
424
587
  end
425
588
 
@@ -429,6 +592,7 @@ begin
429
592
  @machine = StateMachine::Machine.new(@model)
430
593
  @machine.state :parked, :idling
431
594
  @machine.event :ignite
595
+
432
596
  @record = @model.new(:state => 'parked')
433
597
  @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
434
598
  end
@@ -515,43 +679,133 @@ begin
515
679
  end
516
680
  end
517
681
 
518
- class MachineWithLoopbackTest < BaseTestCase
682
+ class MachineWithFailedBeforeCallbacksTest < BaseTestCase
519
683
  def setup
520
- changed_columns = nil
684
+ before_count = 0
685
+ after_count = 0
521
686
 
687
+ @model = new_model
688
+ @machine = StateMachine::Machine.new(@model)
689
+ @machine.state :parked, :idling
690
+ @machine.event :ignite
691
+ @machine.before_transition(lambda {before_count += 1; false})
692
+ @machine.before_transition(lambda {before_count += 1})
693
+ @machine.after_transition(lambda {after_count += 1})
694
+
695
+ @record = @model.new(:state => 'parked')
696
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
697
+ @result = @transition.perform
698
+
699
+ @before_count = before_count
700
+ @after_count = after_count
701
+ end
702
+
703
+ def test_should_not_be_successful
704
+ assert !@result
705
+ end
706
+
707
+ def test_should_not_change_current_state
708
+ assert_equal 'parked', @record.state
709
+ end
710
+
711
+ def test_should_not_run_action
712
+ assert @record.new?
713
+ end
714
+
715
+ def test_should_not_run_further_before_callbacks
716
+ assert_equal 1, @before_count
717
+ end
718
+
719
+ def test_should_not_run_after_callbacks
720
+ assert_equal 0, @after_count
721
+ end
722
+ end
723
+
724
+ class MachineWithFailedActionTest < BaseTestCase
725
+ def setup
522
726
  @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?
727
+ validates_each :state do |object, attribute, value|
728
+ object.errors[attribute] << 'is invalid' unless %w(first_gear).include?(value)
529
729
  end
530
730
  end
531
731
 
532
- DB.alter_table :foo do
533
- add_column :updated_at, :datetime
534
- end
535
- @model.class_eval { get_db_schema(true) }
732
+ @machine = StateMachine::Machine.new(@model)
733
+ @machine.state :parked, :idling
734
+ @machine.event :ignite
536
735
 
537
- @machine = StateMachine::Machine.new(@model, :initial => :parked)
538
- @machine.event :park
736
+ before_transition_called = false
737
+ after_transition_called = false
738
+ after_transition_with_failures_called = false
739
+ @machine.before_transition(lambda {before_transition_called = true})
740
+ @machine.after_transition(lambda {after_transition_called = true})
741
+ @machine.after_transition(lambda {after_transition_with_failures_called = true}, :include_failures => true)
539
742
 
540
- @record = @model.create(:updated_at => Time.now - 1)
541
- @timestamp = @record.updated_at
743
+ @record = @model.new(:state => 'parked')
744
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
745
+ @result = @transition.perform
542
746
 
543
- @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
544
- @transition.perform
747
+ @before_transition_called = before_transition_called
748
+ @after_transition_called = after_transition_called
749
+ @after_transition_with_failures_called = after_transition_with_failures_called
750
+ end
751
+
752
+ def test_should_not_be_successful
753
+ assert !@result
754
+ end
755
+
756
+ def test_should_not_change_current_state
757
+ assert_equal 'parked', @record.state
758
+ end
759
+
760
+ def test_should_not_save_record
761
+ assert @record.new?
762
+ end
763
+
764
+ def test_should_run_before_callback
765
+ assert @before_transition_called
766
+ end
767
+
768
+ def test_should_not_run_after_callback_if_not_including_failures
769
+ assert !@after_transition_called
770
+ end
771
+
772
+ def test_should_run_after_callback_if_including_failures
773
+ assert @after_transition_with_failures_called
774
+ end
775
+ end
776
+
777
+ class MachineWithFailedAfterCallbacksTest < BaseTestCase
778
+ def setup
779
+ after_count = 0
780
+
781
+ @model = new_model
782
+ @machine = StateMachine::Machine.new(@model)
783
+ @machine.state :parked, :idling
784
+ @machine.event :ignite
785
+ @machine.after_transition(lambda {after_count += 1; false})
786
+ @machine.after_transition(lambda {after_count += 1})
787
+
788
+ @record = @model.new(:state => 'parked')
789
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
790
+ @result = @transition.perform
545
791
 
546
- @changed_columns = changed_columns
792
+ @after_count = after_count
547
793
  end
548
794
 
549
- def test_should_include_state_in_changed_columns
550
- assert_equal [:state], @changed_columns
795
+ def test_should_be_successful
796
+ assert @result
551
797
  end
552
798
 
553
- def test_should_update_record
554
- assert_not_equal @timestamp, @record.updated_at
799
+ def test_should_change_current_state
800
+ assert_equal 'idling', @record.state
801
+ end
802
+
803
+ def test_should_save_record
804
+ assert !@record.new?
805
+ end
806
+
807
+ def test_should_not_run_further_after_callbacks
808
+ assert_equal 1, @after_count
555
809
  end
556
810
  end
557
811
 
@@ -564,6 +818,27 @@ begin
564
818
  @record = @model.new
565
819
  end
566
820
 
821
+ def test_should_invalidate_using_errors
822
+ @record.state = 'parked'
823
+
824
+ @machine.invalidate(@record, :state, :invalid_transition, [[:event, :park]])
825
+ assert_equal ['cannot transition via "park"'], @record.errors.on(:state)
826
+ end
827
+
828
+ def test_should_auto_prefix_custom_attributes_on_invalidation
829
+ @machine.invalidate(@record, :event, :invalid)
830
+
831
+ assert_equal ['is invalid'], @record.errors.on(:state_event)
832
+ end
833
+
834
+ def test_should_clear_errors_on_reset
835
+ @record.state = 'parked'
836
+ @record.errors.add(:state, 'is invalid')
837
+
838
+ @machine.reset(@record)
839
+ assert_nil @record.errors.on(:id)
840
+ end
841
+
567
842
  def test_should_be_valid_if_state_is_known
568
843
  @record.state = 'parked'
569
844
 
@@ -578,6 +853,30 @@ begin
578
853
  end
579
854
  end
580
855
 
856
+ class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
857
+ def setup
858
+ @model = new_model do
859
+ alias_method :status, :state
860
+ alias_method :status=, :state=
861
+ end
862
+
863
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :state)
864
+ @machine.state :parked
865
+
866
+ @record = @model.new
867
+ end
868
+
869
+ def test_should_add_validation_errors_to_custom_attribute
870
+ @record.state = 'invalid'
871
+
872
+ assert !@record.valid?
873
+ assert_equal ['state is invalid'], @record.errors.full_messages
874
+
875
+ @record.state = 'parked'
876
+ assert @record.valid?
877
+ end
878
+ end
879
+
581
880
  class MachineWithStateDrivenValidationsTest < BaseTestCase
582
881
  def setup
583
882
  @model = new_model do
@@ -794,6 +1093,146 @@ begin
794
1093
  assert_equal 'idling', @record.state
795
1094
  end
796
1095
  end
1096
+
1097
+ class MachineWithScopesTest < BaseTestCase
1098
+ def setup
1099
+ @model = new_model
1100
+ @machine = StateMachine::Machine.new(@model)
1101
+ @machine.state :parked, :first_gear
1102
+ @machine.state :idling, :value => lambda {'idling'}
1103
+ end
1104
+
1105
+ def test_should_create_singular_with_scope
1106
+ assert @model.respond_to?(:with_state)
1107
+ end
1108
+
1109
+ def test_should_only_include_records_with_state_in_singular_with_scope
1110
+ parked = @model.create :state => 'parked'
1111
+ idling = @model.create :state => 'idling'
1112
+
1113
+ assert_equal [parked], @model.with_state(:parked).all
1114
+ end
1115
+
1116
+ def test_should_create_plural_with_scope
1117
+ assert @model.respond_to?(:with_states)
1118
+ end
1119
+
1120
+ def test_should_only_include_records_with_states_in_plural_with_scope
1121
+ parked = @model.create :state => 'parked'
1122
+ idling = @model.create :state => 'idling'
1123
+
1124
+ assert_equal [parked, idling], @model.with_states(:parked, :idling).all
1125
+ end
1126
+
1127
+ def test_should_create_singular_without_scope
1128
+ assert @model.respond_to?(:without_state)
1129
+ end
1130
+
1131
+ def test_should_only_include_records_without_state_in_singular_without_scope
1132
+ parked = @model.create :state => 'parked'
1133
+ idling = @model.create :state => 'idling'
1134
+
1135
+ assert_equal [parked], @model.without_state(:idling).all
1136
+ end
1137
+
1138
+ def test_should_create_plural_without_scope
1139
+ assert @model.respond_to?(:without_states)
1140
+ end
1141
+
1142
+ def test_should_only_include_records_without_states_in_plural_without_scope
1143
+ parked = @model.create :state => 'parked'
1144
+ idling = @model.create :state => 'idling'
1145
+ first_gear = @model.create :state => 'first_gear'
1146
+
1147
+ assert_equal [parked, idling], @model.without_states(:first_gear).all
1148
+ end
1149
+
1150
+ def test_should_allow_chaining_scopes_and_filters
1151
+ parked = @model.create :state => 'parked'
1152
+ idling = @model.create :state => 'idling'
1153
+
1154
+ assert_equal [idling], @model.without_state(:parked).filter(:state => 'idling').all
1155
+ end
1156
+ end
1157
+
1158
+ class MachineWithScopesAndOwnerSubclassTest < BaseTestCase
1159
+ def setup
1160
+ @model = new_model
1161
+ @machine = StateMachine::Machine.new(@model, :state)
1162
+
1163
+ @subclass = Class.new(@model)
1164
+ @subclass_machine = @subclass.state_machine(:state) {}
1165
+ @subclass_machine.state :parked, :idling, :first_gear
1166
+ end
1167
+
1168
+ def test_should_only_include_records_with_subclass_states_in_with_scope
1169
+ parked = @subclass.create :state => 'parked'
1170
+ idling = @subclass.create :state => 'idling'
1171
+
1172
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling).all
1173
+ end
1174
+
1175
+ def test_should_only_include_records_without_subclass_states_in_without_scope
1176
+ parked = @subclass.create :state => 'parked'
1177
+ idling = @subclass.create :state => 'idling'
1178
+ first_gear = @subclass.create :state => 'first_gear'
1179
+
1180
+ assert_equal [parked, idling], @subclass.without_states(:first_gear).all
1181
+ end
1182
+ end
1183
+
1184
+ class MachineWithComplexPluralizationScopesTest < BaseTestCase
1185
+ def setup
1186
+ @model = new_model
1187
+ @machine = StateMachine::Machine.new(@model, :status)
1188
+ end
1189
+
1190
+ def test_should_create_singular_with_scope
1191
+ assert @model.respond_to?(:with_status)
1192
+ end
1193
+
1194
+ def test_should_create_plural_with_scope
1195
+ assert @model.respond_to?(:with_statuses)
1196
+ end
1197
+ end
1198
+
1199
+ class MachineWithScopesAndJoinsTest < BaseTestCase
1200
+ def setup
1201
+ @company = new_model(:company)
1202
+ SequelTest.const_set('Company', @company)
1203
+
1204
+ @vehicle = new_model(:vehicle) do
1205
+ many_to_one :company, :class => SequelTest::Company
1206
+ end
1207
+ DB.alter_table :vehicle do
1208
+ add_column :company_id, :integer
1209
+ end
1210
+ @vehicle.class_eval { get_db_schema(true) }
1211
+ SequelTest.const_set('Vehicle', @vehicle)
1212
+
1213
+ @company_machine = StateMachine::Machine.new(@company, :initial => :active)
1214
+ @vehicle_machine = StateMachine::Machine.new(@vehicle, :initial => :parked)
1215
+ @vehicle_machine.state :idling
1216
+
1217
+ @ford = @company.create
1218
+ @mustang = @vehicle.create(:company => @ford)
1219
+ end
1220
+
1221
+ def test_should_find_records_in_with_scope
1222
+ assert_equal [@mustang], @vehicle.with_states(:parked).join(:company, :id => :company_id).filter(:company__state => 'active').select(:vehicle.*).all
1223
+ end
1224
+
1225
+ def test_should_find_records_in_without_scope
1226
+ assert_equal [@mustang], @vehicle.without_states(:idling).join(:company, :id => :company_id).filter(:company__state => 'active').select(:vehicle.*).all
1227
+ end
1228
+
1229
+ def teardown
1230
+ SequelTest.class_eval do
1231
+ remove_const('Vehicle')
1232
+ remove_const('Company')
1233
+ end
1234
+ end
1235
+ end
797
1236
  end
798
1237
  rescue LoadError
799
1238
  $stderr.puts "Skipping Sequel tests. `gem install sequel#{" -v #{ENV['SEQUEL_VERSION']}" if ENV['SEQUEL_VERSION']}` and try again."