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,1610 +1,1860 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
2
2
 
3
- begin
4
- # Load library
5
- require 'rubygems'
6
-
7
- gem 'activerecord', ENV['AR_VERSION'] ? "=#{ENV['AR_VERSION']}" : '>=2.0.0'
8
- require 'active_record'
9
-
10
- FIXTURES_ROOT = File.dirname(__FILE__) + '/../../fixtures/'
11
-
12
- # Load TestCase helpers
13
- require 'active_support/test_case'
14
- require 'active_record/fixtures'
15
-
16
- require 'active_record/version'
17
- if ActiveRecord::VERSION::STRING >= '2.1.0'
18
- require 'active_record/test_case'
19
- else
20
- class ActiveRecord::TestCase < ActiveSupport::TestCase
21
- self.fixture_path = FIXTURES_ROOT
22
- self.use_instantiated_fixtures = false
23
- self.use_transactional_fixtures = true
24
- end
3
+ # Load library
4
+ require 'rubygems'
5
+
6
+ gem 'activerecord', ENV['VERSION'] ? "=#{ENV['VERSION']}" : '>=2.0.0'
7
+ require 'active_record'
8
+
9
+ FIXTURES_ROOT = File.dirname(__FILE__) + '/../../fixtures/'
10
+
11
+ # Load TestCase helpers
12
+ require 'active_support/test_case'
13
+ require 'active_record/fixtures'
14
+
15
+ require 'active_record/version'
16
+ if ActiveRecord::VERSION::STRING >= '2.1.0'
17
+ require 'active_record/test_case'
18
+ else
19
+ class ActiveRecord::TestCase < ActiveSupport::TestCase
20
+ self.fixture_path = FIXTURES_ROOT
21
+ self.use_instantiated_fixtures = false
22
+ self.use_transactional_fixtures = true
25
23
  end
26
-
27
- # Establish database connection
28
- ActiveRecord::Base.establish_connection({'adapter' => 'sqlite3', 'database' => ':memory:'})
29
- ActiveRecord::Base.logger = Logger.new("#{File.dirname(__FILE__)}/../../active_record.log")
30
-
31
- module ActiveRecordTest
32
- class BaseTestCase < ActiveRecord::TestCase
33
- def default_test
24
+ end
25
+
26
+ # Establish database connection
27
+ ActiveRecord::Base.establish_connection({'adapter' => 'sqlite3', 'database' => ':memory:'})
28
+ ActiveRecord::Base.logger = Logger.new("#{File.dirname(__FILE__)}/../../active_record.log")
29
+
30
+ module ActiveRecordTest
31
+ class BaseTestCase < ActiveRecord::TestCase
32
+ def default_test
33
+ end
34
+
35
+ protected
36
+ # Creates a new ActiveRecord model (and the associated table)
37
+ def new_model(create_table = :foo, &block)
38
+ table_name = create_table || :foo
39
+
40
+ model = Class.new(ActiveRecord::Base) do
41
+ connection.create_table(table_name, :force => true) {|t| t.string(:state)} if create_table
42
+ set_table_name(table_name.to_s)
43
+
44
+ def self.name; "ActiveRecordTest::#{table_name.capitalize}"; end
45
+ end
46
+ model.class_eval(&block) if block_given?
47
+ model
34
48
  end
35
49
 
36
- protected
37
- # Creates a new ActiveRecord model (and the associated table)
38
- def new_model(create_table = :foo, &block)
39
- table_name = create_table || :foo
50
+ # Creates a new ActiveRecord observer
51
+ def new_observer(model, &block)
52
+ observer = Class.new(ActiveRecord::Observer) do
53
+ attr_accessor :notifications
40
54
 
41
- model = Class.new(ActiveRecord::Base) do
42
- connection.create_table(table_name, :force => true) {|t| t.string(:state)} if create_table
43
- set_table_name(table_name.to_s)
44
-
45
- def self.name; "ActiveRecordTest::#{table_name.capitalize}"; end
46
- end
47
- model.class_eval(&block) if block_given?
48
- model
49
- end
50
-
51
- # Creates a new ActiveRecord observer
52
- def new_observer(model, &block)
53
- observer = Class.new(ActiveRecord::Observer) do
54
- attr_accessor :notifications
55
-
56
- def initialize
57
- super
58
- @notifications = []
59
- end
55
+ def initialize
56
+ super
57
+ @notifications = []
60
58
  end
61
- observer.observe(model)
62
- observer.class_eval(&block) if block_given?
63
- observer
64
59
  end
60
+ observer.observe(model)
61
+ observer.class_eval(&block) if block_given?
62
+ observer
63
+ end
64
+ end
65
+
66
+ class IntegrationTest < BaseTestCase
67
+ def test_should_match_if_class_inherits_from_active_record
68
+ assert StateMachine::Integrations::ActiveRecord.matches?(new_model)
65
69
  end
66
70
 
67
- class IntegrationTest < BaseTestCase
68
- def test_should_match_if_class_inherits_from_active_record
69
- assert StateMachine::Integrations::ActiveRecord.matches?(new_model)
70
- end
71
-
72
- def test_should_not_match_if_class_does_not_inherit_from_active_record
73
- assert !StateMachine::Integrations::ActiveRecord.matches?(Class.new)
74
- end
75
-
76
- def test_should_have_defaults
77
- assert_equal e = {:action => :save}, StateMachine::Integrations::ActiveRecord.defaults
78
- end
71
+ def test_should_not_match_if_class_does_not_inherit_from_active_record
72
+ assert !StateMachine::Integrations::ActiveRecord.matches?(Class.new)
79
73
  end
80
74
 
81
- class MachineWithoutDatabaseTest < BaseTestCase
82
- def setup
83
- @model = new_model(false) do
84
- # Simulate the database not being available entirely
85
- def self.connection
86
- raise ActiveRecord::ConnectionNotEstablished
87
- end
75
+ def test_should_have_defaults
76
+ assert_equal e = {:action => :save}, StateMachine::Integrations::ActiveRecord.defaults
77
+ end
78
+ end
79
+
80
+ class MachineWithoutDatabaseTest < BaseTestCase
81
+ def setup
82
+ @model = new_model(false) do
83
+ # Simulate the database not being available entirely
84
+ def self.connection
85
+ raise ActiveRecord::ConnectionNotEstablished
88
86
  end
89
87
  end
90
-
91
- def test_should_allow_machine_creation
92
- assert_nothing_raised { StateMachine::Machine.new(@model) }
93
- end
94
88
  end
95
89
 
96
- class MachineUnmigratedTest < BaseTestCase
97
- def setup
98
- @model = new_model(false)
99
-
100
- # Drop the table so that it definitely doesn't exist
101
- @model.connection.drop_table(:foo) if @model.table_exists?
102
- end
90
+ def test_should_allow_machine_creation
91
+ assert_nothing_raised { StateMachine::Machine.new(@model) }
92
+ end
93
+ end
94
+
95
+ class MachineUnmigratedTest < BaseTestCase
96
+ def setup
97
+ @model = new_model(false)
103
98
 
104
- def test_should_allow_machine_creation
105
- assert_nothing_raised { StateMachine::Machine.new(@model) }
106
- end
99
+ # Drop the table so that it definitely doesn't exist
100
+ @model.connection.drop_table(:foo) if @model.table_exists?
107
101
  end
108
102
 
109
- class MachineByDefaultTest < BaseTestCase
110
- def setup
111
- @model = new_model
112
- @machine = StateMachine::Machine.new(@model)
103
+ def test_should_allow_machine_creation
104
+ assert_nothing_raised { StateMachine::Machine.new(@model) }
105
+ end
106
+ end
107
+
108
+ class MachineByDefaultTest < BaseTestCase
109
+ def setup
110
+ @model = new_model
111
+ @machine = StateMachine::Machine.new(@model)
112
+ end
113
+
114
+ def test_should_use_save_as_action
115
+ assert_equal :save, @machine.action
116
+ end
117
+
118
+ def test_should_use_transactions
119
+ assert_equal true, @machine.use_transactions
120
+ end
121
+
122
+ def test_should_create_notifier_before_callback
123
+ assert_equal 1, @machine.callbacks[:before].size
124
+ end
125
+
126
+ def test_should_create_notifier_after_callback
127
+ assert_equal 1, @machine.callbacks[:after].size
128
+ end
129
+ end
130
+
131
+ class MachineWithStaticInitialStateTest < BaseTestCase
132
+ def setup
133
+ @model = new_model do
134
+ attr_accessor :value
113
135
  end
114
-
115
- def test_should_use_save_as_action
116
- assert_equal :save, @machine.action
136
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
137
+ end
138
+
139
+ def test_should_set_initial_state_on_created_object
140
+ record = @model.new
141
+ assert_equal 'parked', record.state
142
+ end
143
+
144
+ def test_should_set_initial_state_with_nil_attributes
145
+ record = @model.new(nil)
146
+ assert_equal 'parked', record.state
147
+ end
148
+
149
+ def test_should_still_set_attributes
150
+ record = @model.new(:value => 1)
151
+ assert_equal 1, record.value
152
+ end
153
+
154
+ def test_should_still_allow_initialize_blocks
155
+ block_args = nil
156
+ record = @model.new do |*args|
157
+ block_args = args
117
158
  end
118
159
 
119
- def test_should_use_transactions
120
- assert_equal true, @machine.use_transactions
160
+ assert_equal [record], block_args
161
+ end
162
+
163
+ def test_should_set_attributes_prior_to_after_initialize_hook
164
+ state = nil
165
+ @model.class_eval {define_method(:after_initialize) {}} if ::ActiveRecord::VERSION::MAJOR <= 2
166
+ @model.after_initialize do |record|
167
+ state = record.state
168
+ end
169
+ @model.new
170
+ assert_equal 'parked', state
171
+ end
172
+
173
+ def test_should_set_initial_state_before_setting_attributes
174
+ @model.class_eval do
175
+ attr_accessor :state_during_setter
176
+
177
+ define_method(:value=) do |value|
178
+ self.state_during_setter = state
179
+ end
121
180
  end
122
181
 
123
- def test_should_create_notifier_before_callback
124
- assert_equal 1, @machine.callbacks[:before].size
125
- end
182
+ record = @model.new(:value => 1)
183
+ assert_equal 'parked', record.state_during_setter
184
+ end
185
+
186
+ def test_should_not_set_initial_state_after_already_initialized
187
+ record = @model.new(:value => 1)
188
+ assert_equal 'parked', record.state
126
189
 
127
- def test_should_create_notifier_after_callback
128
- assert_equal 1, @machine.callbacks[:after].size
129
- end
190
+ record.state = 'idling'
191
+ record.attributes = {}
192
+ assert_equal 'idling', record.state
130
193
  end
131
194
 
132
- class MachineWithStaticInitialStateTest < BaseTestCase
133
- def setup
134
- @model = new_model do
135
- attr_accessor :value
136
- end
137
- @machine = StateMachine::Machine.new(@model, :initial => :parked)
138
- end
195
+ def test_should_use_stored_values_when_loading_from_database
196
+ @machine.state :idling
139
197
 
140
- def test_should_set_initial_state_on_created_object
141
- record = @model.new
142
- assert_equal 'parked', record.state
143
- end
198
+ record = @model.find(@model.create(:state => 'idling').id)
199
+ assert_equal 'idling', record.state
200
+ end
201
+
202
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
203
+ @machine.state nil
144
204
 
145
- def test_should_set_initial_state_with_nil_attributes
146
- record = @model.new(nil)
147
- assert_equal 'parked', record.state
205
+ record = @model.find(@model.create(:state => nil).id)
206
+ assert_nil record.state
207
+ end
208
+ end
209
+
210
+ class MachineWithDynamicInitialStateTest < BaseTestCase
211
+ def setup
212
+ @model = new_model do
213
+ attr_accessor :value
148
214
  end
149
-
150
- def test_should_still_set_attributes
151
- record = @model.new(:value => 1)
152
- assert_equal 1, record.value
215
+ @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked})
216
+ @machine.state :parked
217
+ end
218
+
219
+ def test_should_set_initial_state_on_created_object
220
+ record = @model.new
221
+ assert_equal 'parked', record.state
222
+ end
223
+
224
+ def test_should_still_set_attributes
225
+ record = @model.new(:value => 1)
226
+ assert_equal 1, record.value
227
+ end
228
+
229
+ def test_should_still_allow_initialize_blocks
230
+ block_args = nil
231
+ record = @model.new do |*args|
232
+ block_args = args
153
233
  end
154
234
 
155
- def test_should_still_allow_initialize_blocks
156
- block_args = nil
157
- record = @model.new do |*args|
158
- block_args = args
159
- end
235
+ assert_equal [record], block_args
236
+ end
237
+
238
+ def test_should_set_attributes_prior_to_after_initialize_hook
239
+ state = nil
240
+ @model.class_eval {define_method(:after_initialize) {}} if ::ActiveRecord::VERSION::MAJOR <= 2
241
+ @model.after_initialize do |record|
242
+ state = record.state
243
+ end
244
+ @model.new
245
+ assert_equal 'parked', state
246
+ end
247
+
248
+ def test_should_set_initial_state_after_setting_attributes
249
+ @model.class_eval do
250
+ attr_accessor :state_during_setter
160
251
 
161
- assert_equal [record], block_args
162
- end
163
-
164
- def test_should_set_attributes_prior_to_after_initialize_hook
165
- state = nil
166
- @model.class_eval {define_method(:after_initialize) {}} if ::ActiveRecord::VERSION::MAJOR <= 2
167
- @model.after_initialize do |record|
168
- state = record.state
252
+ define_method(:value=) do |value|
253
+ self.state_during_setter = state || 'nil'
169
254
  end
170
- @model.new
171
- assert_equal 'parked', state
172
255
  end
173
256
 
174
- def test_should_set_initial_state_before_setting_attributes
175
- @model.class_eval do
176
- attr_accessor :state_during_setter
177
-
178
- define_method(:value=) do |value|
179
- self.state_during_setter = state
180
- end
181
- end
182
-
183
- record = @model.new(:value => 1)
184
- assert_equal 'parked', record.state_during_setter
185
- end
257
+ record = @model.new(:value => 1)
258
+ assert_equal 'nil', record.state_during_setter
259
+ end
260
+
261
+ def test_should_not_set_initial_state_after_already_initialized
262
+ record = @model.new(:value => 1)
263
+ assert_equal 'parked', record.state
186
264
 
187
- def test_should_not_set_initial_state_after_already_initialized
188
- record = @model.new(:value => 1)
189
- assert_equal 'parked', record.state
190
-
191
- record.state = 'idling'
192
- record.attributes = {}
193
- assert_equal 'idling', record.state
265
+ record.state = 'idling'
266
+ record.attributes = {}
267
+ assert_equal 'idling', record.state
268
+ end
269
+
270
+ def test_should_use_stored_values_when_loading_from_database
271
+ @machine.state :idling
272
+
273
+ record = @model.find(@model.create(:state => 'idling').id)
274
+ assert_equal 'idling', record.state
275
+ end
276
+
277
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
278
+ @machine.state nil
279
+
280
+ record = @model.find(@model.create(:state => nil).id)
281
+ assert_nil record.state
282
+ end
283
+ end
284
+
285
+ class MachineWithColumnDefaultTest < BaseTestCase
286
+ def setup
287
+ @model = new_model do
288
+ connection.add_column :foo, :status, :string, :default => 'idling'
194
289
  end
290
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
291
+ @record = @model.new
195
292
  end
196
293
 
197
- class MachineWithDynamicInitialStateTest < BaseTestCase
198
- def setup
199
- @model = new_model do
200
- attr_accessor :value
294
+ def test_should_use_machine_default
295
+ assert_equal 'parked', @record.status
296
+ end
297
+ end
298
+
299
+ class MachineWithConflictingPredicateTest < BaseTestCase
300
+ def setup
301
+ @model = new_model do
302
+ def state?(*args)
303
+ true
201
304
  end
202
- @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked})
203
- @machine.state :parked
204
305
  end
205
306
 
206
- def test_should_set_initial_state_on_created_object
207
- record = @model.new
208
- assert_equal 'parked', record.state
209
- end
307
+ @machine = StateMachine::Machine.new(@model)
308
+ @record = @model.new
309
+ end
310
+
311
+ def test_should_not_define_attribute_predicate
312
+ assert @record.state?
313
+ end
314
+ end
315
+
316
+ class MachineWithColumnStateAttributeTest < BaseTestCase
317
+ def setup
318
+ @model = new_model
319
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
320
+ @machine.other_states(:idling)
210
321
 
211
- def test_should_still_set_attributes
212
- record = @model.new(:value => 1)
213
- assert_equal 1, record.value
214
- end
322
+ @record = @model.new
323
+ end
324
+
325
+ def test_should_not_override_the_column_reader
326
+ @record[:state] = 'parked'
327
+ assert_equal 'parked', @record.state
328
+ end
329
+
330
+ def test_should_not_override_the_column_writer
331
+ @record.state = 'parked'
332
+ assert_equal 'parked', @record[:state]
333
+ end
334
+
335
+ def test_should_have_an_attribute_predicate
336
+ assert @record.respond_to?(:state?)
337
+ end
338
+
339
+ def test_should_test_for_existence_on_predicate_without_parameters
340
+ assert @record.state?
215
341
 
216
- def test_should_still_allow_initialize_blocks
217
- block_args = nil
218
- record = @model.new do |*args|
219
- block_args = args
342
+ @record.state = nil
343
+ assert !@record.state?
344
+ end
345
+
346
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
347
+ assert !@record.state?(:idling)
348
+ end
349
+
350
+ def test_should_return_true_for_predicate_if_matches_current_value
351
+ assert @record.state?(:parked)
352
+ end
353
+
354
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
355
+ assert_raise(IndexError) { @record.state?(:invalid) }
356
+ end
357
+ end
358
+
359
+ class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
360
+ def setup
361
+ @model = new_model do
362
+ def initialize
363
+ # Skip attribute initialization
364
+ @initialized_state_machines = true
365
+ super
220
366
  end
221
-
222
- assert_equal [record], block_args
223
367
  end
224
368
 
225
- def test_should_set_attributes_prior_to_after_initialize_hook
226
- state = nil
227
- @model.class_eval {define_method(:after_initialize) {}} if ::ActiveRecord::VERSION::MAJOR <= 2
228
- @model.after_initialize do |record|
229
- state = record.state
230
- end
231
- @model.new
232
- assert_equal 'parked', state
369
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
370
+ @machine.other_states(:idling)
371
+ @record = @model.new
372
+ end
373
+
374
+ def test_should_not_define_a_reader_attribute_for_the_attribute
375
+ assert !@record.respond_to?(:status)
376
+ end
377
+
378
+ def test_should_not_define_a_writer_attribute_for_the_attribute
379
+ assert !@record.respond_to?(:status=)
380
+ end
381
+
382
+ def test_should_define_an_attribute_predicate
383
+ assert @record.respond_to?(:status?)
384
+ end
385
+
386
+ def test_should_raise_exception_on_predicate_without_parameters
387
+ old_verbose, $VERBOSE = $VERBOSE, nil
388
+ assert_raise(NoMethodError) { @record.status? }
389
+ ensure
390
+ $VERBOSE = old_verbose
391
+ end
392
+ end
393
+
394
+ class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase
395
+ def setup
396
+ @model = new_model do
397
+ attr_accessor :status
233
398
  end
234
399
 
235
- def test_should_set_initial_state_after_setting_attributes
236
- @model.class_eval do
237
- attr_accessor :state_during_setter
238
-
239
- define_method(:value=) do |value|
240
- self.state_during_setter = state || 'nil'
241
- end
242
- end
243
-
244
- record = @model.new(:value => 1)
245
- assert_equal 'nil', record.state_during_setter
400
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
401
+ @machine.other_states(:idling)
402
+ @record = @model.new
403
+ end
404
+
405
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
406
+ assert !@record.status?(:idling)
407
+ end
408
+
409
+ def test_should_return_true_for_predicate_if_matches_current_value
410
+ assert @record.status?(:parked)
411
+ end
412
+
413
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
414
+ assert_raise(IndexError) { @record.status?(:invalid) }
415
+ end
416
+
417
+ def test_should_set_initial_state_on_created_object
418
+ assert_equal 'parked', @record.status
419
+ end
420
+ end
421
+
422
+ class MachineWithAliasedAttributeTest < BaseTestCase
423
+ def setup
424
+ @model = new_model do
425
+ alias_attribute :vehicle_status, :state
246
426
  end
247
427
 
248
- def test_should_not_set_initial_state_after_already_initialized
249
- record = @model.new(:value => 1)
250
- assert_equal 'parked', record.state
251
-
252
- record.state = 'idling'
253
- record.attributes = {}
254
- assert_equal 'idling', record.state
255
- end
428
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
429
+ @machine.state :parked
430
+
431
+ @record = @model.new
256
432
  end
257
433
 
258
- class MachineWithColumnDefaultTest < BaseTestCase
259
- def setup
260
- @model = new_model do
261
- connection.add_column :foo, :status, :string, :default => 'idling'
262
- end
263
- @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
264
- @record = @model.new
265
- end
434
+ def test_should_check_custom_attribute_for_predicate
435
+ @record.vehicle_status = nil
436
+ assert !@record.status?(:parked)
266
437
 
267
- def test_should_use_machine_default
268
- assert_equal 'parked', @record.status
269
- end
438
+ @record.vehicle_status = 'parked'
439
+ assert @record.status?(:parked)
440
+ end
441
+ end
442
+
443
+ class MachineWithInitializedStateTest < BaseTestCase
444
+ def setup
445
+ @model = new_model
446
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
447
+ @machine.state nil, :idling
270
448
  end
271
449
 
272
- class MachineWithConflictingPredicateTest < BaseTestCase
273
- def setup
274
- @model = new_model do
275
- def state?(*args)
276
- true
277
- end
278
- end
279
-
280
- @machine = StateMachine::Machine.new(@model)
281
- @record = @model.new
450
+ def test_should_allow_nil_initial_state_when_static
451
+ record = @model.new(:state => nil)
452
+ assert_nil record.state
453
+ end
454
+
455
+ def test_should_allow_nil_initial_state_when_dynamic
456
+ @machine.initial_state = lambda {:parked}
457
+ record = @model.new(:state => nil)
458
+ assert_nil record.state
459
+ end
460
+
461
+ def test_should_allow_different_initial_state_when_static
462
+ record = @model.new(:state => 'idling')
463
+ assert_equal 'idling', record.state
464
+ end
465
+
466
+ def test_should_allow_different_initial_state_when_dynamic
467
+ @machine.initial_state = lambda {:parked}
468
+ record = @model.new(:state => 'idling')
469
+ assert_equal 'idling', record.state
470
+ end
471
+
472
+ def test_should_use_default_state_if_protected
473
+ @model.class_eval do
474
+ attr_protected :state
282
475
  end
283
476
 
284
- def test_should_not_define_attribute_predicate
285
- assert @record.state?
477
+ record = @model.new(:state => 'idling')
478
+ assert_equal 'parked', record.state
479
+ end
480
+ end
481
+
482
+ class MachineWithLoopbackTest < BaseTestCase
483
+ def setup
484
+ @model = new_model do
485
+ connection.add_column :foo, :updated_at, :datetime
286
486
  end
487
+
488
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
489
+ @machine.event :park
490
+
491
+ @record = @model.create(:updated_at => Time.now - 1)
492
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
493
+
494
+ @timestamp = @record.updated_at
495
+ @transition.perform
287
496
  end
288
497
 
289
- class MachineWithColumnStateAttributeTest < BaseTestCase
498
+ def test_should_update_record
499
+ assert_not_equal @timestamp, @record.updated_at
500
+ end
501
+ end
502
+
503
+ if ActiveRecord.const_defined?(:Dirty) || ActiveRecord::AttributeMethods.const_defined?(:Dirty)
504
+ class MachineWithDirtyAttributesTest < BaseTestCase
290
505
  def setup
291
506
  @model = new_model
292
507
  @machine = StateMachine::Machine.new(@model, :initial => :parked)
293
- @machine.other_states(:idling)
508
+ @machine.event :ignite
509
+ @machine.state :idling
294
510
 
295
- @record = @model.new
296
- end
297
-
298
- def test_should_not_override_the_column_reader
299
- @record[:state] = 'parked'
300
- assert_equal 'parked', @record.state
511
+ @record = @model.create
512
+
513
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
514
+ @transition.perform(false)
301
515
  end
302
516
 
303
- def test_should_not_override_the_column_writer
304
- @record.state = 'parked'
305
- assert_equal 'parked', @record[:state]
517
+ def test_should_include_state_in_changed_attributes
518
+ assert_equal %w(state), @record.changed
306
519
  end
307
520
 
308
- def test_should_have_an_attribute_predicate
309
- assert @record.respond_to?(:state?)
521
+ def test_should_track_attribute_change
522
+ assert_equal %w(parked idling), @record.changes['state']
310
523
  end
311
524
 
312
- def test_should_test_for_existence_on_predicate_without_parameters
313
- assert @record.state?
525
+ def test_should_not_reset_changes_on_multiple_transitions
526
+ transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
527
+ transition.perform(false)
314
528
 
315
- @record.state = nil
316
- assert !@record.state?
317
- end
318
-
319
- def test_should_return_false_for_predicate_if_does_not_match_current_value
320
- assert !@record.state?(:idling)
321
- end
322
-
323
- def test_should_return_true_for_predicate_if_matches_current_value
324
- assert @record.state?(:parked)
529
+ assert_equal %w(parked idling), @record.changes['state']
325
530
  end
326
531
 
327
- def test_should_raise_exception_for_predicate_if_invalid_state_specified
328
- assert_raise(IndexError) { @record.state?(:invalid) }
532
+ def test_should_not_have_changes_when_loaded_from_database
533
+ record = @model.find(@record.id)
534
+ assert !record.changed?
329
535
  end
330
536
  end
331
537
 
332
- class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
538
+ class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase
333
539
  def setup
334
- @model = new_model do
335
- def initialize
336
- # Skip attribute initialization
337
- @initialized_state_machines = true
338
- super
339
- end
340
- end
540
+ @model = new_model
541
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
542
+ @machine.event :park
341
543
 
342
- @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
343
- @machine.other_states(:idling)
344
- @record = @model.new
345
- end
346
-
347
- def test_should_not_define_a_reader_attribute_for_the_attribute
348
- assert !@record.respond_to?(:status)
349
- end
350
-
351
- def test_should_not_define_a_writer_attribute_for_the_attribute
352
- assert !@record.respond_to?(:status=)
544
+ @record = @model.create
545
+
546
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
547
+ @transition.perform(false)
353
548
  end
354
549
 
355
- def test_should_define_an_attribute_predicate
356
- assert @record.respond_to?(:status?)
550
+ def test_should_include_state_in_changed_attributes
551
+ assert_equal %w(state), @record.changed
357
552
  end
358
553
 
359
- def test_should_raise_exception_on_predicate_without_parameters
360
- old_verbose, $VERBOSE = $VERBOSE, nil
361
- assert_raise(NoMethodError) { @record.status? }
362
- ensure
363
- $VERBOSE = old_verbose
554
+ def test_should_track_attribute_changes
555
+ assert_equal %w(parked parked), @record.changes['state']
364
556
  end
365
557
  end
366
558
 
367
- class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase
559
+ class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase
368
560
  def setup
369
561
  @model = new_model do
370
- attr_accessor :status
562
+ connection.add_column :foo, :status, :string, :default => 'idling'
371
563
  end
372
-
373
564
  @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
374
- @machine.other_states(:idling)
375
- @record = @model.new
376
- end
377
-
378
- def test_should_return_false_for_predicate_if_does_not_match_current_value
379
- assert !@record.status?(:idling)
565
+ @machine.event :ignite
566
+ @machine.state :idling
567
+
568
+ @record = @model.create
569
+
570
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
571
+ @transition.perform(false)
380
572
  end
381
573
 
382
- def test_should_return_true_for_predicate_if_matches_current_value
383
- assert @record.status?(:parked)
574
+ def test_should_include_state_in_changed_attributes
575
+ assert_equal %w(status), @record.changed
384
576
  end
385
577
 
386
- def test_should_raise_exception_for_predicate_if_invalid_state_specified
387
- assert_raise(IndexError) { @record.status?(:invalid) }
578
+ def test_should_track_attribute_change
579
+ assert_equal %w(parked idling), @record.changes['status']
388
580
  end
389
581
 
390
- def test_should_set_initial_state_on_created_object
391
- assert_equal 'parked', @record.status
582
+ def test_should_not_reset_changes_on_multiple_transitions
583
+ transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
584
+ transition.perform(false)
585
+
586
+ assert_equal %w(parked idling), @record.changes['status']
392
587
  end
393
588
  end
394
589
 
395
- class MachineWithAliasedAttributeTest < BaseTestCase
590
+ class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase
396
591
  def setup
397
592
  @model = new_model do
398
- alias_attribute :vehicle_status, :state
593
+ connection.add_column :foo, :status, :string, :default => 'idling'
399
594
  end
595
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
596
+ @machine.event :park
400
597
 
401
- @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
402
- @machine.state :parked
598
+ @record = @model.create
403
599
 
404
- @record = @model.new
600
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
601
+ @transition.perform(false)
405
602
  end
406
603
 
407
- def test_should_check_custom_attribute_for_predicate
408
- @record.vehicle_status = nil
409
- assert !@record.status?(:parked)
410
-
411
- @record.vehicle_status = 'parked'
412
- assert @record.status?(:parked)
604
+ def test_should_include_state_in_changed_attributes
605
+ assert_equal %w(status), @record.changed
606
+ end
607
+
608
+ def test_should_track_attribute_changes
609
+ assert_equal %w(parked parked), @record.changes['status']
413
610
  end
414
611
  end
612
+ else
613
+ $stderr.puts 'Skipping ActiveRecord Dirty tests. `gem install active_record` >= v2.1.0 and try again.'
614
+ end
615
+
616
+ class MachineWithoutTransactionsTest < BaseTestCase
617
+ def setup
618
+ @model = new_model
619
+ @machine = StateMachine::Machine.new(@model, :use_transactions => false)
620
+ end
415
621
 
416
- class MachineWithInitializedStateTest < BaseTestCase
417
- def setup
418
- @model = new_model
419
- @machine = StateMachine::Machine.new(@model, :initial => :parked)
420
- @machine.state nil, :idling
622
+ def test_should_not_rollback_transaction_if_false
623
+ @machine.within_transaction(@model.new) do
624
+ @model.create
625
+ false
421
626
  end
422
627
 
423
- def test_should_allow_nil_initial_state_when_static
424
- record = @model.new(:state => nil)
425
- assert_nil record.state
628
+ assert_equal 1, @model.count
629
+ end
630
+
631
+ def test_should_not_rollback_transaction_if_true
632
+ @machine.within_transaction(@model.new) do
633
+ @model.create
634
+ true
426
635
  end
427
636
 
428
- def test_should_allow_nil_initial_state_when_dynamic
429
- @machine.initial_state = lambda {:parked}
430
- record = @model.new(:state => nil)
431
- assert_nil record.state
637
+ assert_equal 1, @model.count
638
+ end
639
+ end
640
+
641
+ class MachineWithTransactionsTest < BaseTestCase
642
+ def setup
643
+ @model = new_model
644
+ @machine = StateMachine::Machine.new(@model, :use_transactions => true)
645
+ end
646
+
647
+ def test_should_rollback_transaction_if_false
648
+ @machine.within_transaction(@model.new) do
649
+ @model.create
650
+ false
432
651
  end
433
652
 
434
- def test_should_allow_different_initial_state_when_static
435
- record = @model.new(:state => 'idling')
436
- assert_equal 'idling', record.state
653
+ assert_equal 0, @model.count
654
+ end
655
+
656
+ def test_should_not_rollback_transaction_if_true
657
+ @machine.within_transaction(@model.new) do
658
+ @model.create
659
+ true
437
660
  end
438
661
 
439
- def test_should_allow_different_initial_state_when_dynamic
440
- @machine.initial_state = lambda {:parked}
441
- record = @model.new(:state => 'idling')
442
- assert_equal 'idling', record.state
443
- end
662
+ assert_equal 1, @model.count
663
+ end
664
+ end
665
+
666
+ class MachineWithCallbacksTest < BaseTestCase
667
+ def setup
668
+ @model = new_model
669
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
670
+ @machine.other_states :idling
671
+ @machine.event :ignite
672
+
673
+ @record = @model.new(:state => 'parked')
674
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
675
+ end
676
+
677
+ def test_should_run_before_callbacks
678
+ called = false
679
+ @machine.before_transition {called = true}
444
680
 
445
- def test_should_use_default_state_if_protected
446
- @model.class_eval do
447
- attr_protected :state
448
- end
449
-
450
- record = @model.new(:state => 'idling')
451
- assert_equal 'parked', record.state
452
- end
681
+ @transition.perform
682
+ assert called
453
683
  end
454
684
 
455
- class MachineWithLoopbackTest < BaseTestCase
456
- def setup
457
- @model = new_model do
458
- connection.add_column :foo, :updated_at, :datetime
459
- end
460
-
461
- @machine = StateMachine::Machine.new(@model, :initial => :parked)
462
- @machine.event :park
463
-
464
- @record = @model.create(:updated_at => Time.now - 1)
465
- @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
466
-
467
- @timestamp = @record.updated_at
468
- @transition.perform
469
- end
685
+ def test_should_pass_record_to_before_callbacks_with_one_argument
686
+ record = nil
687
+ @machine.before_transition {|arg| record = arg}
470
688
 
471
- def test_should_update_record
472
- assert_not_equal @timestamp, @record.updated_at
473
- end
689
+ @transition.perform
690
+ assert_equal @record, record
474
691
  end
475
692
 
476
- if ActiveRecord.const_defined?(:Dirty) || ActiveRecord::AttributeMethods.const_defined?(:Dirty)
477
- class MachineWithDirtyAttributesTest < BaseTestCase
478
- def setup
479
- @model = new_model
480
- @machine = StateMachine::Machine.new(@model, :initial => :parked)
481
- @machine.event :ignite
482
- @machine.state :idling
483
-
484
- @record = @model.create
485
-
486
- @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
487
- @transition.perform(false)
488
- end
489
-
490
- def test_should_include_state_in_changed_attributes
491
- assert_equal %w(state), @record.changed
492
- end
493
-
494
- def test_should_track_attribute_change
495
- assert_equal %w(parked idling), @record.changes['state']
496
- end
497
-
498
- def test_should_not_reset_changes_on_multiple_transitions
499
- transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
500
- transition.perform(false)
501
-
502
- assert_equal %w(parked idling), @record.changes['state']
503
- end
504
- end
693
+ def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments
694
+ callback_args = nil
695
+ @machine.before_transition {|*args| callback_args = args}
505
696
 
506
- class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase
507
- def setup
508
- @model = new_model
509
- @machine = StateMachine::Machine.new(@model, :initial => :parked)
510
- @machine.event :park
511
-
512
- @record = @model.create
513
-
514
- @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
515
- @transition.perform(false)
516
- end
517
-
518
- def test_should_include_state_in_changed_attributes
519
- assert_equal %w(state), @record.changed
520
- end
521
-
522
- def test_should_track_attribute_changes
523
- assert_equal %w(parked parked), @record.changes['state']
524
- end
525
- end
697
+ @transition.perform
698
+ assert_equal [@record, @transition], callback_args
699
+ end
700
+
701
+ def test_should_run_before_callbacks_outside_the_context_of_the_record
702
+ context = nil
703
+ @machine.before_transition {context = self}
526
704
 
527
- class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase
528
- def setup
529
- @model = new_model do
530
- connection.add_column :foo, :status, :string, :default => 'idling'
531
- end
532
- @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
533
- @machine.event :ignite
534
- @machine.state :idling
535
-
536
- @record = @model.create
537
-
538
- @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
539
- @transition.perform(false)
540
- end
541
-
542
- def test_should_include_state_in_changed_attributes
543
- assert_equal %w(status), @record.changed
544
- end
545
-
546
- def test_should_track_attribute_change
547
- assert_equal %w(parked idling), @record.changes['status']
548
- end
549
-
550
- def test_should_not_reset_changes_on_multiple_transitions
551
- transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
552
- transition.perform(false)
553
-
554
- assert_equal %w(parked idling), @record.changes['status']
555
- end
556
- end
705
+ @transition.perform
706
+ assert_equal self, context
707
+ end
708
+
709
+ def test_should_run_after_callbacks
710
+ called = false
711
+ @machine.after_transition {called = true}
557
712
 
558
- class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase
559
- def setup
560
- @model = new_model do
561
- connection.add_column :foo, :status, :string, :default => 'idling'
562
- end
563
- @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
564
- @machine.event :park
565
-
566
- @record = @model.create
567
-
568
- @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
569
- @transition.perform(false)
570
- end
571
-
572
- def test_should_include_state_in_changed_attributes
573
- assert_equal %w(status), @record.changed
574
- end
575
-
576
- def test_should_track_attribute_changes
577
- assert_equal %w(parked parked), @record.changes['status']
578
- end
579
- end
713
+ @transition.perform
714
+ assert called
580
715
  end
581
716
 
582
- class MachineWithoutTransactionsTest < BaseTestCase
583
- def setup
584
- @model = new_model
585
- @machine = StateMachine::Machine.new(@model, :use_transactions => false)
586
- end
717
+ def test_should_pass_record_to_after_callbacks_with_one_argument
718
+ record = nil
719
+ @machine.after_transition {|arg| record = arg}
587
720
 
588
- def test_should_not_rollback_transaction_if_false
589
- @machine.within_transaction(@model.new) do
590
- @model.create
591
- false
592
- end
593
-
594
- assert_equal 1, @model.count
595
- end
721
+ @transition.perform
722
+ assert_equal @record, record
723
+ end
724
+
725
+ def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments
726
+ callback_args = nil
727
+ @machine.after_transition {|*args| callback_args = args}
596
728
 
597
- def test_should_not_rollback_transaction_if_true
598
- @machine.within_transaction(@model.new) do
599
- @model.create
600
- true
601
- end
602
-
603
- assert_equal 1, @model.count
604
- end
729
+ @transition.perform
730
+ assert_equal [@record, @transition], callback_args
605
731
  end
606
732
 
607
- class MachineWithTransactionsTest < BaseTestCase
608
- def setup
609
- @model = new_model
610
- @machine = StateMachine::Machine.new(@model, :use_transactions => true)
611
- end
733
+ def test_should_run_after_callbacks_outside_the_context_of_the_record
734
+ context = nil
735
+ @machine.after_transition {context = self}
612
736
 
613
- def test_should_rollback_transaction_if_false
614
- @machine.within_transaction(@model.new) do
615
- @model.create
616
- false
617
- end
618
-
619
- assert_equal 0, @model.count
620
- end
737
+ @transition.perform
738
+ assert_equal self, context
739
+ end
740
+
741
+ def test_should_run_around_callbacks
742
+ before_called = false
743
+ after_called = false
744
+ @machine.around_transition {|block| before_called = true; block.call; after_called = true}
745
+
746
+ @transition.perform
747
+ assert before_called
748
+ assert after_called
749
+ end
750
+
751
+ def test_should_include_transition_states_in_known_states
752
+ @machine.before_transition :to => :first_gear, :do => lambda {}
621
753
 
622
- def test_should_not_rollback_transaction_if_true
623
- @machine.within_transaction(@model.new) do
624
- @model.create
625
- true
626
- end
627
-
628
- assert_equal 1, @model.count
629
- end
754
+ assert_equal [:parked, :idling, :first_gear], @machine.states.map {|state| state.name}
630
755
  end
631
756
 
632
- class MachineWithCallbacksTest < BaseTestCase
633
- def setup
634
- @model = new_model
635
- @machine = StateMachine::Machine.new(@model, :initial => :parked)
636
- @machine.other_states :idling
637
- @machine.event :ignite
638
-
639
- @record = @model.new(:state => 'parked')
640
- @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
641
- end
757
+ def test_should_allow_symbolic_callbacks
758
+ callback_args = nil
642
759
 
643
- def test_should_run_before_callbacks
644
- called = false
645
- @machine.before_transition(lambda {called = true})
646
-
647
- @transition.perform
648
- assert called
760
+ klass = class << @record; self; end
761
+ klass.send(:define_method, :after_ignite) do |*args|
762
+ callback_args = args
649
763
  end
650
764
 
651
- def test_should_pass_record_to_before_callbacks_with_one_argument
652
- record = nil
653
- @machine.before_transition(lambda {|arg| record = arg})
654
-
655
- @transition.perform
656
- assert_equal @record, record
657
- end
765
+ @machine.before_transition(:after_ignite)
658
766
 
659
- def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments
660
- callback_args = nil
661
- @machine.before_transition(lambda {|*args| callback_args = args})
662
-
663
- @transition.perform
664
- assert_equal [@record, @transition], callback_args
767
+ @transition.perform
768
+ assert_equal [@transition], callback_args
769
+ end
770
+
771
+ def test_should_allow_string_callbacks
772
+ class << @record
773
+ attr_reader :callback_result
665
774
  end
666
775
 
667
- def test_should_run_before_callbacks_outside_the_context_of_the_record
668
- context = nil
669
- @machine.before_transition(lambda {context = self})
670
-
671
- @transition.perform
672
- assert_equal self, context
673
- end
776
+ @machine.before_transition('@callback_result = [1, 2, 3]')
777
+ @transition.perform
674
778
 
675
- def test_should_run_after_callbacks
676
- called = false
677
- @machine.after_transition(lambda {called = true})
678
-
679
- @transition.perform
680
- assert called
681
- end
779
+ assert_equal [1, 2, 3], @record.callback_result
780
+ end
781
+ end
782
+
783
+ class MachineWithFailedBeforeCallbacksTest < BaseTestCase
784
+ def setup
785
+ @callbacks = []
786
+
787
+ @model = new_model
788
+ @machine = StateMachine::Machine.new(@model)
789
+ @machine.state :parked, :idling
790
+ @machine.event :ignite
791
+ @machine.before_transition {@callbacks << :before_1; false}
792
+ @machine.before_transition {@callbacks << :before_2}
793
+ @machine.after_transition {@callbacks << :after}
794
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
795
+
796
+ @record = @model.new(:state => 'parked')
797
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
798
+ @result = @transition.perform
799
+ end
800
+
801
+ def test_should_not_be_successful
802
+ assert !@result
803
+ end
804
+
805
+ def test_should_not_change_current_state
806
+ assert_equal 'parked', @record.state
807
+ end
808
+
809
+ def test_should_not_run_action
810
+ assert @record.new_record?
811
+ end
812
+
813
+ def test_should_not_run_further_callbacks
814
+ assert_equal [:before_1], @callbacks
815
+ end
816
+ end
817
+
818
+ class MachineWithFailedActionTest < BaseTestCase
819
+ def setup
820
+ @model = new_model do
821
+ validates_inclusion_of :state, :in => %w(first_gear)
822
+ end
823
+
824
+ @machine = StateMachine::Machine.new(@model)
825
+ @machine.state :parked, :idling
826
+ @machine.event :ignite
827
+
828
+ @callbacks = []
829
+ @machine.before_transition {@callbacks << :before}
830
+ @machine.after_transition {@callbacks << :after}
831
+ @machine.after_transition(:include_failures => true) {@callbacks << :after_failure}
832
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
833
+ @machine.around_transition(:include_failures => true) do |block|
834
+ @callbacks << :around_before_failure
835
+ block.call
836
+ @callbacks << :around_after_failure
837
+ end
838
+
839
+ @record = @model.new(:state => 'parked')
840
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
841
+ @result = @transition.perform
842
+ end
843
+
844
+ def test_should_not_be_successful
845
+ assert !@result
846
+ end
847
+
848
+ def test_should_not_change_current_state
849
+ assert_equal 'parked', @record.state
850
+ end
851
+
852
+ def test_should_not_save_record
853
+ assert @record.new_record?
854
+ end
855
+
856
+ def test_should_run_before_callbacks_and_after_callbacks_with_failures
857
+ assert_equal [:before, :around_before, :around_before_failure, :around_after_failure, :after_failure], @callbacks
858
+ end
859
+ end
860
+
861
+ class MachineWithFailedAfterCallbacksTest < BaseTestCase
862
+ def setup
863
+ @callbacks = []
864
+
865
+ @model = new_model
866
+ @machine = StateMachine::Machine.new(@model)
867
+ @machine.state :parked, :idling
868
+ @machine.event :ignite
869
+ @machine.after_transition {@callbacks << :after_1; false}
870
+ @machine.after_transition {@callbacks << :after_2}
871
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
872
+
873
+ @record = @model.new(:state => 'parked')
874
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
875
+ @result = @transition.perform
876
+ end
877
+
878
+ def test_should_be_successful
879
+ assert @result
880
+ end
881
+
882
+ def test_should_change_current_state
883
+ assert_equal 'idling', @record.state
884
+ end
885
+
886
+ def test_should_save_record
887
+ assert !@record.new_record?
888
+ end
889
+
890
+ def test_should_not_run_further_after_callbacks
891
+ assert_equal [:around_before, :around_after, :after_1], @callbacks
892
+ end
893
+ end
894
+
895
+ class MachineWithValidationsTest < BaseTestCase
896
+ def setup
897
+ @model = new_model
898
+ @machine = StateMachine::Machine.new(@model)
899
+ @machine.state :parked
682
900
 
683
- def test_should_pass_record_to_after_callbacks_with_one_argument
684
- record = nil
685
- @machine.after_transition(lambda {|arg| record = arg})
686
-
687
- @transition.perform
688
- assert_equal @record, record
689
- end
901
+ @record = @model.new
902
+ end
903
+
904
+ def test_should_invalidate_using_errors
905
+ I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:I18n)
906
+ @record.state = 'parked'
690
907
 
691
- def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments
692
- callback_args = nil
693
- @machine.after_transition(lambda {|*args| callback_args = args})
694
-
695
- @transition.perform
696
- assert_equal [@record, @transition], callback_args
697
- end
908
+ @machine.invalidate(@record, :state, :invalid_transition, [[:event, :park]])
909
+ assert_equal ['State cannot transition via "park"'], @record.errors.full_messages
910
+ end
911
+
912
+ def test_should_auto_prefix_custom_attributes_on_invalidation
913
+ @machine.invalidate(@record, :event, :invalid)
698
914
 
699
- def test_should_run_after_callbacks_outside_the_context_of_the_record
700
- context = nil
701
- @machine.after_transition(lambda {context = self})
702
-
703
- @transition.perform
704
- assert_equal self, context
705
- end
915
+ assert_equal ['State event is invalid'], @record.errors.full_messages
916
+ end
917
+
918
+ def test_should_clear_errors_on_reset
919
+ @record.state = 'parked'
920
+ @record.errors.add(:state, 'is invalid')
706
921
 
707
- def test_should_include_transition_states_in_known_states
708
- @machine.before_transition :to => :first_gear, :do => lambda {}
709
-
710
- assert_equal [:parked, :idling, :first_gear], @machine.states.map {|state| state.name}
922
+ @machine.reset(@record)
923
+ assert_equal [], @record.errors.full_messages
924
+ end
925
+
926
+ def test_should_be_valid_if_state_is_known
927
+ @record.state = 'parked'
928
+
929
+ assert @record.valid?
930
+ end
931
+
932
+ def test_should_not_be_valid_if_state_is_unknown
933
+ @record.state = 'invalid'
934
+
935
+ assert !@record.valid?
936
+ assert_equal ['State is invalid'], @record.errors.full_messages
937
+ end
938
+ end
939
+
940
+ class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
941
+ def setup
942
+ @model = new_model do
943
+ alias_attribute :status, :state
711
944
  end
712
945
 
713
- def test_should_allow_symbolic_callbacks
714
- callback_args = nil
715
-
716
- klass = class << @record; self; end
717
- klass.send(:define_method, :after_ignite) do |*args|
718
- callback_args = args
719
- end
720
-
721
- @machine.before_transition(:after_ignite)
722
-
723
- @transition.perform
724
- assert_equal [@transition], callback_args
946
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :state)
947
+ @machine.state :parked
948
+
949
+ @record = @model.new
950
+ end
951
+
952
+ def test_should_add_validation_errors_to_custom_attribute
953
+ @record.state = 'invalid'
954
+
955
+ assert !@record.valid?
956
+ assert_equal ['State is invalid'], @record.errors.full_messages
957
+
958
+ @record.state = 'parked'
959
+ assert @record.valid?
960
+ end
961
+ end
962
+
963
+ class MachineWithStateDrivenValidationsTest < BaseTestCase
964
+ def setup
965
+ @model = new_model do
966
+ attr_accessor :seatbelt
725
967
  end
726
968
 
727
- def test_should_allow_string_callbacks
728
- class << @record
729
- attr_reader :callback_result
730
- end
731
-
732
- @machine.before_transition('@callback_result = [1, 2, 3]')
733
- @transition.perform
734
-
735
- assert_equal [1, 2, 3], @record.callback_result
969
+ @machine = StateMachine::Machine.new(@model)
970
+ @machine.state :first_gear, :second_gear do
971
+ validates_presence_of :seatbelt
736
972
  end
973
+ @machine.other_states :parked
737
974
  end
738
975
 
739
- class MachineWithFailedBeforeCallbacksTest < BaseTestCase
740
- def setup
741
- @before_count = 0
742
- @after_count = 0
743
-
744
- @model = new_model
745
- @machine = StateMachine::Machine.new(@model)
746
- @machine.state :parked, :idling
747
- @machine.event :ignite
748
- @machine.before_transition(lambda {@before_count += 1; false})
749
- @machine.before_transition(lambda {@before_count += 1})
750
- @machine.after_transition(lambda {@after_count += 1})
751
-
752
- @record = @model.new(:state => 'parked')
753
- @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
754
- @result = @transition.perform
976
+ def test_should_be_valid_if_validation_fails_outside_state_scope
977
+ record = @model.new(:state => 'parked', :seatbelt => nil)
978
+ assert record.valid?
979
+ end
980
+
981
+ def test_should_be_invalid_if_validation_fails_within_state_scope
982
+ record = @model.new(:state => 'first_gear', :seatbelt => nil)
983
+ assert !record.valid?
984
+ end
985
+
986
+ def test_should_be_valid_if_validation_succeeds_within_state_scope
987
+ record = @model.new(:state => 'second_gear', :seatbelt => true)
988
+ assert record.valid?
989
+ end
990
+ end
991
+
992
+ class MachineWithEventAttributesOnValidationTest < BaseTestCase
993
+ def setup
994
+ @model = new_model
995
+ @machine = StateMachine::Machine.new(@model)
996
+ @machine.event :ignite do
997
+ transition :parked => :idling
755
998
  end
756
999
 
757
- def test_should_not_be_successful
758
- assert !@result
759
- end
1000
+ @record = @model.new
1001
+ @record.state = 'parked'
1002
+ @record.state_event = 'ignite'
1003
+ end
1004
+
1005
+ def test_should_fail_if_event_is_invalid
1006
+ @record.state_event = 'invalid'
1007
+ assert !@record.valid?
1008
+ assert_equal ['State event is invalid'], @record.errors.full_messages
1009
+ end
1010
+
1011
+ def test_should_fail_if_event_has_no_transition
1012
+ @record.state = 'idling'
1013
+ assert !@record.valid?
1014
+ assert_equal ['State event cannot transition when idling'], @record.errors.full_messages
1015
+ end
1016
+
1017
+ def test_should_be_successful_if_event_has_transition
1018
+ assert @record.valid?
1019
+ end
1020
+
1021
+ def test_should_run_before_callbacks
1022
+ ran_callback = false
1023
+ @machine.before_transition { ran_callback = true }
760
1024
 
761
- def test_should_not_change_current_state
762
- assert_equal 'parked', @record.state
763
- end
1025
+ @record.valid?
1026
+ assert ran_callback
1027
+ end
1028
+
1029
+ def test_should_run_around_callbacks_before_yield
1030
+ ran_callback = false
1031
+ @machine.around_transition {|block| ran_callback = true; block.call }
764
1032
 
765
- def test_should_not_run_action
766
- assert @record.new_record?
767
- end
1033
+ @record.valid?
1034
+ assert ran_callback
1035
+ end
1036
+
1037
+ def test_should_persist_new_state
1038
+ @record.valid?
1039
+ assert_equal 'idling', @record.state
1040
+ end
1041
+
1042
+ def test_should_not_run_after_callbacks
1043
+ ran_callback = false
1044
+ @machine.after_transition { ran_callback = true }
768
1045
 
769
- def test_should_not_run_further_before_callbacks
770
- assert_equal 1, @before_count
1046
+ @record.valid?
1047
+ assert !ran_callback
1048
+ end
1049
+
1050
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
1051
+ @model.class_eval do
1052
+ attr_accessor :seatbelt
1053
+ validates_presence_of :seatbelt
771
1054
  end
772
1055
 
773
- def test_should_not_run_after_callbacks
774
- assert_equal 0, @after_count
775
- end
1056
+ ran_callback = false
1057
+ @machine.after_transition { ran_callback = true }
1058
+
1059
+ @record.valid?
1060
+ assert !ran_callback
776
1061
  end
777
1062
 
778
- class MachineWithFailedActionTest < BaseTestCase
779
- def setup
780
- @model = new_model do
781
- validates_inclusion_of :state, :in => %w(first_gear)
782
- end
783
-
784
- @machine = StateMachine::Machine.new(@model)
785
- @machine.state :parked, :idling
786
- @machine.event :ignite
787
-
788
- @before_transition_called = false
789
- @after_transition_called = false
790
- @after_transition_with_failures_called = false
791
- @machine.before_transition(lambda {@before_transition_called = true})
792
- @machine.after_transition(lambda {@after_transition_called = true})
793
- @machine.after_transition(lambda {@after_transition_with_failures_called = true}, :include_failures => true)
794
-
795
- @record = @model.new(:state => 'parked')
796
- @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
797
- @result = @transition.perform
1063
+ def test_should_run_after_callbacks_with_failures_enabled_if_validation_fails
1064
+ @model.class_eval do
1065
+ attr_accessor :seatbelt
1066
+ validates_presence_of :seatbelt
798
1067
  end
799
1068
 
800
- def test_should_not_be_successful
801
- assert !@result
802
- end
1069
+ ran_callback = false
1070
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
803
1071
 
804
- def test_should_not_change_current_state
805
- assert_equal 'parked', @record.state
806
- end
1072
+ @record.valid?
1073
+ assert ran_callback
1074
+ end
1075
+
1076
+ def test_should_not_run_around_callbacks_after_yield
1077
+ ran_callback = false
1078
+ @machine.around_transition {|block| block.call; ran_callback = true }
807
1079
 
808
- def test_should_not_save_record
809
- assert @record.new_record?
1080
+ @record.valid?
1081
+ assert !ran_callback
1082
+ end
1083
+
1084
+ def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails
1085
+ @model.class_eval do
1086
+ attr_accessor :seatbelt
1087
+ validates_presence_of :seatbelt
810
1088
  end
811
1089
 
812
- def test_should_run_before_callback
813
- assert @before_transition_called
1090
+ ran_callback = false
1091
+ @machine.around_transition {|block| block.call; ran_callback = true }
1092
+
1093
+ @record.valid?
1094
+ assert !ran_callback
1095
+ end
1096
+
1097
+ def test_should_run_around_callbacks_after_yield_with_failures_enabled_if_validation_fails
1098
+ @model.class_eval do
1099
+ attr_accessor :seatbelt
1100
+ validates_presence_of :seatbelt
814
1101
  end
815
1102
 
816
- def test_should_not_run_after_callback_if_not_including_failures
817
- assert !@after_transition_called
1103
+ ran_callback = false
1104
+ @machine.around_transition(:include_failures => true) {|block| block.call; ran_callback = true }
1105
+
1106
+ @record.valid?
1107
+ assert ran_callback
1108
+ end
1109
+
1110
+ def test_should_not_run_before_transitions_within_transaction
1111
+ @machine.before_transition { @model.create; raise ActiveRecord::Rollback }
1112
+
1113
+ begin
1114
+ @record.valid?
1115
+ rescue Exception
818
1116
  end
819
1117
 
820
- def test_should_run_after_callback_if_including_failures
821
- assert @after_transition_with_failures_called
1118
+ assert_equal 1, @model.count
1119
+ end
1120
+ end
1121
+
1122
+ class MachineWithEventAttributesOnSaveTest < BaseTestCase
1123
+ def setup
1124
+ @model = new_model
1125
+ @machine = StateMachine::Machine.new(@model)
1126
+ @machine.event :ignite do
1127
+ transition :parked => :idling
822
1128
  end
1129
+
1130
+ @record = @model.new
1131
+ @record.state = 'parked'
1132
+ @record.state_event = 'ignite'
823
1133
  end
824
1134
 
825
- class MachineWithFailedAfterCallbacksTest < BaseTestCase
826
- def setup
827
- @after_count = 0
828
-
829
- @model = new_model
830
- @machine = StateMachine::Machine.new(@model)
831
- @machine.state :parked, :idling
832
- @machine.event :ignite
833
- @machine.after_transition(lambda {@after_count += 1; false})
834
- @machine.after_transition(lambda {@after_count += 1})
835
-
836
- @record = @model.new(:state => 'parked')
837
- @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
838
- @result = @transition.perform
1135
+ def test_should_fail_if_event_is_invalid
1136
+ @record.state_event = 'invalid'
1137
+ assert_equal false, @record.save
1138
+ end
1139
+
1140
+ def test_should_fail_if_event_has_no_transition
1141
+ @record.state = 'idling'
1142
+ assert_equal false, @record.save
1143
+ end
1144
+
1145
+ def test_should_run_before_callbacks
1146
+ ran_callback = false
1147
+ @machine.before_transition { ran_callback = true }
1148
+
1149
+ @record.save
1150
+ assert ran_callback
1151
+ end
1152
+
1153
+ def test_should_run_before_callbacks_once
1154
+ before_count = 0
1155
+ @machine.before_transition { before_count += 1 }
1156
+
1157
+ @record.save
1158
+ assert_equal 1, before_count
1159
+ end
1160
+
1161
+ def test_should_run_around_callbacks_before_yield
1162
+ ran_callback = false
1163
+ @machine.around_transition {|block| ran_callback = true; block.call }
1164
+
1165
+ @record.save
1166
+ assert ran_callback
1167
+ end
1168
+
1169
+ def test_should_run_around_callbacks_before_yield_once
1170
+ around_before_count = 0
1171
+ @machine.around_transition {|block| around_before_count += 1; block.call }
1172
+
1173
+ @record.save
1174
+ assert_equal 1, around_before_count
1175
+ end
1176
+
1177
+ def test_should_persist_new_state
1178
+ @record.save
1179
+ assert_equal 'idling', @record.state
1180
+ end
1181
+
1182
+ def test_should_run_after_callbacks
1183
+ ran_callback = false
1184
+ @machine.after_transition { ran_callback = true }
1185
+
1186
+ @record.save
1187
+ assert ran_callback
1188
+ end
1189
+
1190
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
1191
+ @model.before_create {|record| false}
1192
+
1193
+ ran_callback = false
1194
+ @machine.after_transition { ran_callback = true }
1195
+
1196
+ begin; @record.save; rescue; end
1197
+ assert !ran_callback
1198
+ end
1199
+
1200
+ def test_should_run_after_callbacks_with_failures_enabled_if_fails
1201
+ @model.before_create {|record| false}
1202
+
1203
+ ran_callback = false
1204
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
1205
+
1206
+ begin; @record.save; rescue; end
1207
+ assert ran_callback
1208
+ end
1209
+
1210
+ def test_should_not_run_around_callbacks_with_failures_disabled_if_fails
1211
+ @model.before_create {|record| false}
1212
+
1213
+ ran_callback = false
1214
+ @machine.around_transition {|block| block.call; ran_callback = true }
1215
+
1216
+ begin; @record.save; rescue; end
1217
+ assert !ran_callback
1218
+ end
1219
+
1220
+ def test_should_run_around_callbacks_after_yield
1221
+ ran_callback = false
1222
+ @machine.around_transition {|block| block.call; ran_callback = true }
1223
+
1224
+ @record.save
1225
+ assert ran_callback
1226
+ end
1227
+
1228
+ def test_should_run_around_callbacks_after_yield_with_failures_enabled_if_fails
1229
+ @model.before_create {|record| false}
1230
+
1231
+ ran_callback = false
1232
+ @machine.around_transition(:include_failures => true) {|block| block.call; ran_callback = true }
1233
+
1234
+ begin; @record.save; rescue; end
1235
+ assert ran_callback
1236
+ end
1237
+
1238
+ def test_should_run_before_transitions_within_transaction
1239
+ @machine.before_transition { @model.create; raise ActiveRecord::Rollback }
1240
+
1241
+ begin
1242
+ @record.save
1243
+ rescue Exception
839
1244
  end
840
1245
 
841
- def test_should_be_successful
842
- assert @result
1246
+ assert_equal 0, @model.count
1247
+ end
1248
+
1249
+ def test_should_run_after_transitions_within_transaction
1250
+ @machine.after_transition { @model.create; raise ActiveRecord::Rollback }
1251
+
1252
+ begin
1253
+ @record.save
1254
+ rescue Exception
843
1255
  end
844
1256
 
845
- def test_should_change_current_state
846
- assert_equal 'idling', @record.state
1257
+ assert_equal 0, @model.count
1258
+ end
1259
+
1260
+ def test_should_run_around_transition_within_transaction
1261
+ @machine.around_transition { @model.create; raise ActiveRecord::Rollback }
1262
+
1263
+ begin
1264
+ @record.save
1265
+ rescue Exception
847
1266
  end
848
1267
 
849
- def test_should_save_record
850
- assert !@record.new_record?
1268
+ assert_equal 0, @model.count
1269
+ end
1270
+ end
1271
+
1272
+ class MachineWithEventAttributesOnSaveBangTest < BaseTestCase
1273
+ def setup
1274
+ @model = new_model
1275
+ @machine = StateMachine::Machine.new(@model)
1276
+ @machine.event :ignite do
1277
+ transition :parked => :idling
851
1278
  end
852
1279
 
853
- def test_should_not_run_further_after_callbacks
854
- assert_equal 1, @after_count
1280
+ @record = @model.new
1281
+ @record.state = 'parked'
1282
+ @record.state_event = 'ignite'
1283
+ end
1284
+
1285
+ def test_should_fail_if_event_is_invalid
1286
+ @record.state_event = 'invalid'
1287
+ assert_raise(ActiveRecord::RecordInvalid) { @record.save! }
1288
+ end
1289
+
1290
+ def test_should_fail_if_event_has_no_transition
1291
+ @record.state = 'idling'
1292
+ assert_raise(ActiveRecord::RecordInvalid) { @record.save! }
1293
+ end
1294
+
1295
+ def test_should_be_successful_if_event_has_transition
1296
+ assert_equal true, @record.save!
1297
+ end
1298
+
1299
+ def test_should_run_before_callbacks
1300
+ ran_callback = false
1301
+ @machine.before_transition { ran_callback = true }
1302
+
1303
+ @record.save!
1304
+ assert ran_callback
1305
+ end
1306
+
1307
+ def test_should_run_before_callbacks_once
1308
+ before_count = 0
1309
+ @machine.before_transition { before_count += 1 }
1310
+
1311
+ @record.save!
1312
+ assert_equal 1, before_count
1313
+ end
1314
+
1315
+ def test_should_run_around_callbacks_before_yield
1316
+ ran_callback = false
1317
+ @machine.around_transition {|block| ran_callback = true; block.call }
1318
+
1319
+ @record.save!
1320
+ assert ran_callback
1321
+ end
1322
+
1323
+ def test_should_run_around_callbacks_before_yield_once
1324
+ around_before_count = 0
1325
+ @machine.around_transition {|block| around_before_count += 1; block.call }
1326
+
1327
+ @record.save!
1328
+ assert_equal 1, around_before_count
1329
+ end
1330
+
1331
+ def test_should_persist_new_state
1332
+ @record.save!
1333
+ assert_equal 'idling', @record.state
1334
+ end
1335
+
1336
+ def test_should_run_after_callbacks
1337
+ ran_callback = false
1338
+ @machine.after_transition { ran_callback = true }
1339
+
1340
+ @record.save!
1341
+ assert ran_callback
1342
+ end
1343
+
1344
+ def test_should_run_around_callbacks_after_yield
1345
+ ran_callback = false
1346
+ @machine.around_transition {|block| block.call; ran_callback = true }
1347
+
1348
+ @record.save!
1349
+ assert ran_callback
1350
+ end
1351
+ end
1352
+
1353
+ class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
1354
+ def setup
1355
+ @superclass = new_model do
1356
+ def persist
1357
+ create_or_update
1358
+ end
1359
+ end
1360
+ @model = Class.new(@superclass)
1361
+ @machine = StateMachine::Machine.new(@model, :action => :persist)
1362
+ @machine.event :ignite do
1363
+ transition :parked => :idling
855
1364
  end
1365
+
1366
+ @record = @model.new
1367
+ @record.state = 'parked'
1368
+ @record.state_event = 'ignite'
1369
+ end
1370
+
1371
+ def test_should_not_transition_on_valid?
1372
+ @record.valid?
1373
+ assert_equal 'parked', @record.state
1374
+ end
1375
+
1376
+ def test_should_not_transition_on_save
1377
+ @record.save
1378
+ assert_equal 'parked', @record.state
1379
+ end
1380
+
1381
+ def test_should_not_transition_on_save!
1382
+ @record.save!
1383
+ assert_equal 'parked', @record.state
1384
+ end
1385
+
1386
+ def test_should_transition_on_custom_action
1387
+ @record.persist
1388
+ assert_equal 'idling', @record.state
1389
+ end
1390
+ end
1391
+
1392
+ class MachineWithObserversTest < BaseTestCase
1393
+ def setup
1394
+ @model = new_model
1395
+ @machine = StateMachine::Machine.new(@model)
1396
+ @machine.state :parked, :idling
1397
+ @machine.event :ignite
1398
+ @record = @model.new(:state => 'parked')
1399
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
856
1400
  end
857
1401
 
858
- class MachineWithValidationsTest < BaseTestCase
859
- def setup
860
- @model = new_model
861
- @machine = StateMachine::Machine.new(@model)
862
- @machine.state :parked
863
-
864
- @record = @model.new
865
- end
866
-
867
- def test_should_invalidate_using_errors
868
- I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:I18n)
869
- @record.state = 'parked'
870
-
871
- @machine.invalidate(@record, :state, :invalid_transition, [[:event, :park]])
872
- assert_equal ['State cannot transition via "park"'], @record.errors.full_messages
1402
+ def test_should_call_all_transition_callback_permutations
1403
+ callbacks = [
1404
+ :before_ignite_from_parked_to_idling,
1405
+ :before_ignite_from_parked,
1406
+ :before_ignite_to_idling,
1407
+ :before_ignite,
1408
+ :before_transition_state_from_parked_to_idling,
1409
+ :before_transition_state_from_parked,
1410
+ :before_transition_state_to_idling,
1411
+ :before_transition_state,
1412
+ :before_transition
1413
+ ]
1414
+
1415
+ notified = false
1416
+ observer = new_observer(@model) do
1417
+ callbacks.each do |callback|
1418
+ define_method(callback) do |*args|
1419
+ notifications << callback
1420
+ end
1421
+ end
873
1422
  end
874
1423
 
875
- def test_should_auto_prefix_custom_attributes_on_invalidation
876
- @machine.invalidate(@record, :event, :invalid)
877
-
878
- assert_equal ['State event is invalid'], @record.errors.full_messages
879
- end
1424
+ instance = observer.instance
880
1425
 
881
- def test_should_clear_errors_on_reset
882
- @record.state = 'parked'
883
- @record.errors.add(:state, 'is invalid')
884
-
885
- @machine.reset(@record)
886
- assert_equal [], @record.errors.full_messages
1426
+ @transition.perform
1427
+ assert_equal callbacks, instance.notifications
1428
+ end
1429
+
1430
+ def test_should_pass_record_and_transition_to_before_callbacks
1431
+ observer = new_observer(@model) do
1432
+ def before_transition(*args)
1433
+ notifications << args
1434
+ end
887
1435
  end
1436
+ instance = observer.instance
888
1437
 
889
- def test_should_be_valid_if_state_is_known
890
- @record.state = 'parked'
891
-
892
- assert @record.valid?
1438
+ @transition.perform
1439
+ assert_equal [[@record, @transition]], instance.notifications
1440
+ end
1441
+
1442
+ def test_should_pass_record_and_transition_to_after_callbacks
1443
+ observer = new_observer(@model) do
1444
+ def after_transition(*args)
1445
+ notifications << args
1446
+ end
893
1447
  end
1448
+ instance = observer.instance
894
1449
 
895
- def test_should_not_be_valid_if_state_is_unknown
896
- @record.state = 'invalid'
897
-
898
- assert !@record.valid?
899
- assert_equal ['State is invalid'], @record.errors.full_messages
900
- end
1450
+ @transition.perform
1451
+ assert_equal [[@record, @transition]], instance.notifications
901
1452
  end
902
1453
 
903
- class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
904
- def setup
905
- @model = new_model do
906
- alias_attribute :status, :state
1454
+ def test_should_call_methods_outside_the_context_of_the_record
1455
+ observer = new_observer(@model) do
1456
+ def before_ignite(*args)
1457
+ notifications << self
907
1458
  end
908
-
909
- @machine = StateMachine::Machine.new(@model, :status, :attribute => :state)
910
- @machine.state :parked
911
-
912
- @record = @model.new
913
1459
  end
1460
+ instance = observer.instance
914
1461
 
915
- def test_should_add_validation_errors_to_custom_attribute
916
- @record.state = 'invalid'
917
-
918
- assert !@record.valid?
919
- assert_equal ['State is invalid'], @record.errors.full_messages
920
-
921
- @record.state = 'parked'
922
- assert @record.valid?
923
- end
1462
+ @transition.perform
1463
+ assert_equal [instance], instance.notifications
924
1464
  end
925
-
926
- class MachineWithStateDrivenValidationsTest < BaseTestCase
927
- def setup
928
- @model = new_model do
929
- attr_accessor :seatbelt
1465
+
1466
+ def test_should_use_original_observer_behavior_to_handle_non_state_machine_callbacks
1467
+ observer = new_observer(@model) do
1468
+ def before_save(object)
930
1469
  end
931
1470
 
932
- @machine = StateMachine::Machine.new(@model)
933
- @machine.state :first_gear, :second_gear do
934
- validates_presence_of :seatbelt
1471
+ def before_ignite(*args)
1472
+ end
1473
+
1474
+ def update_without_multiple_args(observed_method, object)
1475
+ notifications << [observed_method, object] if [:before_save, :before_ignite].include?(observed_method)
1476
+ super
935
1477
  end
936
- @machine.other_states :parked
937
- end
938
-
939
- def test_should_be_valid_if_validation_fails_outside_state_scope
940
- record = @model.new(:state => 'parked', :seatbelt => nil)
941
- assert record.valid?
942
1478
  end
943
1479
 
944
- def test_should_be_invalid_if_validation_fails_within_state_scope
945
- record = @model.new(:state => 'first_gear', :seatbelt => nil)
946
- assert !record.valid?
947
- end
1480
+ instance = observer.instance
948
1481
 
949
- def test_should_be_valid_if_validation_succeeds_within_state_scope
950
- record = @model.new(:state => 'second_gear', :seatbelt => true)
951
- assert record.valid?
952
- end
1482
+ @transition.perform
1483
+ assert_equal [[:before_save, @record]], instance.notifications
1484
+ end
1485
+ end
1486
+
1487
+ class MachineWithNamespacedObserversTest < BaseTestCase
1488
+ def setup
1489
+ @model = new_model
1490
+ @machine = StateMachine::Machine.new(@model, :state, :namespace => 'alarm')
1491
+ @machine.state :active, :off
1492
+ @machine.event :enable
1493
+ @record = @model.new(:state => 'off')
1494
+ @transition = StateMachine::Transition.new(@record, @machine, :enable, :off, :active)
953
1495
  end
954
1496
 
955
- class MachineWithEventAttributesOnValidationTest < BaseTestCase
956
- def setup
957
- @model = new_model
958
- @machine = StateMachine::Machine.new(@model)
959
- @machine.event :ignite do
960
- transition :parked => :idling
1497
+ def test_should_call_namespaced_before_event_method
1498
+ observer = new_observer(@model) do
1499
+ def before_enable_alarm(*args)
1500
+ notifications << args
961
1501
  end
962
-
963
- @record = @model.new
964
- @record.state = 'parked'
965
- @record.state_event = 'ignite'
966
- end
967
-
968
- def test_should_fail_if_event_is_invalid
969
- @record.state_event = 'invalid'
970
- assert !@record.valid?
971
- assert_equal ['State event is invalid'], @record.errors.full_messages
972
1502
  end
1503
+ instance = observer.instance
973
1504
 
974
- def test_should_fail_if_event_has_no_transition
975
- @record.state = 'idling'
976
- assert !@record.valid?
977
- assert_equal ['State event cannot transition when idling'], @record.errors.full_messages
978
- end
979
-
980
- def test_should_be_successful_if_event_has_transition
981
- assert @record.valid?
1505
+ @transition.perform
1506
+ assert_equal [[@record, @transition]], instance.notifications
1507
+ end
1508
+
1509
+ def test_should_call_namespaced_after_event_method
1510
+ observer = new_observer(@model) do
1511
+ def after_enable_alarm(*args)
1512
+ notifications << args
1513
+ end
982
1514
  end
1515
+ instance = observer.instance
983
1516
 
984
- def test_should_run_before_callbacks
985
- ran_callback = false
986
- @machine.before_transition { ran_callback = true }
987
-
988
- @record.valid?
989
- assert ran_callback
990
- end
1517
+ @transition.perform
1518
+ assert_equal [[@record, @transition]], instance.notifications
1519
+ end
1520
+ end
1521
+
1522
+ class MachineWithMixedCallbacksTest < BaseTestCase
1523
+ def setup
1524
+ @model = new_model
1525
+ @machine = StateMachine::Machine.new(@model)
1526
+ @machine.state :parked, :idling
1527
+ @machine.event :ignite
1528
+ @record = @model.new(:state => 'parked')
1529
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
991
1530
 
992
- def test_should_persist_new_state
993
- @record.valid?
994
- assert_equal 'idling', @record.state
995
- end
1531
+ @notifications = []
996
1532
 
997
- def test_should_not_run_after_callbacks
998
- ran_callback = false
999
- @machine.after_transition { ran_callback = true }
1000
-
1001
- @record.valid?
1002
- assert !ran_callback
1533
+ # Create callbacks
1534
+ @machine.before_transition {@notifications << :callback_before_transition}
1535
+ @machine.after_transition {@notifications << :callback_after_transition}
1536
+ @machine.around_transition do |block|
1537
+ @notifications << :callback_around_before_transition
1538
+ block.call
1539
+ @notifications << :callback_arond_after_transition
1003
1540
  end
1004
1541
 
1005
- def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
1006
- @model.class_eval do
1007
- attr_accessor :seatbelt
1008
- validates_presence_of :seatbelt
1542
+ # Create observer callbacks
1543
+ observer = new_observer(@model) do
1544
+ def before_ignite(*args)
1545
+ notifications << :observer_before_ignite
1009
1546
  end
1010
1547
 
1011
- ran_callback = false
1012
- @machine.after_transition { ran_callback = true }
1013
-
1014
- @record.valid?
1015
- assert !ran_callback
1016
- end
1017
-
1018
- def test_should_run_after_callbacks_with_failures_enabled_if_validation_fails
1019
- @model.class_eval do
1020
- attr_accessor :seatbelt
1021
- validates_presence_of :seatbelt
1548
+ def before_transition(*args)
1549
+ notifications << :observer_before_transition
1022
1550
  end
1023
1551
 
1024
- ran_callback = false
1025
- @machine.after_transition(:include_failures => true) { ran_callback = true }
1552
+ def after_ignite(*args)
1553
+ notifications << :observer_after_ignite
1554
+ end
1026
1555
 
1027
- @record.valid?
1028
- assert ran_callback
1556
+ def after_transition(*args)
1557
+ notifications << :observer_after_transition
1558
+ end
1029
1559
  end
1560
+ instance = observer.instance
1561
+ instance.notifications = @notifications
1562
+
1563
+ @transition.perform
1030
1564
  end
1031
1565
 
1032
- class MachineWithEventAttributesOnSaveBangTest < BaseTestCase
1566
+ def test_should_invoke_callbacks_in_specific_order
1567
+ expected = [
1568
+ :callback_before_transition,
1569
+ :callback_around_before_transition,
1570
+ :observer_before_ignite,
1571
+ :observer_before_transition,
1572
+ :callback_arond_after_transition,
1573
+ :callback_after_transition,
1574
+ :observer_after_ignite,
1575
+ :observer_after_transition
1576
+ ]
1577
+
1578
+ assert_equal expected, @notifications
1579
+ end
1580
+ end
1581
+
1582
+ if ActiveRecord.const_defined?(:NamedScope)
1583
+ class MachineWithScopesTest < BaseTestCase
1033
1584
  def setup
1034
1585
  @model = new_model
1035
1586
  @machine = StateMachine::Machine.new(@model)
1036
- @machine.event :ignite do
1037
- transition :parked => :idling
1038
- end
1039
-
1040
- @record = @model.new
1041
- @record.state = 'parked'
1042
- @record.state_event = 'ignite'
1587
+ @machine.state :parked, :first_gear
1588
+ @machine.state :idling, :value => lambda {'idling'}
1043
1589
  end
1044
1590
 
1045
- def test_should_fail_if_event_is_invalid
1046
- @record.state_event = 'invalid'
1047
- assert_raise(ActiveRecord::RecordInvalid) { @record.save! }
1591
+ def test_should_create_singular_with_scope
1592
+ assert @model.respond_to?(:with_state)
1048
1593
  end
1049
1594
 
1050
- def test_should_fail_if_event_has_no_transition
1051
- @record.state = 'idling'
1052
- assert_raise(ActiveRecord::RecordInvalid) { @record.save! }
1595
+ def test_should_only_include_records_with_state_in_singular_with_scope
1596
+ parked = @model.create :state => 'parked'
1597
+ idling = @model.create :state => 'idling'
1598
+
1599
+ assert_equal [parked], @model.with_state(:parked).find(:all)
1053
1600
  end
1054
1601
 
1055
- def test_should_be_successful_if_event_has_transition
1056
- assert_equal true, @record.save!
1602
+ def test_should_create_plural_with_scope
1603
+ assert @model.respond_to?(:with_states)
1057
1604
  end
1058
1605
 
1059
- def test_should_run_before_callbacks
1060
- ran_callback = false
1061
- @machine.before_transition { ran_callback = true }
1606
+ def test_should_only_include_records_with_states_in_plural_with_scope
1607
+ parked = @model.create :state => 'parked'
1608
+ idling = @model.create :state => 'idling'
1062
1609
 
1063
- @record.save!
1064
- assert ran_callback
1610
+ assert_equal [parked, idling], @model.with_states(:parked, :idling).find(:all)
1065
1611
  end
1066
1612
 
1067
- def test_should_run_before_callbacks_once
1068
- before_count = 0
1069
- @machine.before_transition { before_count += 1 }
1070
-
1071
- @record.save!
1072
- assert_equal 1, before_count
1613
+ def test_should_create_singular_without_scope
1614
+ assert @model.respond_to?(:without_state)
1073
1615
  end
1074
1616
 
1075
- def test_should_persist_new_state
1076
- @record.save!
1077
- assert_equal 'idling', @record.state
1617
+ def test_should_only_include_records_without_state_in_singular_without_scope
1618
+ parked = @model.create :state => 'parked'
1619
+ idling = @model.create :state => 'idling'
1620
+
1621
+ assert_equal [parked], @model.without_state(:idling).find(:all)
1078
1622
  end
1079
1623
 
1080
- def test_should_run_after_callbacks
1081
- ran_callback = false
1082
- @machine.after_transition { ran_callback = true }
1083
-
1084
- @record.save!
1085
- assert ran_callback
1624
+ def test_should_create_plural_without_scope
1625
+ assert @model.respond_to?(:without_states)
1086
1626
  end
1087
1627
 
1088
- def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
1089
- @model.before_create {|record| false}
1628
+ def test_should_only_include_records_without_states_in_plural_without_scope
1629
+ parked = @model.create :state => 'parked'
1630
+ idling = @model.create :state => 'idling'
1631
+ first_gear = @model.create :state => 'first_gear'
1090
1632
 
1091
- ran_callback = false
1092
- @machine.after_transition { ran_callback = true }
1093
-
1094
- begin; @record.save!; rescue; end
1095
- assert !ran_callback
1633
+ assert_equal [parked, idling], @model.without_states(:first_gear).find(:all)
1096
1634
  end
1097
1635
 
1098
- def test_should_run_after_callbacks_with_failures_enabled_if_fails
1099
- @model.before_create {|record| false}
1100
-
1101
- ran_callback = false
1102
- @machine.after_transition(:include_failures => true) { ran_callback = true }
1636
+ def test_should_allow_chaining_scopes
1637
+ parked = @model.create :state => 'parked'
1638
+ idling = @model.create :state => 'idling'
1103
1639
 
1104
- begin; @record.save!; rescue; end
1105
- assert ran_callback
1640
+ assert_equal [idling], @model.without_state(:parked).with_state(:idling).find(:all)
1106
1641
  end
1107
1642
  end
1108
1643
 
1109
- class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
1644
+ class MachineWithScopesAndOwnerSubclassTest < BaseTestCase
1110
1645
  def setup
1111
- @superclass = new_model do
1112
- def persist
1113
- create_or_update
1114
- end
1115
- end
1116
- @model = Class.new(@superclass)
1117
- @machine = StateMachine::Machine.new(@model, :action => :persist)
1118
- @machine.event :ignite do
1119
- transition :parked => :idling
1120
- end
1646
+ @model = new_model
1647
+ @machine = StateMachine::Machine.new(@model, :state)
1121
1648
 
1122
- @record = @model.new
1123
- @record.state = 'parked'
1124
- @record.state_event = 'ignite'
1125
- end
1126
-
1127
- def test_should_not_transition_on_valid?
1128
- @record.valid?
1129
- assert_equal 'parked', @record.state
1130
- end
1131
-
1132
- def test_should_not_transition_on_save
1133
- @record.save
1134
- assert_equal 'parked', @record.state
1649
+ @subclass = Class.new(@model)
1650
+ @subclass_machine = @subclass.state_machine(:state) {}
1651
+ @subclass_machine.state :parked, :idling, :first_gear
1135
1652
  end
1136
1653
 
1137
- def test_should_not_transition_on_save!
1138
- @record.save!
1139
- assert_equal 'parked', @record.state
1654
+ def test_should_only_include_records_with_subclass_states_in_with_scope
1655
+ parked = @subclass.create :state => 'parked'
1656
+ idling = @subclass.create :state => 'idling'
1657
+
1658
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling).find(:all)
1140
1659
  end
1141
1660
 
1142
- def test_should_transition_on_custom_action
1143
- @record.persist
1144
- assert_equal 'idling', @record.state
1661
+ def test_should_only_include_records_without_subclass_states_in_without_scope
1662
+ parked = @subclass.create :state => 'parked'
1663
+ idling = @subclass.create :state => 'idling'
1664
+ first_gear = @subclass.create :state => 'first_gear'
1665
+
1666
+ assert_equal [parked, idling], @subclass.without_states(:first_gear).find(:all)
1145
1667
  end
1146
1668
  end
1147
1669
 
1148
- class MachineWithObserversTest < BaseTestCase
1670
+ class MachineWithComplexPluralizationScopesTest < BaseTestCase
1149
1671
  def setup
1150
1672
  @model = new_model
1151
- @machine = StateMachine::Machine.new(@model)
1152
- @machine.state :parked, :idling
1153
- @machine.event :ignite
1154
- @record = @model.new(:state => 'parked')
1155
- @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
1156
- end
1157
-
1158
- def test_should_call_all_transition_callback_permutations
1159
- callbacks = [
1160
- :before_ignite_from_parked_to_idling,
1161
- :before_ignite_from_parked,
1162
- :before_ignite_to_idling,
1163
- :before_ignite,
1164
- :before_transition_state_from_parked_to_idling,
1165
- :before_transition_state_from_parked,
1166
- :before_transition_state_to_idling,
1167
- :before_transition_state,
1168
- :before_transition
1169
- ]
1170
-
1171
- notified = false
1172
- observer = new_observer(@model) do
1173
- callbacks.each do |callback|
1174
- define_method(callback) do |*args|
1175
- notifications << callback
1176
- end
1177
- end
1178
- end
1179
-
1180
- instance = observer.instance
1181
-
1182
- @transition.perform
1183
- assert_equal callbacks, instance.notifications
1673
+ @machine = StateMachine::Machine.new(@model, :status)
1184
1674
  end
1185
1675
 
1186
- def test_should_pass_record_and_transition_to_before_callbacks
1187
- observer = new_observer(@model) do
1188
- def before_transition(*args)
1189
- notifications << args
1190
- end
1191
- end
1192
- instance = observer.instance
1193
-
1194
- @transition.perform
1195
- assert_equal [[@record, @transition]], instance.notifications
1676
+ def test_should_create_singular_with_scope
1677
+ assert @model.respond_to?(:with_status)
1196
1678
  end
1197
1679
 
1198
- def test_should_pass_record_and_transition_to_after_callbacks
1199
- observer = new_observer(@model) do
1200
- def after_transition(*args)
1201
- notifications << args
1202
- end
1203
- end
1204
- instance = observer.instance
1205
-
1206
- @transition.perform
1207
- assert_equal [[@record, @transition]], instance.notifications
1680
+ def test_should_create_plural_with_scope
1681
+ assert @model.respond_to?(:with_statuses)
1208
1682
  end
1209
-
1210
- def test_should_call_methods_outside_the_context_of_the_record
1211
- observer = new_observer(@model) do
1212
- def before_ignite(*args)
1213
- notifications << self
1214
- end
1215
- end
1216
- instance = observer.instance
1683
+ end
1684
+
1685
+ class MachineWithScopesAndJoinsTest < BaseTestCase
1686
+ def setup
1687
+ @company = new_model(:company)
1688
+ ActiveRecordTest.const_set('Company', @company)
1217
1689
 
1218
- @transition.perform
1219
- assert_equal [instance], instance.notifications
1220
- end
1221
-
1222
- def test_should_use_original_observer_behavior_to_handle_non_state_machine_callbacks
1223
- observer = new_observer(@model) do
1224
- def before_save(object)
1225
- end
1226
-
1227
- def before_ignite(*args)
1228
- end
1229
-
1230
- def update_without_multiple_args(observed_method, object)
1231
- notifications << [observed_method, object] if [:before_save, :before_ignite].include?(observed_method)
1232
- super
1233
- end
1690
+ @vehicle = new_model(:vehicle) do
1691
+ connection.add_column :vehicle, :company_id, :integer
1692
+ belongs_to :company, :class_name => 'ActiveRecordTest::Company'
1234
1693
  end
1694
+ ActiveRecordTest.const_set('Vehicle', @vehicle)
1235
1695
 
1236
- instance = observer.instance
1696
+ @company_machine = StateMachine::Machine.new(@company, :initial => :active)
1697
+ @vehicle_machine = StateMachine::Machine.new(@vehicle, :initial => :parked)
1698
+ @vehicle_machine.state :idling
1237
1699
 
1238
- @transition.perform
1239
- assert_equal [[:before_save, @record]], instance.notifications
1700
+ @ford = @company.create
1701
+ @mustang = @vehicle.create(:company => @ford)
1240
1702
  end
1241
- end
1242
-
1243
- class MachineWithNamespacedObserversTest < BaseTestCase
1244
- def setup
1245
- @model = new_model
1246
- @machine = StateMachine::Machine.new(@model, :state, :namespace => 'alarm')
1247
- @machine.state :active, :off
1248
- @machine.event :enable
1249
- @record = @model.new(:state => 'off')
1250
- @transition = StateMachine::Transition.new(@record, @machine, :enable, :off, :active)
1703
+
1704
+ def test_should_find_records_in_with_scope
1705
+ assert_equal [@mustang], @vehicle.with_states(:parked).find(:all, :include => :company, :conditions => 'company.state = "active"')
1251
1706
  end
1252
1707
 
1253
- def test_should_call_namespaced_before_event_method
1254
- observer = new_observer(@model) do
1255
- def before_enable_alarm(*args)
1256
- notifications << args
1257
- end
1258
- end
1259
- instance = observer.instance
1260
-
1261
- @transition.perform
1262
- assert_equal [[@record, @transition]], instance.notifications
1708
+ def test_should_find_records_in_without_scope
1709
+ assert_equal [@mustang], @vehicle.without_states(:idling).find(:all, :include => :company, :conditions => 'company.state = "active"')
1263
1710
  end
1264
1711
 
1265
- def test_should_call_namespaced_after_event_method
1266
- observer = new_observer(@model) do
1267
- def after_enable_alarm(*args)
1268
- notifications << args
1269
- end
1712
+ def teardown
1713
+ ActiveRecordTest.class_eval do
1714
+ remove_const('Vehicle')
1715
+ remove_const('Company')
1270
1716
  end
1271
- instance = observer.instance
1272
-
1273
- @transition.perform
1274
- assert_equal [[@record, @transition]], instance.notifications
1275
1717
  end
1276
1718
  end
1277
-
1278
- class MachineWithMixedCallbacksTest < BaseTestCase
1719
+ else
1720
+ $stderr.puts 'Skipping ActiveRecord Scope tests. `gem install active_record` >= v2.1.0 and try again.'
1721
+ end
1722
+
1723
+ if Object.const_defined?(:I18n)
1724
+ class MachineWithInternationalizationTest < BaseTestCase
1279
1725
  def setup
1280
- @model = new_model
1281
- @machine = StateMachine::Machine.new(@model)
1282
- @machine.state :parked, :idling
1283
- @machine.event :ignite
1284
- @record = @model.new(:state => 'parked')
1285
- @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
1286
-
1287
- @notifications = []
1288
-
1289
- # Create callbacks
1290
- @machine.before_transition(lambda {@notifications << :callback_before_transition})
1291
- @machine.after_transition(lambda {@notifications << :callback_after_transition})
1292
-
1293
- # Create observer callbacks
1294
- observer = new_observer(@model) do
1295
- def before_ignite(*args)
1296
- notifications << :observer_before_ignite
1297
- end
1298
-
1299
- def before_transition(*args)
1300
- notifications << :observer_before_transition
1301
- end
1302
-
1303
- def after_ignite(*args)
1304
- notifications << :observer_after_ignite
1305
- end
1306
-
1307
- def after_transition(*args)
1308
- notifications << :observer_after_transition
1309
- end
1310
- end
1311
- instance = observer.instance
1312
- instance.notifications = @notifications
1726
+ I18n.backend = I18n::Backend::Simple.new
1313
1727
 
1314
- @transition.perform
1315
- end
1316
-
1317
- def test_should_invoke_callbacks_in_specific_order
1318
- expected = [
1319
- :callback_before_transition,
1320
- :observer_before_ignite,
1321
- :observer_before_transition,
1322
- :callback_after_transition,
1323
- :observer_after_ignite,
1324
- :observer_after_transition
1325
- ]
1728
+ # Initialize the backend
1729
+ I18n.backend.translate(:en, 'activerecord.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling')
1326
1730
 
1327
- assert_equal expected, @notifications
1731
+ @model = new_model
1328
1732
  end
1329
- end
1330
-
1331
- if ActiveRecord.const_defined?(:NamedScope)
1332
- class MachineWithScopesTest < BaseTestCase
1333
- def setup
1334
- @model = new_model
1335
- @machine = StateMachine::Machine.new(@model)
1336
- @machine.state :parked, :first_gear
1337
- @machine.state :idling, :value => lambda {'idling'}
1338
- end
1339
-
1340
- def test_should_create_singular_with_scope
1341
- assert @model.respond_to?(:with_state)
1342
- end
1343
-
1344
- def test_should_only_include_records_with_state_in_singular_with_scope
1345
- parked = @model.create :state => 'parked'
1346
- idling = @model.create :state => 'idling'
1347
-
1348
- assert_equal [parked], @model.with_state(:parked).find(:all)
1349
- end
1350
-
1351
- def test_should_create_plural_with_scope
1352
- assert @model.respond_to?(:with_states)
1353
- end
1733
+
1734
+ def test_should_use_defaults
1735
+ I18n.backend.store_translations(:en, {
1736
+ :activerecord => {:errors => {:messages => {:invalid_transition => 'cannot {{event}}'}}}
1737
+ })
1354
1738
 
1355
- def test_should_only_include_records_with_states_in_plural_with_scope
1356
- parked = @model.create :state => 'parked'
1357
- idling = @model.create :state => 'idling'
1358
-
1359
- assert_equal [parked, idling], @model.with_states(:parked, :idling).find(:all)
1360
- end
1739
+ machine = StateMachine::Machine.new(@model)
1740
+ machine.state :parked, :idling
1741
+ machine.event :ignite
1361
1742
 
1362
- def test_should_create_singular_without_scope
1363
- assert @model.respond_to?(:without_state)
1364
- end
1743
+ record = @model.new(:state => 'idling')
1365
1744
 
1366
- def test_should_only_include_records_without_state_in_singular_without_scope
1367
- parked = @model.create :state => 'parked'
1368
- idling = @model.create :state => 'idling'
1369
-
1370
- assert_equal [parked], @model.without_state(:idling).find(:all)
1371
- end
1745
+ machine.invalidate(record, :state, :invalid_transition, [[:event, :ignite]])
1746
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1747
+ end
1748
+
1749
+ def test_should_allow_customized_error_key
1750
+ I18n.backend.store_translations(:en, {
1751
+ :activerecord => {:errors => {:messages => {:bad_transition => 'cannot {{event}}'}}}
1752
+ })
1372
1753
 
1373
- def test_should_create_plural_without_scope
1374
- assert @model.respond_to?(:without_states)
1375
- end
1754
+ machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => :bad_transition})
1755
+ machine.state :parked, :idling
1376
1756
 
1377
- def test_should_only_include_records_without_states_in_plural_without_scope
1378
- parked = @model.create :state => 'parked'
1379
- idling = @model.create :state => 'idling'
1380
- first_gear = @model.create :state => 'first_gear'
1381
-
1382
- assert_equal [parked, idling], @model.without_states(:first_gear).find(:all)
1383
- end
1757
+ record = @model.new(:state => 'idling')
1384
1758
 
1385
- def test_should_allow_chaining_scopes
1386
- parked = @model.create :state => 'parked'
1387
- idling = @model.create :state => 'idling'
1388
-
1389
- assert_equal [idling], @model.without_state(:parked).with_state(:idling).find(:all)
1390
- end
1759
+ machine.invalidate(record, :state, :invalid_transition, [[:event, :ignite]])
1760
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1391
1761
  end
1392
1762
 
1393
- class MachineWithScopesAndOwnerSubclassTest < BaseTestCase
1394
- def setup
1395
- @model = new_model
1396
- @machine = StateMachine::Machine.new(@model, :state)
1397
-
1398
- @subclass = Class.new(@model)
1399
- @subclass_machine = @subclass.state_machine(:state) {}
1400
- @subclass_machine.state :parked, :idling, :first_gear
1401
- end
1763
+ def test_should_allow_customized_error_string
1764
+ machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => 'cannot {{event}}'})
1765
+ machine.state :parked, :idling
1402
1766
 
1403
- def test_should_only_include_records_with_subclass_states_in_with_scope
1404
- parked = @subclass.create :state => 'parked'
1405
- idling = @subclass.create :state => 'idling'
1406
-
1407
- assert_equal [parked, idling], @subclass.with_states(:parked, :idling).find(:all)
1408
- end
1767
+ record = @model.new(:state => 'idling')
1409
1768
 
1410
- def test_should_only_include_records_without_subclass_states_in_without_scope
1411
- parked = @subclass.create :state => 'parked'
1412
- idling = @subclass.create :state => 'idling'
1413
- first_gear = @subclass.create :state => 'first_gear'
1414
-
1415
- assert_equal [parked, idling], @subclass.without_states(:first_gear).find(:all)
1416
- end
1769
+ machine.invalidate(record, :state, :invalid_transition, [[:event, :ignite]])
1770
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1417
1771
  end
1418
1772
 
1419
- class MachineWithComplexPluralizationScopesTest < BaseTestCase
1420
- def setup
1421
- @model = new_model
1422
- @machine = StateMachine::Machine.new(@model, :status)
1423
- end
1773
+ def test_should_allow_customized_state_key_scoped_to_class_and_machine
1774
+ I18n.backend.store_translations(:en, {
1775
+ :activerecord => {:state_machines => {:'active_record_test/foo' => {:state => {:states => {:parked => 'shutdown'}}}}}
1776
+ })
1424
1777
 
1425
- def test_should_create_singular_with_scope
1426
- assert @model.respond_to?(:with_status)
1427
- end
1778
+ machine = StateMachine::Machine.new(@model, :initial => :parked)
1779
+ record = @model.new
1428
1780
 
1429
- def test_should_create_plural_with_scope
1430
- assert @model.respond_to?(:with_statuses)
1431
- end
1781
+ machine.invalidate(record, :event, :invalid_event, [[:state, :parked]])
1782
+ assert_equal ['State event cannot transition when shutdown'], record.errors.full_messages
1432
1783
  end
1433
1784
 
1434
- class MachineWithScopesAndJoinsTest < BaseTestCase
1435
- def setup
1436
- @company = new_model(:company)
1437
- ActiveRecordTest.const_set('Company', @company)
1438
-
1439
- @vehicle = new_model(:vehicle) do
1440
- connection.add_column :vehicle, :company_id, :integer
1441
- belongs_to :company, :class_name => 'ActiveRecordTest::Company'
1442
- end
1443
- ActiveRecordTest.const_set('Vehicle', @vehicle)
1444
-
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).find(:all, :include => :company, :conditions => 'company.state = "active"')
1455
- end
1785
+ def test_should_allow_customized_state_key_scoped_to_machine
1786
+ I18n.backend.store_translations(:en, {
1787
+ :activerecord => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}}
1788
+ })
1456
1789
 
1457
- def test_should_find_records_in_without_scope
1458
- assert_equal [@mustang], @vehicle.without_states(:idling).find(:all, :include => :company, :conditions => 'company.state = "active"')
1459
- end
1790
+ machine = StateMachine::Machine.new(@model, :initial => :parked)
1791
+ record = @model.new
1460
1792
 
1461
- def teardown
1462
- ActiveRecordTest.class_eval do
1463
- remove_const('Vehicle')
1464
- remove_const('Company')
1465
- end
1466
- end
1793
+ machine.invalidate(record, :event, :invalid_event, [[:state, :parked]])
1794
+ assert_equal ['State event cannot transition when shutdown'], record.errors.full_messages
1467
1795
  end
1468
- end
1469
-
1470
- if Object.const_defined?(:I18n)
1471
- class MachineWithInternationalizationTest < BaseTestCase
1472
- def setup
1473
- I18n.backend = I18n::Backend::Simple.new
1474
-
1475
- # Initialize the backend
1476
- I18n.backend.translate(:en, 'activerecord.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling')
1477
-
1478
- @model = new_model
1479
- end
1796
+
1797
+ def test_should_allow_customized_state_key_unscoped
1798
+ I18n.backend.store_translations(:en, {
1799
+ :activerecord => {:state_machines => {:states => {:parked => 'shutdown'}}}
1800
+ })
1480
1801
 
1481
- def test_should_use_defaults
1482
- I18n.backend.store_translations(:en, {
1483
- :activerecord => {:errors => {:messages => {:invalid_transition => 'cannot {{event}}'}}}
1484
- })
1485
-
1486
- machine = StateMachine::Machine.new(@model)
1487
- machine.state :parked, :idling
1488
- machine.event :ignite
1489
-
1490
- record = @model.new(:state => 'idling')
1491
-
1492
- machine.invalidate(record, :state, :invalid_transition, [[:event, :ignite]])
1493
- assert_equal ['State cannot ignite'], record.errors.full_messages
1494
- end
1802
+ machine = StateMachine::Machine.new(@model, :initial => :parked)
1803
+ record = @model.new
1495
1804
 
1496
- def test_should_allow_customized_error_key
1497
- I18n.backend.store_translations(:en, {
1498
- :activerecord => {:errors => {:messages => {:bad_transition => 'cannot {{event}}'}}}
1499
- })
1500
-
1501
- machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => :bad_transition})
1502
- machine.state :parked, :idling
1503
-
1504
- record = @model.new(:state => 'idling')
1505
-
1506
- machine.invalidate(record, :state, :invalid_transition, [[:event, :ignite]])
1507
- assert_equal ['State cannot ignite'], record.errors.full_messages
1508
- end
1805
+ machine.invalidate(record, :event, :invalid_event, [[:state, :parked]])
1806
+ assert_equal ['State event cannot transition when shutdown'], record.errors.full_messages
1807
+ end
1808
+
1809
+ def test_should_allow_customized_event_key_scoped_to_class_and_machine
1810
+ I18n.backend.store_translations(:en, {
1811
+ :activerecord => {:state_machines => {:'active_record_test/foo' => {:state => {:events => {:park => 'stop'}}}}}
1812
+ })
1509
1813
 
1510
- def test_should_allow_customized_error_string
1511
- machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => 'cannot {{event}}'})
1512
- machine.state :parked, :idling
1513
-
1514
- record = @model.new(:state => 'idling')
1515
-
1516
- machine.invalidate(record, :state, :invalid_transition, [[:event, :ignite]])
1517
- assert_equal ['State cannot ignite'], record.errors.full_messages
1518
- end
1814
+ machine = StateMachine::Machine.new(@model)
1815
+ machine.event :park
1816
+ record = @model.new
1519
1817
 
1520
- def test_should_allow_customized_state_key_scoped_to_class_and_machine
1521
- I18n.backend.store_translations(:en, {
1522
- :activerecord => {:state_machines => {:'active_record_test/foo' => {:state => {:states => {:parked => 'shutdown'}}}}}
1523
- })
1524
-
1525
- machine = StateMachine::Machine.new(@model, :initial => :parked)
1526
- record = @model.new
1527
-
1528
- machine.invalidate(record, :event, :invalid_event, [[:state, :parked]])
1529
- assert_equal ['State event cannot transition when shutdown'], record.errors.full_messages
1530
- end
1818
+ machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
1819
+ assert_equal ['State cannot transition via "stop"'], record.errors.full_messages
1820
+ end
1821
+
1822
+ def test_should_allow_customized_event_key_scoped_to_machine
1823
+ I18n.backend.store_translations(:en, {
1824
+ :activerecord => {:state_machines => {:state => {:events => {:park => 'stop'}}}}
1825
+ })
1531
1826
 
1532
- def test_should_allow_customized_state_key_scoped_to_machine
1533
- I18n.backend.store_translations(:en, {
1534
- :activerecord => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}}
1535
- })
1536
-
1537
- machine = StateMachine::Machine.new(@model, :initial => :parked)
1538
- record = @model.new
1539
-
1540
- machine.invalidate(record, :event, :invalid_event, [[:state, :parked]])
1541
- assert_equal ['State event cannot transition when shutdown'], record.errors.full_messages
1542
- end
1827
+ machine = StateMachine::Machine.new(@model)
1828
+ machine.event :park
1829
+ record = @model.new
1543
1830
 
1544
- def test_should_allow_customized_state_key_unscoped
1545
- I18n.backend.store_translations(:en, {
1546
- :activerecord => {:state_machines => {:states => {:parked => 'shutdown'}}}
1547
- })
1548
-
1549
- machine = StateMachine::Machine.new(@model, :initial => :parked)
1550
- record = @model.new
1551
-
1552
- machine.invalidate(record, :event, :invalid_event, [[:state, :parked]])
1553
- assert_equal ['State event cannot transition when shutdown'], record.errors.full_messages
1554
- end
1831
+ machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
1832
+ assert_equal ['State cannot transition via "stop"'], record.errors.full_messages
1833
+ end
1834
+
1835
+ def test_should_allow_customized_event_key_unscoped
1836
+ I18n.backend.store_translations(:en, {
1837
+ :activerecord => {:state_machines => {:events => {:park => 'stop'}}}
1838
+ })
1555
1839
 
1556
- def test_should_allow_customized_event_key_scoped_to_class_and_machine
1557
- I18n.backend.store_translations(:en, {
1558
- :activerecord => {:state_machines => {:'active_record_test/foo' => {:state => {:events => {:park => 'stop'}}}}}
1559
- })
1560
-
1561
- machine = StateMachine::Machine.new(@model)
1562
- machine.event :park
1563
- record = @model.new
1564
-
1565
- machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
1566
- assert_equal ['State cannot transition via "stop"'], record.errors.full_messages
1567
- end
1840
+ machine = StateMachine::Machine.new(@model)
1841
+ machine.event :park
1842
+ record = @model.new
1568
1843
 
1569
- def test_should_allow_customized_event_key_scoped_to_machine
1570
- I18n.backend.store_translations(:en, {
1571
- :activerecord => {:state_machines => {:state => {:events => {:park => 'stop'}}}}
1572
- })
1573
-
1574
- machine = StateMachine::Machine.new(@model)
1575
- machine.event :park
1576
- record = @model.new
1577
-
1578
- machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
1579
- assert_equal ['State cannot transition via "stop"'], record.errors.full_messages
1580
- end
1844
+ machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
1845
+ assert_equal ['State cannot transition via "stop"'], record.errors.full_messages
1846
+ end
1847
+
1848
+ def test_should_only_add_locale_once_in_load_path
1849
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{state_machine/integrations/active_record/locale\.rb$}}.length
1581
1850
 
1582
- def test_should_allow_customized_event_key_unscoped
1583
- I18n.backend.store_translations(:en, {
1584
- :activerecord => {:state_machines => {:events => {:park => 'stop'}}}
1585
- })
1586
-
1587
- machine = StateMachine::Machine.new(@model)
1588
- machine.event :park
1589
- record = @model.new
1590
-
1591
- machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
1592
- assert_equal ['State cannot transition via "stop"'], record.errors.full_messages
1593
- end
1851
+ # Create another ActiveRecord model that will triger the i18n feature
1852
+ new_model
1594
1853
 
1595
- def test_should_only_add_locale_once_in_load_path
1596
- assert_equal 1, I18n.load_path.select {|path| path =~ %r{state_machine/integrations/active_record/locale\.rb$}}.length
1597
-
1598
- # Create another ActiveRecord model that will triger the i18n feature
1599
- new_model
1600
-
1601
- assert_equal 1, I18n.load_path.select {|path| path =~ %r{state_machine/integrations/active_record/locale\.rb$}}.length
1602
- end
1854
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{state_machine/integrations/active_record/locale\.rb$}}.length
1603
1855
  end
1604
- else
1605
- $stderr.puts 'Skipping ActiveRecord I18n tests. `gem install active_record` >= v2.2.0 and try again.'
1606
1856
  end
1857
+ else
1858
+ $stderr.puts 'Skipping ActiveRecord I18n tests. `gem install active_record` >= v2.2.0 and try again.'
1607
1859
  end
1608
- rescue LoadError
1609
- $stderr.puts "Skipping ActiveRecord tests. `gem install activerecord#{" -v #{ENV['AR_VERSION']}" if ENV['AR_VERSION']}` and try again."
1610
1860
  end