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.
- data/CHANGELOG.rdoc +19 -0
- data/README.rdoc +24 -4
- data/Rakefile +10 -16
- data/lib/state_machine.rb +1 -1
- data/lib/state_machine/event_collection.rb +4 -6
- data/lib/state_machine/integrations/active_record.rb +100 -23
- data/lib/state_machine/integrations/active_record/observer.rb +5 -1
- data/lib/state_machine/integrations/data_mapper.rb +22 -4
- data/lib/state_machine/integrations/sequel.rb +6 -7
- data/lib/state_machine/machine.rb +29 -10
- data/lib/state_machine/machine_collection.rb +20 -13
- data/lib/state_machine/state.rb +4 -4
- data/{tasks → lib/tasks}/state_machine.rake +0 -0
- data/{tasks → lib/tasks}/state_machine.rb +1 -1
- data/test/functional/state_machine_test.rb +21 -1
- data/test/unit/event_collection_test.rb +9 -0
- data/test/unit/event_test.rb +45 -1
- data/test/unit/guard_test.rb +1 -1
- data/test/unit/integrations/active_record_test.rb +651 -325
- data/test/unit/integrations/data_mapper_test.rb +954 -404
- data/test/unit/integrations/sequel_test.rb +628 -189
- data/test/unit/machine_collection_test.rb +223 -18
- data/test/unit/machine_test.rb +16 -13
- data/test/unit/state_test.rb +14 -15
- metadata +70 -78
@@ -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(
|
22
|
-
|
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
|
26
|
-
model = Class.new(Sequel::Model(
|
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;
|
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
|
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
|
54
|
-
|
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
|
58
|
-
|
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
|
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
|
132
|
-
@machine.
|
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
|
166
|
-
|
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
|
172
|
-
|
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
|
184
|
-
|
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 =>
|
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 =>
|
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
|
363
|
+
class MachineWithInitializedStateTest < BaseTestCase
|
362
364
|
def setup
|
363
365
|
@model = new_model
|
364
|
-
@machine = StateMachine::Machine.new(@model, :
|
366
|
+
@machine = StateMachine::Machine.new(@model, :initial => :parked)
|
367
|
+
@machine.state nil, :idling
|
365
368
|
end
|
366
369
|
|
367
|
-
def
|
368
|
-
|
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
|
372
|
-
|
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
|
459
|
+
class MachineWithDirtyAttributesTest < BaseTestCase
|
377
460
|
def setup
|
378
461
|
@model = new_model
|
379
|
-
@machine = StateMachine::Machine.new(@model, :
|
462
|
+
@machine = StateMachine::Machine.new(@model, :initial => :parked)
|
463
|
+
@machine.event :ignite
|
464
|
+
@machine.state :idling
|
380
465
|
|
381
|
-
@
|
382
|
-
|
383
|
-
@
|
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
|
387
|
-
|
388
|
-
|
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
|
-
|
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
|
394
|
-
|
395
|
-
|
396
|
-
|
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
|
-
|
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
|
564
|
+
class MachineWithTransactionsTest < BaseTestCase
|
403
565
|
def setup
|
404
|
-
@model = new_model
|
405
|
-
|
406
|
-
|
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
|
-
|
410
|
-
@machine.state :parked
|
411
|
-
|
412
|
-
@record = @model.new
|
576
|
+
assert_equal 0, @model.count
|
413
577
|
end
|
414
578
|
|
415
|
-
def
|
416
|
-
@
|
417
|
-
|
418
|
-
|
419
|
-
|
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
|
-
@
|
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
|
682
|
+
class MachineWithFailedBeforeCallbacksTest < BaseTestCase
|
519
683
|
def setup
|
520
|
-
|
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
|
-
|
524
|
-
|
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
|
-
|
533
|
-
|
534
|
-
|
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
|
-
|
538
|
-
|
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.
|
541
|
-
@
|
743
|
+
@record = @model.new(:state => 'parked')
|
744
|
+
@transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
|
745
|
+
@result = @transition.perform
|
542
746
|
|
543
|
-
@
|
544
|
-
@
|
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
|
-
@
|
792
|
+
@after_count = after_count
|
547
793
|
end
|
548
794
|
|
549
|
-
def
|
550
|
-
|
795
|
+
def test_should_be_successful
|
796
|
+
assert @result
|
551
797
|
end
|
552
798
|
|
553
|
-
def
|
554
|
-
|
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."
|