state_machine 0.8.1 → 0.9.0

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