state_machine 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. data/CHANGELOG.rdoc +17 -0
  2. data/LICENSE +1 -1
  3. data/README.rdoc +162 -23
  4. data/Rakefile +3 -18
  5. data/lib/state_machine.rb +3 -4
  6. data/lib/state_machine/callback.rb +65 -13
  7. data/lib/state_machine/eval_helpers.rb +20 -4
  8. data/lib/state_machine/initializers.rb +4 -0
  9. data/lib/state_machine/initializers/merb.rb +1 -0
  10. data/lib/state_machine/initializers/rails.rb +7 -0
  11. data/lib/state_machine/integrations.rb +21 -6
  12. data/lib/state_machine/integrations/active_model.rb +414 -0
  13. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  14. data/lib/state_machine/integrations/{active_record → active_model}/observer.rb +7 -7
  15. data/lib/state_machine/integrations/active_record.rb +65 -129
  16. data/lib/state_machine/integrations/active_record/locale.rb +4 -11
  17. data/lib/state_machine/integrations/data_mapper.rb +24 -6
  18. data/lib/state_machine/integrations/data_mapper/observer.rb +36 -0
  19. data/lib/state_machine/integrations/mongo_mapper.rb +295 -0
  20. data/lib/state_machine/integrations/sequel.rb +33 -7
  21. data/lib/state_machine/machine.rb +121 -23
  22. data/lib/state_machine/machine_collection.rb +12 -103
  23. data/lib/state_machine/transition.rb +125 -164
  24. data/lib/state_machine/transition_collection.rb +244 -0
  25. data/lib/tasks/state_machine.rb +12 -15
  26. data/test/functional/state_machine_test.rb +11 -1
  27. data/test/unit/callback_test.rb +305 -32
  28. data/test/unit/eval_helpers_test.rb +103 -1
  29. data/test/unit/event_test.rb +2 -1
  30. data/test/unit/guard_test.rb +2 -1
  31. data/test/unit/integrations/active_model_test.rb +909 -0
  32. data/test/unit/integrations/active_record_test.rb +1542 -1292
  33. data/test/unit/integrations/data_mapper_test.rb +1369 -1041
  34. data/test/unit/integrations/mongo_mapper_test.rb +1349 -0
  35. data/test/unit/integrations/sequel_test.rb +1214 -985
  36. data/test/unit/integrations_test.rb +8 -0
  37. data/test/unit/machine_collection_test.rb +140 -513
  38. data/test/unit/machine_test.rb +212 -10
  39. data/test/unit/state_test.rb +2 -1
  40. data/test/unit/transition_collection_test.rb +2098 -0
  41. data/test/unit/transition_test.rb +704 -552
  42. metadata +16 -3
@@ -1,1239 +1,1468 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
2
2
 
3
- begin
4
- # Load library
5
- require 'rubygems'
3
+ # Load library
4
+ require 'rubygems'
5
+
6
+ gem 'sequel', ENV['VERSION'] ? "=#{ENV['VERSION']}" : '>=2.8.0'
7
+ require 'sequel'
8
+ require 'logger'
9
+
10
+ # Establish database connection
11
+ DB = Sequel.connect('sqlite:///', :loggers => [Logger.new("#{File.dirname(__FILE__)}/../../sequel.log")])
12
+
13
+ module SequelTest
14
+ class BaseTestCase < Test::Unit::TestCase
15
+ def default_test
16
+ end
17
+
18
+ protected
19
+ # Creates a new Sequel model (and the associated table)
20
+ def new_model(create_table = :foo, &block)
21
+ table_name = create_table || :foo
22
+
23
+ DB.create_table!(table_name) do
24
+ primary_key :id
25
+ column :state, :string
26
+ end if create_table
27
+ model = Class.new(Sequel::Model(table_name)) do
28
+ self.raise_on_save_failure = false
29
+ def self.name; "SequelTest::#{table_name.to_s.capitalize}"; end
30
+ end
31
+ model.plugin(:validation_class_methods) if model.respond_to?(:plugin)
32
+ model.plugin(:hook_class_methods) if model.respond_to?(:plugin)
33
+ model.class_eval(&block) if block_given?
34
+ model
35
+ end
36
+ end
6
37
 
7
- gem 'sequel', ENV['SEQUEL_VERSION'] ? "=#{ENV['SEQUEL_VERSION']}" : '>=2.8.0'
8
- require 'sequel'
9
- require 'logger'
38
+ class IntegrationTest < BaseTestCase
39
+ def test_should_match_if_class_inherits_from_sequel
40
+ assert StateMachine::Integrations::Sequel.matches?(new_model)
41
+ end
42
+
43
+ def test_should_not_match_if_class_does_not_inherit_from_sequel
44
+ assert !StateMachine::Integrations::Sequel.matches?(Class.new)
45
+ end
46
+
47
+ def test_should_have_defaults
48
+ assert_equal e = {:action => :save}, StateMachine::Integrations::Sequel.defaults
49
+ end
50
+ end
10
51
 
11
- # Establish database connection
12
- DB = Sequel.connect('sqlite:///', :loggers => [Logger.new("#{File.dirname(__FILE__)}/../../sequel.log")])
52
+ class MachineWithoutDatabaseTest < BaseTestCase
53
+ def setup
54
+ @model = new_model(false)
55
+ end
56
+
57
+ def test_should_allow_machine_creation
58
+ assert_nothing_raised { StateMachine::Machine.new(@model) }
59
+ end
60
+ end
13
61
 
14
- module SequelTest
15
- class BaseTestCase < Test::Unit::TestCase
16
- def default_test
17
- end
18
-
19
- protected
20
- # Creates a new Sequel model (and the associated table)
21
- def new_model(create_table = :foo, &block)
22
- table_name = create_table || :foo
23
-
24
- DB.create_table!(table_name) do
25
- primary_key :id
26
- column :state, :string
27
- end if create_table
28
- model = Class.new(Sequel::Model(table_name)) do
29
- self.raise_on_save_failure = false
30
- def self.name; "SequelTest::#{table_name.to_s.capitalize}"; end
31
- end
32
- model.plugin(:validation_class_methods) if model.respond_to?(:plugin)
33
- model.plugin(:hook_class_methods) if model.respond_to?(:plugin)
34
- model.class_eval(&block) if block_given?
35
- model
36
- end
62
+ class MachineUnmigratedTest < BaseTestCase
63
+ def setup
64
+ @model = new_model(false)
37
65
  end
38
66
 
39
- class IntegrationTest < BaseTestCase
40
- def test_should_match_if_class_inherits_from_sequel
41
- assert StateMachine::Integrations::Sequel.matches?(new_model)
42
- end
43
-
44
- def test_should_not_match_if_class_does_not_inherit_from_sequel
45
- assert !StateMachine::Integrations::Sequel.matches?(Class.new)
46
- end
47
-
48
- def test_should_have_defaults
49
- assert_equal e = {:action => :save}, StateMachine::Integrations::Sequel.defaults
50
- end
67
+ def test_should_allow_machine_creation
68
+ assert_nothing_raised { StateMachine::Machine.new(@model) }
69
+ end
70
+ end
71
+
72
+ class MachineByDefaultTest < BaseTestCase
73
+ def setup
74
+ @model = new_model
75
+ @machine = StateMachine::Machine.new(@model)
51
76
  end
52
77
 
53
- class MachineWithoutDatabaseTest < BaseTestCase
54
- def setup
55
- @model = new_model(false)
56
- end
57
-
58
- def test_should_allow_machine_creation
59
- assert_nothing_raised { StateMachine::Machine.new(@model) }
60
- end
78
+ def test_should_use_save_as_action
79
+ assert_equal :save, @machine.action
61
80
  end
62
81
 
63
- class MachineUnmigratedTest < BaseTestCase
64
- def setup
65
- @model = new_model(false)
66
- end
67
-
68
- def test_should_allow_machine_creation
69
- assert_nothing_raised { StateMachine::Machine.new(@model) }
70
- end
82
+ def test_should_use_transactions
83
+ assert_equal true, @machine.use_transactions
71
84
  end
72
85
 
73
- class MachineByDefaultTest < BaseTestCase
74
- def setup
75
- @model = new_model
76
- @machine = StateMachine::Machine.new(@model)
77
- end
78
-
79
- def test_should_use_save_as_action
80
- assert_equal :save, @machine.action
86
+ def test_should_not_have_any_before_callbacks
87
+ assert_equal 0, @machine.callbacks[:before].size
88
+ end
89
+
90
+ def test_should_not_have_any_after_callbacks
91
+ assert_equal 0, @machine.callbacks[:after].size
92
+ end
93
+ end
94
+
95
+ class MachineWithStaticInitialStateTest < BaseTestCase
96
+ def setup
97
+ @model = new_model do
98
+ attr_accessor :value
81
99
  end
82
-
83
- def test_should_use_transactions
84
- assert_equal true, @machine.use_transactions
100
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
101
+ end
102
+
103
+ def test_should_set_initial_state_on_created_object
104
+ record = @model.new
105
+ assert_equal 'parked', record.state
106
+ end
107
+
108
+ def test_should_set_initial_state_with_nil_attributes
109
+ @model.class_eval do
110
+ def set(hash)
111
+ super(hash || {})
112
+ end
85
113
  end
86
114
 
87
- def test_should_not_have_any_before_callbacks
88
- assert_equal 0, @machine.callbacks[:before].size
115
+ record = @model.new(nil)
116
+ assert_equal 'parked', record.state
117
+ end
118
+
119
+ def test_should_still_set_attributes
120
+ record = @model.new(:value => 1)
121
+ assert_equal 1, record.value
122
+ end
123
+
124
+ def test_should_still_allow_initialize_blocks
125
+ block_args = nil
126
+ record = @model.new do |*args|
127
+ block_args = args
89
128
  end
90
129
 
91
- def test_should_not_have_any_after_callbacks
92
- assert_equal 0, @machine.callbacks[:after].size
130
+ assert_equal [record], block_args
131
+ end
132
+
133
+ def test_should_set_attributes_prior_to_after_initialize_hook
134
+ state = nil
135
+ @model.class_eval do
136
+ define_method(:after_initialize) do
137
+ state = self.state
138
+ end
93
139
  end
140
+ @model.new
141
+ assert_equal 'parked', state
94
142
  end
95
143
 
96
- class MachineWithStaticInitialStateTest < BaseTestCase
97
- def setup
98
- @model = new_model do
99
- attr_accessor :value
144
+ def test_should_set_initial_state_before_setting_attributes
145
+ @model.class_eval do
146
+ attr_accessor :state_during_setter
147
+
148
+ define_method(:value=) do |value|
149
+ self.state_during_setter = state
100
150
  end
101
- @machine = StateMachine::Machine.new(@model, :initial => :parked)
102
151
  end
103
152
 
104
- def test_should_set_initial_state_on_created_object
105
- record = @model.new
106
- assert_equal 'parked', record.state
107
- end
153
+ record = @model.new(:value => 1)
154
+ assert_equal 'parked', record.state_during_setter
155
+ end
156
+
157
+ def test_should_not_set_initial_state_after_already_initialized
158
+ record = @model.new(:value => 1)
159
+ assert_equal 'parked', record.state
108
160
 
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
161
+ record.state = 'idling'
162
+ record.set({})
163
+ assert_equal 'idling', record.state
164
+ end
165
+
166
+ def test_should_use_stored_values_when_loading_from_database
167
+ @machine.state :idling
119
168
 
120
- def test_should_still_set_attributes
121
- record = @model.new(:value => 1)
122
- assert_equal 1, record.value
123
- end
169
+ record = @model[@model.create(:state => 'idling').id]
170
+ assert_equal 'idling', record.state
171
+ end
172
+
173
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
174
+ @machine.state nil
124
175
 
125
- def test_should_still_allow_initialize_blocks
126
- block_args = nil
127
- record = @model.new do |*args|
128
- block_args = args
129
- end
130
-
131
- assert_equal [record], block_args
176
+ record = @model[@model.create(:state => nil).id]
177
+ assert_nil record.state
178
+ end
179
+ end
180
+
181
+ class MachineWithDynamicInitialStateTest < BaseTestCase
182
+ def setup
183
+ @model = new_model do
184
+ attr_accessor :value
132
185
  end
133
-
134
- def test_should_set_attributes_prior_to_after_initialize_hook
135
- state = nil
136
- @model.class_eval do
137
- define_method(:after_initialize) do
138
- state = self.state
139
- end
140
- end
141
- @model.new
142
- assert_equal 'parked', state
186
+ @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked})
187
+ @machine.state :parked
188
+ end
189
+
190
+ def test_should_set_initial_state_on_created_object
191
+ record = @model.new
192
+ assert_equal 'parked', record.state
193
+ end
194
+
195
+ def test_should_still_set_attributes
196
+ record = @model.new(:value => 1)
197
+ assert_equal 1, record.value
198
+ end
199
+
200
+ def test_should_still_allow_initialize_blocks
201
+ block_args = nil
202
+ record = @model.new do |*args|
203
+ block_args = args
143
204
  end
144
205
 
145
- def test_should_set_initial_state_before_setting_attributes
146
- @model.class_eval do
147
- attr_accessor :state_during_setter
148
-
149
- define_method(:value=) do |value|
150
- self.state_during_setter = state
151
- end
206
+ assert_equal [record], block_args
207
+ end
208
+
209
+ def test_should_not_have_any_changed_columns
210
+ record = @model.new
211
+ assert record.changed_columns.empty?
212
+ end
213
+
214
+ def test_should_set_attributes_prior_to_after_initialize_hook
215
+ state = nil
216
+ @model.class_eval do
217
+ define_method(:after_initialize) do
218
+ state = self.state
152
219
  end
153
-
154
- record = @model.new(:value => 1)
155
- assert_equal 'parked', record.state_during_setter
156
- end
157
-
158
- def test_should_not_set_initial_state_after_already_initialized
159
- record = @model.new(:value => 1)
160
- assert_equal 'parked', record.state
161
-
162
- record.state = 'idling'
163
- record.set({})
164
- assert_equal 'idling', record.state
165
220
  end
221
+ @model.new
222
+ assert_equal 'parked', state
166
223
  end
167
224
 
168
- class MachineWithDynamicInitialStateTest < BaseTestCase
169
- def setup
170
- @model = new_model do
171
- attr_accessor :value
225
+ def test_should_set_initial_state_after_setting_attributes
226
+ @model.class_eval do
227
+ attr_accessor :state_during_setter
228
+
229
+ define_method(:value=) do |value|
230
+ self.state_during_setter = state || 'nil'
172
231
  end
173
- @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked})
174
- @machine.state :parked
175
232
  end
176
233
 
177
- def test_should_set_initial_state_on_created_object
178
- record = @model.new
179
- assert_equal 'parked', record.state
180
- end
234
+ record = @model.new(:value => 1)
235
+ assert_equal 'nil', record.state_during_setter
236
+ end
237
+
238
+ def test_should_not_set_initial_state_after_already_initialized
239
+ record = @model.new(:value => 1)
240
+ assert_equal 'parked', record.state
181
241
 
182
- def test_should_still_set_attributes
183
- record = @model.new(:value => 1)
184
- assert_equal 1, record.value
185
- end
242
+ record.state = 'idling'
243
+ record.set({})
244
+ assert_equal 'idling', record.state
245
+ end
246
+
247
+ def test_should_use_stored_values_when_loading_from_database
248
+ @machine.state :idling
186
249
 
187
- def test_should_still_allow_initialize_blocks
188
- block_args = nil
189
- record = @model.new do |*args|
190
- block_args = args
191
- end
192
-
193
- assert_equal [record], block_args
194
- end
250
+ record = @model[@model.create(:state => 'idling').id]
251
+ assert_equal 'idling', record.state
252
+ end
253
+
254
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
255
+ @machine.state nil
195
256
 
196
- def test_should_not_have_any_changed_columns
197
- record = @model.new
198
- assert record.changed_columns.empty?
257
+ record = @model[@model.create(:state => nil).id]
258
+ assert_nil record.state
259
+ end
260
+ end
261
+
262
+ class MachineWithColumnDefaultTest < BaseTestCase
263
+ def setup
264
+ @model = new_model
265
+ DB.alter_table :foo do
266
+ add_column :status, :string, :default => 'idling'
199
267
  end
268
+ @model.class_eval { get_db_schema(true) }
200
269
 
201
- def test_should_set_attributes_prior_to_after_initialize_hook
202
- state = nil
203
- @model.class_eval do
204
- define_method(:after_initialize) do
205
- state = self.state
206
- end
270
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
271
+ @record = @model.new
272
+ end
273
+
274
+ def test_should_use_machine_default
275
+ assert_equal 'parked', @record.status
276
+ end
277
+ end
278
+
279
+ class MachineWithConflictingPredicateTest < BaseTestCase
280
+ def setup
281
+ @model = new_model do
282
+ def state?(*args)
283
+ true
207
284
  end
208
- @model.new
209
- assert_equal 'parked', state
210
285
  end
211
286
 
212
- def test_should_set_initial_state_after_setting_attributes
213
- @model.class_eval do
214
- attr_accessor :state_during_setter
215
-
216
- define_method(:value=) do |value|
217
- self.state_during_setter = state || 'nil'
218
- end
219
- end
220
-
221
- record = @model.new(:value => 1)
222
- assert_equal 'nil', record.state_during_setter
223
- end
287
+ @machine = StateMachine::Machine.new(@model)
288
+ @record = @model.new
289
+ end
290
+
291
+ def test_should_not_define_attribute_predicate
292
+ assert @record.state?
293
+ end
294
+ end
295
+
296
+ class MachineWithColumnStateAttributeTest < BaseTestCase
297
+ def setup
298
+ @model = new_model
299
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
300
+ @machine.other_states(:idling)
224
301
 
225
- def test_should_not_set_initial_state_after_already_initialized
226
- record = @model.new(:value => 1)
227
- assert_equal 'parked', record.state
228
-
229
- record.state = 'idling'
230
- record.set({})
231
- assert_equal 'idling', record.state
232
- end
302
+ @record = @model.new
303
+ end
304
+
305
+ def test_should_not_override_the_column_reader
306
+ record = @model.new
307
+ record[:state] = 'parked'
308
+ assert_equal 'parked', record.state
309
+ end
310
+
311
+ def test_should_not_override_the_column_writer
312
+ record = @model.new
313
+ record.state = 'parked'
314
+ assert_equal 'parked', record[:state]
315
+ end
316
+
317
+ def test_should_have_an_attribute_predicate
318
+ assert @record.respond_to?(:state?)
233
319
  end
234
320
 
235
- class MachineWithColumnDefaultTest < BaseTestCase
236
- def setup
237
- @model = new_model
238
- DB.alter_table :foo do
239
- add_column :status, :string, :default => 'idling'
321
+ def test_should_raise_exception_for_predicate_without_parameters
322
+ assert_raise(IndexError) { @record.state? }
323
+ end
324
+
325
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
326
+ assert !@record.state?(:idling)
327
+ end
328
+
329
+ def test_should_return_true_for_predicate_if_matches_current_value
330
+ assert @record.state?(:parked)
331
+ end
332
+
333
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
334
+ assert_raise(IndexError) { @record.state?(:invalid) }
335
+ end
336
+ end
337
+
338
+ class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
339
+ def setup
340
+ @model = new_model do
341
+ def initialize
342
+ # Skip attribute initialization
343
+ @initialized_state_machines = true
344
+ super
240
345
  end
241
- @model.class_eval { get_db_schema(true) }
242
-
243
- @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
244
- @record = @model.new
245
346
  end
246
347
 
247
- def test_should_use_machine_default
248
- assert_equal 'parked', @record.status
249
- end
348
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
349
+ @machine.other_states(:idling)
350
+ @record = @model.new
250
351
  end
251
352
 
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
353
+ def test_should_not_define_a_reader_attribute_for_the_attribute
354
+ assert !@record.respond_to?(:status)
355
+ end
356
+
357
+ def test_should_not_define_a_writer_attribute_for_the_attribute
358
+ assert !@record.respond_to?(:status=)
359
+ end
360
+
361
+ def test_should_define_an_attribute_predicate
362
+ assert @record.respond_to?(:status?)
363
+ end
364
+ end
365
+
366
+ class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase
367
+ def setup
368
+ @model = new_model do
369
+ attr_accessor :status
262
370
  end
263
371
 
264
- def test_should_not_define_attribute_predicate
265
- assert @record.state?
266
- end
372
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
373
+ @machine.other_states(:idling)
374
+ @record = @model.new
267
375
  end
268
376
 
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
377
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
378
+ assert !@record.status?(:idling)
379
+ end
380
+
381
+ def test_should_return_true_for_predicate_if_matches_current_value
382
+ assert @record.status?(:parked)
383
+ end
384
+
385
+ def test_should_set_initial_state_on_created_object
386
+ assert_equal 'parked', @record.status
387
+ end
388
+ end
389
+
390
+ class MachineWithInitializedStateTest < BaseTestCase
391
+ def setup
392
+ @model = new_model
393
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
394
+ @machine.state nil, :idling
395
+ end
396
+
397
+ def test_should_allow_nil_initial_state_when_static
398
+ record = @model.new(:state => nil)
399
+ assert_nil record.state
400
+ end
401
+
402
+ def test_should_allow_nil_initial_state_when_dynamic
403
+ @machine.initial_state = lambda {:parked}
404
+ record = @model.new(:state => nil)
405
+ assert_nil record.state
406
+ end
407
+
408
+ def test_should_allow_different_initial_state_when_static
409
+ record = @model.new(:state => 'idling')
410
+ assert_equal 'idling', record.state
411
+ end
412
+
413
+ def test_should_allow_different_initial_state_when_dynamic
414
+ @machine.initial_state = lambda {:parked}
415
+ record = @model.new(:state => 'idling')
416
+ assert_equal 'idling', record.state
417
+ end
418
+
419
+ def test_should_use_default_state_if_protected
420
+ @model.class_eval do
421
+ self.strict_param_setting = false
422
+ set_restricted_columns :state
276
423
  end
277
424
 
278
- def test_should_not_override_the_column_reader
279
- record = @model.new
280
- record[:state] = 'parked'
281
- assert_equal 'parked', record.state
425
+ record = @model.new(:state => 'idling')
426
+ assert_equal 'parked', record.state
427
+ end
428
+ end
429
+
430
+ class MachineWithAliasedAttributeTest < BaseTestCase
431
+ def setup
432
+ @model = new_model do
433
+ alias_method :vehicle_status, :state
434
+ alias_method :vehicle_status=, :state=
282
435
  end
283
436
 
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
437
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
438
+ @machine.state :parked
289
439
 
290
- def test_should_have_an_attribute_predicate
291
- assert @record.respond_to?(:state?)
292
- end
440
+ @record = @model.new
441
+ end
442
+
443
+ def test_should_add_validation_errors_to_custom_attribute
444
+ @record.vehicle_status = 'invalid'
293
445
 
294
- def test_should_raise_exception_for_predicate_without_parameters
295
- assert_raise(IndexError) { @record.state? }
296
- end
446
+ assert !@record.valid?
447
+ assert_equal ['is invalid'], @record.errors.on(:vehicle_status)
297
448
 
298
- def test_should_return_false_for_predicate_if_does_not_match_current_value
299
- assert !@record.state?(:idling)
449
+ @record.vehicle_status = 'parked'
450
+ assert @record.valid?
451
+ end
452
+ end
453
+
454
+ class MachineWithLoopbackTest < BaseTestCase
455
+ def setup
456
+ @model = new_model do
457
+ # Simulate timestamps plugin
458
+ define_method(:before_update) do
459
+ changed_columns = self.changed_columns.dup
460
+
461
+ super()
462
+ self.updated_at = Time.now if changed_columns.any?
463
+ end
300
464
  end
301
465
 
302
- def test_should_return_true_for_predicate_if_matches_current_value
303
- assert @record.state?(:parked)
466
+ DB.alter_table :foo do
467
+ add_column :updated_at, :datetime
304
468
  end
469
+ @model.class_eval { get_db_schema(true) }
305
470
 
306
- def test_should_raise_exception_for_predicate_if_invalid_state_specified
307
- assert_raise(IndexError) { @record.state?(:invalid) }
308
- end
471
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
472
+ @machine.event :park
473
+
474
+ @record = @model.create(:updated_at => Time.now - 1)
475
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
476
+
477
+ @timestamp = @record.updated_at
478
+ @transition.perform
309
479
  end
310
480
 
311
- class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
312
- def setup
313
- @model = new_model do
314
- def initialize
315
- # Skip attribute initialization
316
- @initialized_state_machines = true
317
- super
318
- end
319
- end
320
-
321
- @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
322
- @machine.other_states(:idling)
323
- @record = @model.new
324
- end
481
+ def test_should_update_record
482
+ assert_not_equal @timestamp, @record.updated_at
483
+ end
484
+ end
485
+
486
+ class MachineWithDirtyAttributesTest < BaseTestCase
487
+ def setup
488
+ @model = new_model
489
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
490
+ @machine.event :ignite
491
+ @machine.state :idling
325
492
 
326
- def test_should_not_define_a_reader_attribute_for_the_attribute
327
- assert !@record.respond_to?(:status)
328
- end
493
+ @record = @model.create
329
494
 
330
- def test_should_not_define_a_writer_attribute_for_the_attribute
331
- assert !@record.respond_to?(:status=)
332
- end
495
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
496
+ @transition.perform(false)
497
+ end
498
+
499
+ def test_should_include_state_in_changed_attributes
500
+ assert_equal [:state], @record.changed_columns
501
+ end
502
+
503
+ def test_should_not_have_changes_when_loaded_from_database
504
+ record = @model[@record.id]
505
+ assert record.changed_columns.empty?
506
+ end
507
+ end
508
+
509
+ class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase
510
+ def setup
511
+ @model = new_model
512
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
513
+ @machine.event :park
333
514
 
334
- def test_should_define_an_attribute_predicate
335
- assert @record.respond_to?(:status?)
336
- end
515
+ @record = @model.create
516
+
517
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
518
+ @transition.perform(false)
337
519
  end
338
520
 
339
- class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase
340
- def setup
341
- @model = new_model do
342
- attr_accessor :status
343
- end
344
-
345
- @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
346
- @machine.other_states(:idling)
347
- @record = @model.new
521
+ def test_should_include_state_in_changed_attributes
522
+ assert_equal [:state], @record.changed_columns
523
+ end
524
+ end
525
+
526
+ class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase
527
+ def setup
528
+ @model = new_model
529
+ DB.alter_table :foo do
530
+ add_column :status, :string, :default => 'idling'
348
531
  end
532
+ @model.class_eval { get_db_schema(true) }
349
533
 
350
- def test_should_return_false_for_predicate_if_does_not_match_current_value
351
- assert !@record.status?(:idling)
352
- end
534
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
535
+ @machine.event :ignite
536
+ @machine.state :idling
353
537
 
354
- def test_should_return_true_for_predicate_if_matches_current_value
355
- assert @record.status?(:parked)
356
- end
538
+ @record = @model.create
357
539
 
358
- def test_should_set_initial_state_on_created_object
359
- assert_equal 'parked', @record.status
360
- end
540
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
541
+ @transition.perform(false)
361
542
  end
362
543
 
363
- class MachineWithInitializedStateTest < BaseTestCase
364
- def setup
365
- @model = new_model
366
- @machine = StateMachine::Machine.new(@model, :initial => :parked)
367
- @machine.state nil, :idling
544
+ def test_should_include_state_in_changed_attributes
545
+ assert_equal [:status], @record.changed_columns
546
+ end
547
+ end
548
+
549
+ class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase
550
+ def setup
551
+ @model = new_model
552
+ DB.alter_table :foo do
553
+ add_column :status, :string, :default => 'idling'
368
554
  end
555
+ @model.class_eval { get_db_schema(true) }
369
556
 
370
- def test_should_allow_nil_initial_state_when_static
371
- record = @model.new(:state => nil)
372
- assert_nil record.state
373
- end
557
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
558
+ @machine.event :park
374
559
 
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
560
+ @record = @model.create
380
561
 
381
- def test_should_allow_different_initial_state_when_static
382
- record = @model.new(:state => 'idling')
383
- assert_equal 'idling', record.state
562
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
563
+ @transition.perform(false)
564
+ end
565
+
566
+ def test_should_include_state_in_changed_attributes
567
+ assert_equal [:status], @record.changed_columns
568
+ end
569
+ end
570
+
571
+ class MachineWithoutTransactionsTest < BaseTestCase
572
+ def setup
573
+ @model = new_model
574
+ @machine = StateMachine::Machine.new(@model, :use_transactions => false)
575
+ end
576
+
577
+ def test_should_not_rollback_transaction_if_false
578
+ @machine.within_transaction(@model.new) do
579
+ @model.create
580
+ false
384
581
  end
385
582
 
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
583
+ assert_equal 1, @model.count
584
+ end
585
+
586
+ def test_should_not_rollback_transaction_if_true
587
+ @machine.within_transaction(@model.new) do
588
+ @model.create
589
+ true
390
590
  end
391
591
 
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
592
+ assert_equal 1, @model.count
593
+ end
594
+ end
595
+
596
+ class MachineWithTransactionsTest < BaseTestCase
597
+ def setup
598
+ @model = new_model
599
+ @machine = StateMachine::Machine.new(@model, :use_transactions => true)
401
600
  end
402
601
 
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
602
+ def test_should_rollback_transaction_if_false
603
+ @machine.within_transaction(@model.new) do
604
+ @model.create
605
+ false
414
606
  end
415
607
 
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
608
+ assert_equal 0, @model.count
425
609
  end
426
610
 
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
611
+ def test_should_not_rollback_transaction_if_true
612
+ @machine.within_transaction(@model.new) do
613
+ @model.create
614
+ true
452
615
  end
453
616
 
454
- def test_should_update_record
455
- assert_not_equal @timestamp, @record.updated_at
456
- end
617
+ assert_equal 1, @model.count
618
+ end
619
+ end
620
+
621
+ class MachineWithCallbacksTest < BaseTestCase
622
+ def setup
623
+ @model = new_model
624
+ @machine = StateMachine::Machine.new(@model)
625
+ @machine.state :parked, :idling
626
+ @machine.event :ignite
627
+
628
+ @record = @model.new(:state => 'parked')
629
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
457
630
  end
458
631
 
459
- class MachineWithDirtyAttributesTest < BaseTestCase
460
- def setup
461
- @model = new_model
462
- @machine = StateMachine::Machine.new(@model, :initial => :parked)
463
- @machine.event :ignite
464
- @machine.state :idling
465
-
466
- @record = @model.create
467
-
468
- @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
469
- @transition.perform(false)
470
- end
632
+ def test_should_run_before_callbacks
633
+ called = false
634
+ @machine.before_transition {called = true}
471
635
 
472
- def test_should_include_state_in_changed_attributes
473
- assert_equal [:state], @record.changed_columns
474
- end
636
+ @transition.perform
637
+ assert called
475
638
  end
476
639
 
477
- class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase
478
- def setup
479
- @model = new_model
480
- @machine = StateMachine::Machine.new(@model, :initial => :parked)
481
- @machine.event :park
482
-
483
- @record = @model.create
484
-
485
- @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
486
- @transition.perform(false)
487
- end
640
+ def test_should_pass_transition_to_before_callbacks_with_one_argument
641
+ transition = nil
642
+ @machine.before_transition {|arg| transition = arg}
488
643
 
489
- def test_should_include_state_in_changed_attributes
490
- assert_equal [:state], @record.changed_columns
491
- end
644
+ @transition.perform
645
+ assert_equal @transition, transition
492
646
  end
493
647
 
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) }
501
-
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
648
+ def test_should_pass_transition_to_before_callbacks_with_multiple_arguments
649
+ callback_args = nil
650
+ @machine.before_transition {|*args| callback_args = args}
511
651
 
512
- def test_should_include_state_in_changed_attributes
513
- assert_equal [:status], @record.changed_columns
514
- end
652
+ @transition.perform
653
+ assert_equal [@transition], callback_args
515
654
  end
516
655
 
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
656
+ def test_should_run_before_callbacks_within_the_context_of_the_record
657
+ context = nil
658
+ @machine.before_transition {context = self}
533
659
 
534
- def test_should_include_state_in_changed_attributes
535
- assert_equal [:status], @record.changed_columns
536
- end
660
+ @transition.perform
661
+ assert_equal @record, context
537
662
  end
538
663
 
539
- class MachineWithoutTransactionsTest < BaseTestCase
540
- def setup
541
- @model = new_model
542
- @machine = StateMachine::Machine.new(@model, :use_transactions => false)
543
- end
664
+ def test_should_run_after_callbacks
665
+ called = false
666
+ @machine.after_transition {called = true}
544
667
 
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
668
+ @transition.perform
669
+ assert called
670
+ end
671
+
672
+ def test_should_pass_transition_to_after_callbacks_with_multiple_arguments
673
+ callback_args = nil
674
+ @machine.after_transition {|*args| callback_args = args}
553
675
 
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
561
- end
676
+ @transition.perform
677
+ assert_equal [@transition], callback_args
562
678
  end
563
679
 
564
- class MachineWithTransactionsTest < BaseTestCase
565
- def setup
566
- @model = new_model
567
- @machine = StateMachine::Machine.new(@model, :use_transactions => true)
568
- end
680
+ def test_should_run_after_callbacks_with_the_context_of_the_record
681
+ context = nil
682
+ @machine.after_transition {context = self}
569
683
 
570
- def test_should_rollback_transaction_if_false
571
- @machine.within_transaction(@model.new) do
572
- @model.create
573
- false
574
- end
575
-
576
- assert_equal 0, @model.count
577
- end
684
+ @transition.perform
685
+ assert_equal @record, context
686
+ end
687
+
688
+ def test_should_run_around_callbacks
689
+ before_called = false
690
+ after_called = [false]
691
+ @machine.around_transition {|block| before_called = true; block.call; after_called[0] = true}
692
+
693
+ @transition.perform
694
+ assert before_called
695
+ assert after_called[0]
696
+ end
697
+
698
+ def test_should_run_around_callbacks_with_the_context_of_the_record
699
+ context = nil
700
+ @machine.around_transition {|block| context = self; block.call}
578
701
 
579
- def test_should_not_rollback_transaction_if_true
580
- @machine.within_transaction(@model.new) do
581
- @model.create
582
- true
583
- end
584
-
585
- assert_equal 1, @model.count
586
- end
702
+ @transition.perform
703
+ assert_equal @record, context
587
704
  end
588
705
 
589
- class MachineWithCallbacksTest < BaseTestCase
590
- def setup
591
- @model = new_model
592
- @machine = StateMachine::Machine.new(@model)
593
- @machine.state :parked, :idling
594
- @machine.event :ignite
595
-
596
- @record = @model.new(:state => 'parked')
597
- @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
598
- end
706
+ def test_should_allow_symbolic_callbacks
707
+ callback_args = nil
599
708
 
600
- def test_should_run_before_callbacks
601
- called = false
602
- @machine.before_transition(lambda {called = true})
603
-
604
- @transition.perform
605
- assert called
709
+ klass = class << @record; self; end
710
+ klass.send(:define_method, :after_ignite) do |*args|
711
+ callback_args = args
606
712
  end
607
713
 
608
- def test_should_pass_transition_to_before_callbacks_with_one_argument
609
- transition = nil
610
- @machine.before_transition(lambda {|arg| transition = arg})
611
-
612
- @transition.perform
613
- assert_equal @transition, transition
614
- end
714
+ @machine.before_transition(:after_ignite)
615
715
 
616
- def test_should_pass_transition_to_before_callbacks_with_multiple_arguments
617
- callback_args = nil
618
- @machine.before_transition(lambda {|*args| callback_args = args})
619
-
620
- @transition.perform
621
- assert_equal [@transition], callback_args
716
+ @transition.perform
717
+ assert_equal [@transition], callback_args
718
+ end
719
+
720
+ def test_should_allow_string_callbacks
721
+ class << @record
722
+ attr_reader :callback_result
622
723
  end
623
724
 
624
- def test_should_run_before_callbacks_within_the_context_of_the_record
625
- context = nil
626
- @machine.before_transition(lambda {context = self})
627
-
628
- @transition.perform
629
- assert_equal @record, context
630
- end
725
+ @machine.before_transition('@callback_result = [1, 2, 3]')
726
+ @transition.perform
631
727
 
632
- def test_should_run_after_callbacks
633
- called = false
634
- @machine.after_transition(lambda {called = true})
635
-
636
- @transition.perform
637
- assert called
728
+ assert_equal [1, 2, 3], @record.callback_result
729
+ end
730
+ end
731
+
732
+ class MachineWithFailedBeforeCallbacksTest < BaseTestCase
733
+ def setup
734
+ callbacks = []
735
+
736
+ @model = new_model
737
+ @machine = StateMachine::Machine.new(@model)
738
+ @machine.state :parked, :idling
739
+ @machine.event :ignite
740
+ @machine.before_transition {callbacks << :before_1; false}
741
+ @machine.before_transition {callbacks << :before_2}
742
+ @machine.after_transition {callbacks << :after}
743
+ @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after}
744
+
745
+ @record = @model.new(:state => 'parked')
746
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
747
+ @result = @transition.perform
748
+
749
+ @callbacks = callbacks
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_run_action
761
+ assert @record.new?
762
+ end
763
+
764
+ def test_should_not_run_further_callbacks
765
+ assert_equal [:before_1], @callbacks
766
+ end
767
+ end
768
+
769
+ class MachineWithFailedActionTest < BaseTestCase
770
+ def setup
771
+ @model = new_model do
772
+ validates_each :state do |object, attribute, value|
773
+ object.errors[attribute] << 'is invalid' unless %w(first_gear).include?(value)
774
+ end
638
775
  end
639
776
 
640
- def test_should_pass_transition_to_after_callbacks_with_multiple_arguments
641
- callback_args = nil
642
- @machine.after_transition(lambda {|*args| callback_args = args})
643
-
644
- @transition.perform
645
- assert_equal [@transition], callback_args
646
- end
777
+ @machine = StateMachine::Machine.new(@model)
778
+ @machine.state :parked, :idling
779
+ @machine.event :ignite
647
780
 
648
- def test_should_run_after_callbacks_with_the_context_of_the_record
649
- context = nil
650
- @machine.after_transition(lambda {context = self})
651
-
652
- @transition.perform
653
- assert_equal @record, context
781
+ callbacks = []
782
+ @machine.before_transition {callbacks << :before}
783
+ @machine.after_transition {callbacks << :after}
784
+ @machine.after_transition(:include_failures => true) {callbacks << :after_failure}
785
+ @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after}
786
+ @machine.around_transition(:include_failures => true) do |block|
787
+ callbacks << :around_before_failure
788
+ block.call
789
+ callbacks << :around_after_failure
654
790
  end
655
791
 
656
- def test_should_allow_symbolic_callbacks
657
- callback_args = nil
658
-
659
- klass = class << @record; self; end
660
- klass.send(:define_method, :after_ignite) do |*args|
661
- callback_args = args
662
- end
663
-
664
- @machine.before_transition(:after_ignite)
665
-
666
- @transition.perform
667
- assert_equal [@transition], callback_args
668
- end
792
+ @record = @model.new(:state => 'parked')
793
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
794
+ @result = @transition.perform
669
795
 
670
- def test_should_allow_string_callbacks
671
- class << @record
672
- attr_reader :callback_result
673
- end
674
-
675
- @machine.before_transition('@callback_result = [1, 2, 3]')
676
- @transition.perform
677
-
678
- assert_equal [1, 2, 3], @record.callback_result
679
- end
796
+ @callbacks = callbacks
680
797
  end
681
798
 
682
- class MachineWithFailedBeforeCallbacksTest < BaseTestCase
683
- def setup
684
- before_count = 0
685
- after_count = 0
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
799
+ def test_should_not_be_successful
800
+ assert !@result
801
+ end
802
+
803
+ def test_should_not_change_current_state
804
+ assert_equal 'parked', @record.state
805
+ end
806
+
807
+ def test_should_not_save_record
808
+ assert @record.new?
809
+ end
810
+
811
+ def test_should_run_before_callbacks_and_after_callbacks_with_failures
812
+ assert_equal [:before, :around_before, :around_before_failure, :around_after_failure, :after_failure], @callbacks
813
+ end
814
+ end
815
+
816
+ class MachineWithFailedAfterCallbacksTest < BaseTestCase
817
+ def setup
818
+ callbacks = []
819
+
820
+ @model = new_model
821
+ @machine = StateMachine::Machine.new(@model)
822
+ @machine.state :parked, :idling
823
+ @machine.event :ignite
824
+ @machine.after_transition {callbacks << :after_1; false}
825
+ @machine.after_transition {callbacks << :after_2}
826
+ @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after}
827
+
828
+ @record = @model.new(:state => 'parked')
829
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
830
+ @result = @transition.perform
831
+
832
+ @callbacks = callbacks
833
+ end
834
+
835
+ def test_should_be_successful
836
+ assert @result
837
+ end
838
+
839
+ def test_should_change_current_state
840
+ assert_equal 'idling', @record.state
841
+ end
842
+
843
+ def test_should_save_record
844
+ assert !@record.new?
845
+ end
846
+
847
+ def test_should_not_run_further_after_callbacks
848
+ assert_equal [:around_before, :around_after, :after_1], @callbacks
849
+ end
850
+ end
851
+
852
+ class MachineWithValidationsTest < BaseTestCase
853
+ def setup
854
+ @model = new_model
855
+ @machine = StateMachine::Machine.new(@model)
856
+ @machine.state :parked
702
857
 
703
- def test_should_not_be_successful
704
- assert !@result
705
- end
858
+ @record = @model.new
859
+ end
860
+
861
+ def test_should_invalidate_using_errors
862
+ @record.state = 'parked'
706
863
 
707
- def test_should_not_change_current_state
708
- assert_equal 'parked', @record.state
709
- end
864
+ @machine.invalidate(@record, :state, :invalid_transition, [[:event, :park]])
865
+ assert_equal ['cannot transition via "park"'], @record.errors.on(:state)
866
+ end
867
+
868
+ def test_should_auto_prefix_custom_attributes_on_invalidation
869
+ @machine.invalidate(@record, :event, :invalid)
710
870
 
711
- def test_should_not_run_action
712
- assert @record.new?
713
- end
871
+ assert_equal ['is invalid'], @record.errors.on(:state_event)
872
+ end
873
+
874
+ def test_should_clear_errors_on_reset
875
+ @record.state = 'parked'
876
+ @record.errors.add(:state, 'is invalid')
714
877
 
715
- def test_should_not_run_further_before_callbacks
716
- assert_equal 1, @before_count
717
- end
878
+ @machine.reset(@record)
879
+ assert_nil @record.errors.on(:id)
880
+ end
881
+
882
+ def test_should_be_valid_if_state_is_known
883
+ @record.state = 'parked'
718
884
 
719
- def test_should_not_run_after_callbacks
720
- assert_equal 0, @after_count
721
- end
885
+ assert @record.valid?
722
886
  end
723
887
 
724
- class MachineWithFailedActionTest < BaseTestCase
725
- def setup
726
- @model = new_model do
727
- validates_each :state do |object, attribute, value|
728
- object.errors[attribute] << 'is invalid' unless %w(first_gear).include?(value)
729
- end
730
- end
731
-
732
- @machine = StateMachine::Machine.new(@model)
733
- @machine.state :parked, :idling
734
- @machine.event :ignite
735
-
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)
742
-
743
- @record = @model.new(:state => 'parked')
744
- @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
745
- @result = @transition.perform
746
-
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
888
+ def test_should_not_be_valid_if_state_is_unknown
889
+ @record.state = 'invalid'
751
890
 
752
- def test_should_not_be_successful
753
- assert !@result
891
+ assert !@record.valid?
892
+ assert_equal ['state is invalid'], @record.errors.full_messages
893
+ end
894
+ end
895
+
896
+ class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
897
+ def setup
898
+ @model = new_model do
899
+ alias_method :status, :state
900
+ alias_method :status=, :state=
754
901
  end
755
902
 
756
- def test_should_not_change_current_state
757
- assert_equal 'parked', @record.state
758
- end
903
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :state)
904
+ @machine.state :parked
759
905
 
760
- def test_should_not_save_record
761
- assert @record.new?
762
- end
906
+ @record = @model.new
907
+ end
908
+
909
+ def test_should_add_validation_errors_to_custom_attribute
910
+ @record.state = 'invalid'
763
911
 
764
- def test_should_run_before_callback
765
- assert @before_transition_called
766
- end
912
+ assert !@record.valid?
913
+ assert_equal ['state is invalid'], @record.errors.full_messages
767
914
 
768
- def test_should_not_run_after_callback_if_not_including_failures
769
- assert !@after_transition_called
915
+ @record.state = 'parked'
916
+ assert @record.valid?
917
+ end
918
+ end
919
+
920
+ class MachineWithStateDrivenValidationsTest < BaseTestCase
921
+ def setup
922
+ @model = new_model do
923
+ attr_accessor :seatbelt
770
924
  end
771
925
 
772
- def test_should_run_after_callback_if_including_failures
773
- assert @after_transition_with_failures_called
926
+ @machine = StateMachine::Machine.new(@model)
927
+ @machine.state :first_gear do
928
+ validates_presence_of :seatbelt
774
929
  end
930
+ @machine.other_states :parked
775
931
  end
776
932
 
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
791
-
792
- @after_count = after_count
933
+ def test_should_be_valid_if_validation_fails_outside_state_scope
934
+ record = @model.new(:state => 'parked', :seatbelt => nil)
935
+ assert record.valid?
936
+ end
937
+
938
+ def test_should_be_invalid_if_validation_fails_within_state_scope
939
+ record = @model.new(:state => 'first_gear', :seatbelt => nil)
940
+ assert !record.valid?
941
+ end
942
+
943
+ def test_should_be_valid_if_validation_succeeds_within_state_scope
944
+ record = @model.new(:state => 'first_gear', :seatbelt => true)
945
+ assert record.valid?
946
+ end
947
+ end
948
+
949
+ class MachineWithEventAttributesOnValidationTest < BaseTestCase
950
+ def setup
951
+ @model = new_model
952
+ @machine = StateMachine::Machine.new(@model)
953
+ @machine.event :ignite do
954
+ transition :parked => :idling
793
955
  end
794
956
 
795
- def test_should_be_successful
796
- assert @result
797
- end
957
+ @record = @model.new
958
+ @record.state = 'parked'
959
+ @record.state_event = 'ignite'
960
+ end
961
+
962
+ def test_should_fail_if_event_is_invalid
963
+ @record.state_event = 'invalid'
964
+ assert !@record.valid?
965
+ assert_equal ['state_event is invalid'], @record.errors.full_messages
966
+ end
967
+
968
+ def test_should_fail_if_event_has_no_transition
969
+ @record.state = 'idling'
970
+ assert !@record.valid?
971
+ assert_equal ['state_event cannot transition when idling'], @record.errors.full_messages
972
+ end
973
+
974
+ def test_should_be_successful_if_event_has_transition
975
+ assert @record.valid?
976
+ end
977
+
978
+ def test_should_run_before_callbacks
979
+ ran_callback = false
980
+ @machine.before_transition { ran_callback = true }
798
981
 
799
- def test_should_change_current_state
800
- assert_equal 'idling', @record.state
801
- end
982
+ @record.valid?
983
+ assert ran_callback
984
+ end
985
+
986
+ def test_should_run_around_callbacks_before_yield
987
+ ran_callback = false
988
+ @machine.around_transition {|block| ran_callback = true; block.call }
802
989
 
803
- def test_should_save_record
804
- assert !@record.new?
805
- end
990
+ @record.valid?
991
+ assert ran_callback
992
+ end
993
+
994
+ def test_should_persist_new_state
995
+ @record.valid?
996
+ assert_equal 'idling', @record.state
997
+ end
998
+
999
+ def test_should_not_run_after_callbacks
1000
+ ran_callback = false
1001
+ @machine.after_transition { ran_callback = true }
806
1002
 
807
- def test_should_not_run_further_after_callbacks
808
- assert_equal 1, @after_count
809
- end
1003
+ @record.valid?
1004
+ assert !ran_callback
810
1005
  end
811
1006
 
812
- class MachineWithValidationsTest < BaseTestCase
813
- def setup
814
- @model = new_model
815
- @machine = StateMachine::Machine.new(@model)
816
- @machine.state :parked
817
-
818
- @record = @model.new
1007
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
1008
+ @model.class_eval do
1009
+ attr_accessor :seatbelt
1010
+ validates_presence_of :seatbelt
819
1011
  end
820
1012
 
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
1013
+ ran_callback = false
1014
+ @machine.after_transition { ran_callback = true }
827
1015
 
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)
1016
+ @record.valid?
1017
+ assert !ran_callback
1018
+ end
1019
+
1020
+ def test_should_run_after_callbacks_with_failures_enabled_if_validation_fails
1021
+ @model.class_eval do
1022
+ attr_accessor :seatbelt
1023
+ validates_presence_of :seatbelt
832
1024
  end
833
1025
 
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
1026
+ ran_callback = false
1027
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
841
1028
 
842
- def test_should_be_valid_if_state_is_known
843
- @record.state = 'parked'
844
-
845
- assert @record.valid?
846
- end
1029
+ @record.valid?
1030
+ assert ran_callback
1031
+ end
1032
+
1033
+ def test_should_not_run_around_callbacks_after_yield
1034
+ ran_callback = [false]
1035
+ @machine.around_transition {|block| block.call; ran_callback[0] = true }
847
1036
 
848
- def test_should_not_be_valid_if_state_is_unknown
849
- @record.state = 'invalid'
850
-
851
- assert !@record.valid?
852
- assert_equal ['state is invalid'], @record.errors.full_messages
853
- end
1037
+ @record.valid?
1038
+ assert !ran_callback[0]
854
1039
  end
855
1040
 
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
1041
+ def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails
1042
+ @model.class_eval do
1043
+ attr_accessor :seatbelt
1044
+ validates_presence_of :seatbelt
867
1045
  end
868
1046
 
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
1047
+ ran_callback = [false]
1048
+ @machine.around_transition {|block| block.call; ran_callback[0] = true }
1049
+
1050
+ @record.valid?
1051
+ assert !ran_callback[0]
878
1052
  end
879
1053
 
880
- class MachineWithStateDrivenValidationsTest < BaseTestCase
881
- def setup
882
- @model = new_model do
883
- attr_accessor :seatbelt
884
- end
885
-
886
- @machine = StateMachine::Machine.new(@model)
887
- @machine.state :first_gear do
888
- validates_presence_of :seatbelt
889
- end
890
- @machine.other_states :parked
1054
+ def test_should_run_around_callbacks_after_yield_with_failures_enabled_if_validation_fails
1055
+ @model.class_eval do
1056
+ attr_accessor :seatbelt
1057
+ validates_presence_of :seatbelt
891
1058
  end
892
1059
 
893
- def test_should_be_valid_if_validation_fails_outside_state_scope
894
- record = @model.new(:state => 'parked', :seatbelt => nil)
895
- assert record.valid?
1060
+ ran_callback = [false]
1061
+ @machine.around_transition(:include_failures => true) {|block| block.call; ran_callback[0] = true }
1062
+
1063
+ @record.valid?
1064
+ assert ran_callback[0]
1065
+ end
1066
+
1067
+ def test_should_not_run_before_transitions_within_transaction
1068
+ @machine.before_transition { self.class.create; raise Sequel::Error::Rollback }
1069
+
1070
+ begin
1071
+ @record.valid?
1072
+ rescue Sequel::Error::Rollback
896
1073
  end
897
1074
 
898
- def test_should_be_invalid_if_validation_fails_within_state_scope
899
- record = @model.new(:state => 'first_gear', :seatbelt => nil)
900
- assert !record.valid?
1075
+ assert_equal 1, @model.count
1076
+ end
1077
+ end
1078
+
1079
+ class MachineWithEventAttributesOnSaveTest < BaseTestCase
1080
+ def setup
1081
+ @model = new_model
1082
+ @machine = StateMachine::Machine.new(@model)
1083
+ @machine.event :ignite do
1084
+ transition :parked => :idling
901
1085
  end
902
1086
 
903
- def test_should_be_valid_if_validation_succeeds_within_state_scope
904
- record = @model.new(:state => 'first_gear', :seatbelt => true)
905
- assert record.valid?
1087
+ @record = @model.new
1088
+ @record.state = 'parked'
1089
+ @record.state_event = 'ignite'
1090
+ end
1091
+
1092
+ def test_should_fail_if_event_is_invalid
1093
+ @record.state_event = 'invalid'
1094
+ assert !@record.save
1095
+ end
1096
+
1097
+ def test_should_raise_exception_when_enabled_if_event_is_invalid
1098
+ @record.state_event = 'invalid'
1099
+ @model.raise_on_save_failure = true
1100
+ if defined?(Sequel::BeforeHookFailed)
1101
+ assert_raise(Sequel::BeforeHookFailed) { @record.save }
1102
+ else
1103
+ assert_raise(Sequel::Error) { @record.save }
906
1104
  end
907
1105
  end
908
1106
 
909
- class MachineWithEventAttributesOnValidationTest < BaseTestCase
910
- def setup
911
- @model = new_model
912
- @machine = StateMachine::Machine.new(@model)
913
- @machine.event :ignite do
914
- transition :parked => :idling
915
- end
916
-
917
- @record = @model.new
918
- @record.state = 'parked'
919
- @record.state_event = 'ignite'
1107
+ def test_should_fail_if_event_has_no_transition
1108
+ @record.state = 'idling'
1109
+ assert !@record.save
1110
+ end
1111
+
1112
+ def test_should_raise_exception_when_enabled_if_event_has_no_transition
1113
+ @record.state = 'idling'
1114
+ @model.raise_on_save_failure = true
1115
+ if defined?(Sequel::BeforeHookFailed)
1116
+ assert_raise(Sequel::BeforeHookFailed) { @record.save }
1117
+ else
1118
+ assert_raise(Sequel::Error) { @record.save }
920
1119
  end
1120
+ end
1121
+
1122
+ def test_should_be_successful_if_event_has_transition
1123
+ assert @record.save
1124
+ end
1125
+
1126
+ def test_should_run_before_callbacks
1127
+ ran_callback = false
1128
+ @machine.before_transition { ran_callback = true }
921
1129
 
922
- def test_should_fail_if_event_is_invalid
923
- @record.state_event = 'invalid'
924
- assert !@record.valid?
925
- assert_equal ['state_event is invalid'], @record.errors.full_messages
926
- end
1130
+ @record.save
1131
+ assert ran_callback
1132
+ end
1133
+
1134
+ def test_should_run_before_callbacks_once
1135
+ before_count = 0
1136
+ @machine.before_transition { before_count += 1 }
927
1137
 
928
- def test_should_fail_if_event_has_no_transition
929
- @record.state = 'idling'
930
- assert !@record.valid?
931
- assert_equal ['state_event cannot transition when idling'], @record.errors.full_messages
932
- end
1138
+ @record.save
1139
+ assert_equal 1, before_count
1140
+ end
1141
+
1142
+ def test_should_run_around_callbacks_before_yield
1143
+ ran_callback = false
1144
+ @machine.around_transition {|block| ran_callback = true; block.call }
933
1145
 
934
- def test_should_be_successful_if_event_has_transition
935
- assert @record.valid?
936
- end
1146
+ @record.save
1147
+ assert ran_callback
1148
+ end
1149
+
1150
+ def test_should_run_around_callbacks_before_yield_once
1151
+ around_before_count = 0
1152
+ @machine.around_transition {|block| around_before_count += 1; block.call }
937
1153
 
938
- def test_should_run_before_callbacks
939
- ran_callback = false
940
- @machine.before_transition { ran_callback = true }
941
-
942
- @record.valid?
943
- assert ran_callback
944
- end
1154
+ @record.save
1155
+ assert_equal 1, around_before_count
1156
+ end
1157
+
1158
+ def test_should_persist_new_state
1159
+ @record.save
1160
+ assert_equal 'idling', @record.state
1161
+ end
1162
+
1163
+ def test_should_run_after_callbacks
1164
+ ran_callback = false
1165
+ @machine.after_transition { ran_callback = true }
945
1166
 
946
- def test_should_persist_new_state
947
- @record.valid?
948
- assert_equal 'idling', @record.state
949
- end
1167
+ @record.save
1168
+ assert ran_callback
1169
+ end
1170
+
1171
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
1172
+ @model.before_create {|record| false}
950
1173
 
951
- def test_should_not_run_after_callbacks
952
- ran_callback = false
953
- @machine.after_transition { ran_callback = true }
954
-
955
- @record.valid?
956
- assert !ran_callback
957
- end
1174
+ ran_callback = false
1175
+ @machine.after_transition { ran_callback = true }
958
1176
 
959
- def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
960
- @model.class_eval do
961
- attr_accessor :seatbelt
962
- validates_presence_of :seatbelt
963
- end
1177
+ @record.save
1178
+ assert !ran_callback
1179
+ end
1180
+
1181
+ if defined?(Sequel::MAJOR) && Sequel::MAJOR >= 3 && Sequel::MINOR >= 7
1182
+ def test_should_not_run_after_callbacks_with_failures_enabled_if_fails
1183
+ @model.before_create {|record| false}
964
1184
 
965
1185
  ran_callback = false
966
- @machine.after_transition { ran_callback = true }
1186
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
967
1187
 
968
- @record.valid?
1188
+ @record.save
969
1189
  assert !ran_callback
970
1190
  end
971
-
972
- def test_should_run_after_callbacks_with_failures_enabled_if_validation_fails
973
- @model.class_eval do
974
- attr_accessor :seatbelt
975
- validates_presence_of :seatbelt
976
- end
1191
+ else
1192
+ def test_should_run_after_callbacks_with_failures_enabled_if_fails
1193
+ @model.before_create {|record| false}
977
1194
 
978
1195
  ran_callback = false
979
1196
  @machine.after_transition(:include_failures => true) { ran_callback = true }
980
1197
 
981
- @record.valid?
1198
+ @record.save
982
1199
  assert ran_callback
983
1200
  end
984
1201
  end
985
1202
 
986
- class MachineWithEventAttributesOnSaveTest < BaseTestCase
987
- def setup
988
- @model = new_model
989
- @machine = StateMachine::Machine.new(@model)
990
- @machine.event :ignite do
991
- transition :parked => :idling
992
- end
993
-
994
- @record = @model.new
995
- @record.state = 'parked'
996
- @record.state_event = 'ignite'
997
- end
998
-
999
- def test_should_fail_if_event_is_invalid
1000
- @record.state_event = 'invalid'
1001
- assert !@record.save
1002
- end
1003
-
1004
- def test_should_fail_if_event_has_no_transition
1005
- @record.state = 'idling'
1006
- assert !@record.save
1007
- end
1203
+ def test_should_not_run_before_transitions_within_transaction
1204
+ @machine.before_transition { self.class.create; raise Sequel::Error::Rollback }
1008
1205
 
1009
- def test_should_be_successful_if_event_has_transition
1010
- assert @record.save
1011
- end
1012
-
1013
- def test_should_run_before_callbacks
1014
- ran_callback = false
1015
- @machine.before_transition { ran_callback = true }
1016
-
1206
+ begin
1017
1207
  @record.save
1018
- assert ran_callback
1208
+ rescue Sequel::Error::Rollback
1019
1209
  end
1020
1210
 
1021
- def test_should_run_before_callbacks_once
1022
- before_count = 0
1023
- @machine.before_transition { before_count += 1 }
1024
-
1025
- @record.save
1026
- assert_equal 1, before_count
1027
- end
1211
+ assert_equal 1, @model.count
1212
+ end
1213
+
1214
+ def test_should_not_run_around_callbacks_with_failures_disabled_if_fails
1215
+ @model.before_create {|record| false}
1028
1216
 
1029
- def test_should_persist_new_state
1030
- @record.save
1031
- assert_equal 'idling', @record.state
1032
- end
1217
+ ran_callback = [false]
1218
+ @machine.around_transition {|block| block.call; ran_callback[0] = true }
1033
1219
 
1034
- def test_should_run_after_callbacks
1035
- ran_callback = false
1036
- @machine.after_transition { ran_callback = true }
1037
-
1038
- @record.save
1039
- assert ran_callback
1040
- end
1220
+ @record.save
1221
+ assert !ran_callback[0]
1222
+ end
1223
+
1224
+ def test_should_run_around_callbacks_after_yield
1225
+ ran_callback = [false]
1226
+ @machine.around_transition {|block| block.call; ran_callback[0] = true }
1041
1227
 
1042
- def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
1228
+ @record.save
1229
+ assert ran_callback[0]
1230
+ end
1231
+
1232
+ if defined?(Sequel::MAJOR) && Sequel::MAJOR >= 3 && Sequel::MINOR >= 7
1233
+ def test_should_not_run_around_callbacks_after_yield_with_failures_enabled_if_fails
1043
1234
  @model.before_create {|record| false}
1044
1235
 
1045
- ran_callback = false
1046
- @machine.after_transition { ran_callback = true }
1236
+ ran_callback = [false]
1237
+ @machine.around_transition(:include_failures => true) {|block| block.call; ran_callback[0] = true }
1047
1238
 
1048
1239
  @record.save
1049
- assert !ran_callback
1240
+ assert !ran_callback[0]
1050
1241
  end
1051
-
1052
- def test_should_run_after_callbacks_with_failures_enabled_if_fails
1242
+ else
1243
+ def test_should_run_around_callbacks_after_yield_with_failures_enabled_if_fails
1053
1244
  @model.before_create {|record| false}
1054
1245
 
1055
- ran_callback = false
1056
- @machine.after_transition(:include_failures => true) { ran_callback = true }
1246
+ ran_callback = [false]
1247
+ @machine.around_transition(:include_failures => true) {|block| block.call; ran_callback[0] = true }
1057
1248
 
1058
1249
  @record.save
1059
- assert ran_callback
1250
+ assert ran_callback[0]
1060
1251
  end
1061
1252
  end
1062
1253
 
1063
- class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
1064
- def setup
1065
- @superclass = new_model do
1066
- def persist
1067
- save
1068
- end
1069
- end
1070
- @model = Class.new(@superclass)
1071
- @machine = StateMachine::Machine.new(@model, :action => :persist)
1072
- @machine.event :ignite do
1073
- transition :parked => :idling
1074
- end
1254
+ if defined?(Sequel::MAJOR) && (Sequel::MAJOR >= 3 || Sequel::MAJOR == 2 && Sequel::MINOR == 12)
1255
+ def test_should_run_after_transitions_within_transaction
1256
+ @machine.after_transition { self.class.create; raise Sequel::Error::Rollback }
1075
1257
 
1076
- @record = @model.new
1077
- @record.state = 'parked'
1078
- @record.state_event = 'ignite'
1079
- end
1080
-
1081
- def test_should_not_transition_on_valid?
1082
- @record.valid?
1083
- assert_equal 'parked', @record.state
1084
- end
1085
-
1086
- def test_should_not_transition_on_save
1087
1258
  @record.save
1088
- assert_equal 'parked', @record.state
1089
- end
1090
-
1091
- def test_should_transition_on_custom_action
1092
- @record.persist
1093
- assert_equal 'idling', @record.state
1094
- end
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)
1259
+
1260
+ assert_equal 0, @model.count
1107
1261
  end
1108
1262
 
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'
1263
+ def test_should_run_around_transition_within_transaction
1264
+ @machine.around_transition {|block| block.call; self.class.create; raise Sequel::Error::Rollback }
1112
1265
 
1113
- assert_equal [parked], @model.with_state(:parked).all
1266
+ @record.save
1267
+
1268
+ assert_equal 0, @model.count
1114
1269
  end
1115
-
1116
- def test_should_create_plural_with_scope
1117
- assert @model.respond_to?(:with_states)
1270
+ else
1271
+ def test_should_not_run_after_transitions_within_transaction
1272
+ @machine.after_transition { self.class.create; raise Sequel::Error::Rollback }
1273
+
1274
+ begin
1275
+ @record.save
1276
+ rescue Sequel::Error::Rollback
1277
+ end
1278
+
1279
+ assert_equal 2, @model.count
1118
1280
  end
1119
1281
 
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'
1282
+ def test_should_not_run_around_transition_within_transaction
1283
+ @machine.around_transition {|block| block.call; self.class.create; raise Sequel::Error::Rollback }
1284
+
1285
+ begin
1286
+ @record.save
1287
+ rescue Sequel::Error::Rollback
1288
+ end
1123
1289
 
1124
- assert_equal [parked, idling], @model.with_states(:parked, :idling).all
1290
+ assert_equal 2, @model.count
1125
1291
  end
1126
-
1127
- def test_should_create_singular_without_scope
1128
- assert @model.respond_to?(:without_state)
1292
+ end
1293
+ end
1294
+
1295
+ class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
1296
+ def setup
1297
+ @superclass = new_model do
1298
+ def persist
1299
+ save
1300
+ end
1129
1301
  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
1302
+ @model = Class.new(@superclass)
1303
+ @machine = StateMachine::Machine.new(@model, :action => :persist)
1304
+ @machine.event :ignite do
1305
+ transition :parked => :idling
1136
1306
  end
1137
1307
 
1138
- def test_should_create_plural_without_scope
1139
- assert @model.respond_to?(:without_states)
1140
- end
1308
+ @record = @model.new
1309
+ @record.state = 'parked'
1310
+ @record.state_event = 'ignite'
1311
+ end
1312
+
1313
+ def test_should_not_transition_on_valid?
1314
+ @record.valid?
1315
+ assert_equal 'parked', @record.state
1316
+ end
1317
+
1318
+ def test_should_not_transition_on_save
1319
+ @record.save
1320
+ assert_equal 'parked', @record.state
1321
+ end
1322
+
1323
+ def test_should_transition_on_custom_action
1324
+ @record.persist
1325
+ assert_equal 'idling', @record.state
1326
+ end
1327
+ end
1328
+
1329
+ class MachineWithScopesTest < BaseTestCase
1330
+ def setup
1331
+ @model = new_model
1332
+ @machine = StateMachine::Machine.new(@model)
1333
+ @machine.state :parked, :first_gear
1334
+ @machine.state :idling, :value => lambda {'idling'}
1335
+ end
1336
+
1337
+ def test_should_create_singular_with_scope
1338
+ assert @model.respond_to?(:with_state)
1339
+ end
1340
+
1341
+ def test_should_only_include_records_with_state_in_singular_with_scope
1342
+ parked = @model.create :state => 'parked'
1343
+ idling = @model.create :state => 'idling'
1141
1344
 
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
1345
+ assert_equal [parked], @model.with_state(:parked).all
1346
+ end
1347
+
1348
+ def test_should_create_plural_with_scope
1349
+ assert @model.respond_to?(:with_states)
1350
+ end
1351
+
1352
+ def test_should_only_include_records_with_states_in_plural_with_scope
1353
+ parked = @model.create :state => 'parked'
1354
+ idling = @model.create :state => 'idling'
1149
1355
 
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
1356
+ assert_equal [parked, idling], @model.with_states(:parked, :idling).all
1156
1357
  end
1157
1358
 
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
1359
+ def test_should_create_singular_without_scope
1360
+ assert @model.respond_to?(:without_state)
1361
+ end
1362
+
1363
+ def test_should_only_include_records_without_state_in_singular_without_scope
1364
+ parked = @model.create :state => 'parked'
1365
+ idling = @model.create :state => 'idling'
1167
1366
 
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
1367
+ assert_equal [parked], @model.without_state(:idling).all
1368
+ end
1369
+
1370
+ def test_should_create_plural_without_scope
1371
+ assert @model.respond_to?(:without_states)
1372
+ end
1373
+
1374
+ def test_should_only_include_records_without_states_in_plural_without_scope
1375
+ parked = @model.create :state => 'parked'
1376
+ idling = @model.create :state => 'idling'
1377
+ first_gear = @model.create :state => 'first_gear'
1174
1378
 
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
1379
+ assert_equal [parked, idling], @model.without_states(:first_gear).all
1182
1380
  end
1183
1381
 
1184
- class MachineWithComplexPluralizationScopesTest < BaseTestCase
1185
- def setup
1186
- @model = new_model
1187
- @machine = StateMachine::Machine.new(@model, :status)
1188
- end
1382
+ def test_should_allow_chaining_scopes_and_filters
1383
+ parked = @model.create :state => 'parked'
1384
+ idling = @model.create :state => 'idling'
1189
1385
 
1190
- def test_should_create_singular_with_scope
1191
- assert @model.respond_to?(:with_status)
1192
- end
1386
+ assert_equal [idling], @model.without_state(:parked).filter(:state => 'idling').all
1387
+ end
1388
+ end
1389
+
1390
+ class MachineWithScopesAndOwnerSubclassTest < BaseTestCase
1391
+ def setup
1392
+ @model = new_model
1393
+ @machine = StateMachine::Machine.new(@model, :state)
1394
+
1395
+ @subclass = Class.new(@model)
1396
+ @subclass_machine = @subclass.state_machine(:state) {}
1397
+ @subclass_machine.state :parked, :idling, :first_gear
1398
+ end
1399
+
1400
+ def test_should_only_include_records_with_subclass_states_in_with_scope
1401
+ parked = @subclass.create :state => 'parked'
1402
+ idling = @subclass.create :state => 'idling'
1193
1403
 
1194
- def test_should_create_plural_with_scope
1195
- assert @model.respond_to?(:with_statuses)
1196
- end
1404
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling).all
1197
1405
  end
1198
1406
 
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
1407
+ def test_should_only_include_records_without_subclass_states_in_without_scope
1408
+ parked = @subclass.create :state => 'parked'
1409
+ idling = @subclass.create :state => 'idling'
1410
+ first_gear = @subclass.create :state => 'first_gear'
1220
1411
 
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
1412
+ assert_equal [parked, idling], @subclass.without_states(:first_gear).all
1413
+ end
1414
+ end
1415
+
1416
+ class MachineWithComplexPluralizationScopesTest < BaseTestCase
1417
+ def setup
1418
+ @model = new_model
1419
+ @machine = StateMachine::Machine.new(@model, :status)
1420
+ end
1421
+
1422
+ def test_should_create_singular_with_scope
1423
+ assert @model.respond_to?(:with_status)
1424
+ end
1425
+
1426
+ def test_should_create_plural_with_scope
1427
+ assert @model.respond_to?(:with_statuses)
1428
+ end
1429
+ end
1430
+
1431
+ class MachineWithScopesAndJoinsTest < BaseTestCase
1432
+ def setup
1433
+ @company = new_model(:company)
1434
+ SequelTest.const_set('Company', @company)
1224
1435
 
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
1436
+ @vehicle = new_model(:vehicle) do
1437
+ many_to_one :company, :class => SequelTest::Company
1438
+ end
1439
+ DB.alter_table :vehicle do
1440
+ add_column :company_id, :integer
1227
1441
  end
1442
+ @vehicle.class_eval { get_db_schema(true) }
1443
+ SequelTest.const_set('Vehicle', @vehicle)
1228
1444
 
1229
- def teardown
1230
- SequelTest.class_eval do
1231
- remove_const('Vehicle')
1232
- remove_const('Company')
1233
- end
1445
+ @company_machine = StateMachine::Machine.new(@company, :initial => :active)
1446
+ @vehicle_machine = StateMachine::Machine.new(@vehicle, :initial => :parked)
1447
+ @vehicle_machine.state :idling
1448
+
1449
+ @ford = @company.create
1450
+ @mustang = @vehicle.create(:company => @ford)
1451
+ end
1452
+
1453
+ def test_should_find_records_in_with_scope
1454
+ assert_equal [@mustang], @vehicle.with_states(:parked).join(:company, :id => :company_id).filter(:company__state => 'active').select(:vehicle.*).all
1455
+ end
1456
+
1457
+ def test_should_find_records_in_without_scope
1458
+ assert_equal [@mustang], @vehicle.without_states(:idling).join(:company, :id => :company_id).filter(:company__state => 'active').select(:vehicle.*).all
1459
+ end
1460
+
1461
+ def teardown
1462
+ SequelTest.class_eval do
1463
+ remove_const('Vehicle')
1464
+ remove_const('Company')
1234
1465
  end
1235
1466
  end
1236
1467
  end
1237
- rescue LoadError
1238
- $stderr.puts "Skipping Sequel tests. `gem install sequel#{" -v #{ENV['SEQUEL_VERSION']}" if ENV['SEQUEL_VERSION']}` and try again."
1239
1468
  end