state_machine 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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."