enum_state_machine 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -12
  3. data/.ruby-version +1 -1
  4. data/.ruby-version.orig +5 -0
  5. data/Gemfile +0 -1
  6. data/Rakefile +0 -18
  7. data/enum_state_machine.gemspec +35 -0
  8. data/enum_state_machine.gemspec.orig +43 -0
  9. data/lib/enum_state_machine/assertions.rb +36 -0
  10. data/lib/enum_state_machine/branch.rb +225 -0
  11. data/lib/enum_state_machine/callback.rb +232 -0
  12. data/lib/enum_state_machine/core.rb +12 -0
  13. data/lib/enum_state_machine/core_ext/class/state_machine.rb +5 -0
  14. data/lib/enum_state_machine/core_ext.rb +2 -0
  15. data/lib/enum_state_machine/error.rb +13 -0
  16. data/lib/enum_state_machine/eval_helpers.rb +87 -0
  17. data/lib/enum_state_machine/event.rb +257 -0
  18. data/lib/enum_state_machine/event_collection.rb +141 -0
  19. data/lib/enum_state_machine/extensions.rb +149 -0
  20. data/lib/enum_state_machine/graph.rb +93 -0
  21. data/lib/enum_state_machine/helper_module.rb +17 -0
  22. data/lib/enum_state_machine/initializers/rails.rb +22 -0
  23. data/lib/enum_state_machine/initializers.rb +4 -0
  24. data/lib/enum_state_machine/integrations/active_model/locale.rb +11 -0
  25. data/lib/enum_state_machine/integrations/active_model/observer.rb +33 -0
  26. data/lib/enum_state_machine/integrations/active_model/observer_update.rb +42 -0
  27. data/lib/enum_state_machine/integrations/active_model/versions.rb +31 -0
  28. data/lib/enum_state_machine/integrations/active_model.rb +585 -0
  29. data/lib/enum_state_machine/integrations/active_record/locale.rb +20 -0
  30. data/lib/enum_state_machine/integrations/active_record/versions.rb +123 -0
  31. data/lib/enum_state_machine/integrations/active_record.rb +548 -0
  32. data/lib/enum_state_machine/integrations/base.rb +100 -0
  33. data/lib/enum_state_machine/integrations.rb +97 -0
  34. data/lib/enum_state_machine/machine.rb +2292 -0
  35. data/lib/enum_state_machine/machine_collection.rb +86 -0
  36. data/lib/enum_state_machine/macro_methods.rb +518 -0
  37. data/lib/enum_state_machine/matcher.rb +123 -0
  38. data/lib/enum_state_machine/matcher_helpers.rb +54 -0
  39. data/lib/enum_state_machine/node_collection.rb +222 -0
  40. data/lib/enum_state_machine/path.rb +120 -0
  41. data/lib/enum_state_machine/path_collection.rb +90 -0
  42. data/lib/enum_state_machine/state.rb +297 -0
  43. data/lib/enum_state_machine/state_collection.rb +112 -0
  44. data/lib/enum_state_machine/state_context.rb +138 -0
  45. data/lib/enum_state_machine/state_enum.rb +23 -0
  46. data/lib/enum_state_machine/transition.rb +470 -0
  47. data/lib/enum_state_machine/transition_collection.rb +245 -0
  48. data/lib/enum_state_machine/version.rb +3 -0
  49. data/lib/enum_state_machine/yard/handlers/base.rb +32 -0
  50. data/lib/enum_state_machine/yard/handlers/event.rb +25 -0
  51. data/lib/enum_state_machine/yard/handlers/machine.rb +344 -0
  52. data/lib/enum_state_machine/yard/handlers/state.rb +25 -0
  53. data/lib/enum_state_machine/yard/handlers/transition.rb +47 -0
  54. data/lib/enum_state_machine/yard/handlers.rb +12 -0
  55. data/lib/enum_state_machine/yard/templates/default/class/html/setup.rb +30 -0
  56. data/lib/enum_state_machine/yard/templates/default/class/html/state_machines.erb +12 -0
  57. data/lib/enum_state_machine/yard/templates.rb +3 -0
  58. data/lib/enum_state_machine/yard.rb +8 -0
  59. data/lib/enum_state_machine.rb +9 -0
  60. data/lib/tasks/enum_state_machine.rake +1 -0
  61. data/lib/tasks/enum_state_machine.rb +24 -0
  62. data/lib/yard-enum_state_machine.rb +2 -0
  63. data/test/functional/state_machine_test.rb +1066 -0
  64. data/test/unit/graph_test.rb +9 -5
  65. data/test/unit/integrations/active_model_test.rb +1245 -0
  66. data/test/unit/integrations/active_record_test.rb +2551 -0
  67. data/test/unit/integrations/base_test.rb +104 -0
  68. data/test/unit/integrations_test.rb +71 -0
  69. data/test/unit/invalid_event_test.rb +20 -0
  70. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  71. data/test/unit/invalid_transition_test.rb +115 -0
  72. data/test/unit/machine_collection_test.rb +603 -0
  73. data/test/unit/machine_test.rb +3395 -0
  74. data/test/unit/state_machine_test.rb +31 -0
  75. metadata +212 -44
  76. data/Appraisals +0 -28
  77. data/gemfiles/active_model_4.0.4.gemfile +0 -9
  78. data/gemfiles/active_model_4.0.4.gemfile.lock +0 -51
  79. data/gemfiles/active_record_4.0.4.gemfile +0 -11
  80. data/gemfiles/active_record_4.0.4.gemfile.lock +0 -61
  81. data/gemfiles/default.gemfile +0 -7
  82. data/gemfiles/default.gemfile.lock +0 -27
  83. data/gemfiles/graphviz_1.0.9.gemfile +0 -7
  84. data/gemfiles/graphviz_1.0.9.gemfile.lock +0 -30
@@ -0,0 +1,2551 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
2
+
3
+ require 'active_record'
4
+
5
+ FIXTURES_ROOT = File.dirname(__FILE__) + '/../../fixtures/'
6
+
7
+ # Load TestCase helpers
8
+ require 'active_support/test_case'
9
+ require 'active_record/fixtures'
10
+
11
+ begin
12
+ require 'active_record/test_case'
13
+ rescue LoadError
14
+ class ActiveRecord::TestCase < ActiveSupport::TestCase
15
+ self.fixture_path = FIXTURES_ROOT
16
+ self.use_instantiated_fixtures = false
17
+ self.use_transactional_fixtures = true
18
+ end
19
+ end
20
+
21
+ require 'active_record/version'
22
+ if ActiveRecord::VERSION::MAJOR >= 4
23
+ require 'rails/observers/activerecord/active_record'
24
+ require 'active_record/mass_assignment_security'
25
+ end
26
+
27
+ # Establish database connection
28
+ ActiveRecord::Base.establish_connection('adapter' => RUBY_PLATFORM == 'java' ? 'jdbcsqlite3' : '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
34
+ end
35
+
36
+ protected
37
+ # Creates a new ActiveRecord model (and the associated table)
38
+ def new_model(create_table = :foo, &block)
39
+ name = create_table || :foo
40
+ table_name = "#{name}_#{rand(1000000)}"
41
+
42
+ model = Class.new(ActiveRecord::Base) do
43
+ self.table_name = table_name.to_s
44
+ connection.create_table(table_name, :force => true) {|t| t.string(:state)} if create_table
45
+
46
+ (class << self; self; end).class_eval do
47
+ define_method(:name) { "ActiveRecordTest::#{name.to_s.capitalize}" }
48
+ end
49
+ end
50
+ model.class_eval(&block) if block_given?
51
+ model.reset_column_information if create_table
52
+ model
53
+ end
54
+
55
+ # Creates a new ActiveRecord observer
56
+ def new_observer(model, &block)
57
+ observer = Class.new(ActiveRecord::Observer) do
58
+ attr_accessor :notifications
59
+
60
+ def initialize
61
+ super
62
+ @notifications = []
63
+ end
64
+ end
65
+
66
+ (class << observer; self; end).class_eval do
67
+ define_method(:name) do
68
+ "#{model.name}Observer"
69
+ end
70
+ end
71
+
72
+ observer.observe(model)
73
+ observer.class_eval(&block) if block_given?
74
+ observer
75
+ end
76
+ end
77
+
78
+ class IntegrationTest < BaseTestCase
79
+ def test_should_have_an_integration_name
80
+ assert_equal :active_record, EnumStateMachine::Integrations::ActiveRecord.integration_name
81
+ end
82
+
83
+ def test_should_be_available
84
+ assert EnumStateMachine::Integrations::ActiveRecord.available?
85
+ end
86
+
87
+ def test_should_match_if_class_inherits_from_active_record
88
+ assert EnumStateMachine::Integrations::ActiveRecord.matches?(new_model)
89
+ end
90
+
91
+ def test_should_not_match_if_class_does_not_inherit_from_active_record
92
+ assert !EnumStateMachine::Integrations::ActiveRecord.matches?(Class.new)
93
+ end
94
+
95
+ def test_should_have_defaults
96
+ assert_equal({:action => :save}, EnumStateMachine::Integrations::ActiveRecord.defaults)
97
+ end
98
+
99
+ def test_should_have_a_locale_path
100
+ assert_not_nil EnumStateMachine::Integrations::ActiveRecord.locale_path
101
+ end
102
+ end
103
+
104
+ class MachineWithoutDatabaseTest < BaseTestCase
105
+ def setup
106
+ @model = new_model(false) do
107
+ # Simulate the database not being available entirely
108
+ def self.connection
109
+ raise ActiveRecord::ConnectionNotEstablished
110
+ end
111
+
112
+ def self.connected?
113
+ false
114
+ end
115
+ end
116
+ end
117
+
118
+ def test_should_allow_machine_creation
119
+ assert_nothing_raised { EnumStateMachine::Machine.new(@model) }
120
+ end
121
+ end
122
+
123
+ class MachineUnmigratedTest < BaseTestCase
124
+ def setup
125
+ @model = new_model(false)
126
+
127
+ # Drop the table so that it definitely doesn't exist
128
+ @model.connection.drop_table(@model.table_name) if @model.table_exists?
129
+ end
130
+
131
+ def test_should_allow_machine_creation
132
+ assert_nothing_raised { EnumStateMachine::Machine.new(@model) }
133
+ end
134
+ end
135
+
136
+ class MachineByDefaultTest < BaseTestCase
137
+ def setup
138
+ @model = new_model
139
+ @machine = EnumStateMachine::Machine.new(@model)
140
+ end
141
+
142
+ def test_should_use_save_as_action
143
+ assert_equal :save, @machine.action
144
+ end
145
+
146
+ def test_should_use_transactions
147
+ assert_equal true, @machine.use_transactions
148
+ end
149
+
150
+ def test_should_create_notifier_before_callback
151
+ assert_equal 1, @machine.callbacks[:before].size
152
+ end
153
+
154
+ def test_should_create_notifier_after_callback
155
+ assert_equal 1, @machine.callbacks[:after].size
156
+ end
157
+ end
158
+
159
+ class MachineWithStatesTest < BaseTestCase
160
+ def setup
161
+ @model = new_model
162
+ @machine = EnumStateMachine::Machine.new(@model)
163
+ @machine.state :first_gear
164
+ end
165
+
166
+ def test_should_humanize_name
167
+ assert_equal 'first gear', @machine.state(:first_gear).human_name
168
+ end
169
+ end
170
+
171
+ class MachineWithStaticInitialStateTest < BaseTestCase
172
+ def setup
173
+ @model = new_model(:vehicle) do
174
+ attr_accessor :value
175
+ end
176
+ @machine = EnumStateMachine::Machine.new(@model, :initial => :parked)
177
+ end
178
+
179
+ def test_should_set_initial_state_on_created_object
180
+ record = @model.new
181
+ assert_equal 'parked', record.state
182
+ end
183
+
184
+ def test_should_set_initial_state_with_nil_attributes
185
+ record = @model.new(nil)
186
+ assert_equal 'parked', record.state
187
+ end
188
+
189
+ def test_should_still_set_attributes
190
+ record = @model.new(:value => 1)
191
+ assert_equal 1, record.value
192
+ end
193
+
194
+ def test_should_still_allow_initialize_blocks
195
+ block_args = nil
196
+ record = @model.new do |*args|
197
+ block_args = args
198
+ end
199
+
200
+ assert_equal [record], block_args
201
+ end
202
+
203
+ def test_should_set_attributes_prior_to_initialize_block
204
+ state = nil
205
+ @model.new do |record|
206
+ state = record.state
207
+ end
208
+
209
+ assert_equal 'parked', state
210
+ end
211
+
212
+ def test_should_set_attributes_prior_to_after_initialize_hook
213
+ state = nil
214
+ @model.class_eval {define_method(:after_initialize) {}} if ActiveRecord::VERSION::MAJOR <= 2
215
+ @model.after_initialize do |record|
216
+ state = record.state
217
+ end
218
+ @model.new
219
+ assert_equal 'parked', state
220
+ end
221
+
222
+ def test_should_set_initial_state_before_setting_attributes
223
+ @model.class_eval do
224
+ attr_accessor :state_during_setter
225
+
226
+ remove_method :value=
227
+ define_method(:value=) do |value|
228
+ self.state_during_setter = state
229
+ end
230
+ end
231
+
232
+ record = @model.new(:value => 1)
233
+ assert_equal 'parked', record.state_during_setter
234
+ end
235
+
236
+ def test_should_not_set_initial_state_after_already_initialized
237
+ record = @model.new(:value => 1)
238
+ assert_equal 'parked', record.state
239
+
240
+ record.state = 'idling'
241
+ record.attributes = {}
242
+ assert_equal 'idling', record.state
243
+ end
244
+
245
+ def test_should_persist_initial_state
246
+ record = @model.new
247
+ record.save
248
+ record.reload
249
+ assert_equal 'parked', record.state
250
+ end
251
+
252
+ unless ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0
253
+ def test_should_persist_initial_state_on_dup
254
+ record = @model.create.dup
255
+ record.save
256
+ record.reload
257
+ assert_equal 'parked', record.state
258
+ end
259
+ end
260
+
261
+ def test_should_use_stored_values_when_loading_from_database
262
+ @machine.state :idling
263
+
264
+ record = @model.find(@model.create(:state => 'idling').id)
265
+ assert_equal 'idling', record.state
266
+ end
267
+
268
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
269
+ @machine.state nil
270
+
271
+ record = @model.find(@model.create(:state => nil).id)
272
+ assert_nil record.state
273
+ end
274
+
275
+ def test_should_use_stored_values_when_loading_for_many_association
276
+ @machine.state :idling
277
+
278
+ @model.connection.add_column @model.table_name, :owner_id, :integer
279
+ @model.reset_column_information
280
+ ActiveRecordTest.const_set('Vehicle', @model)
281
+
282
+ owner_model = new_model(:owner) do
283
+ has_many :vehicles, :class_name => 'ActiveRecordTest::Vehicle'
284
+ end
285
+ ActiveRecordTest.const_set('Owner', owner_model)
286
+
287
+ owner = owner_model.create
288
+ record = @model.create(:state => 'idling', :owner_id => owner.id)
289
+ assert_equal 'idling', owner.vehicles[0].state
290
+ end
291
+
292
+ def test_should_use_stored_values_when_loading_for_one_association
293
+ @machine.state :idling
294
+
295
+ @model.connection.add_column @model.table_name, :owner_id, :integer
296
+ @model.reset_column_information
297
+ ActiveRecordTest.const_set('Vehicle', @model)
298
+
299
+ owner_model = new_model(:owner) do
300
+ has_one :vehicle, :class_name => 'ActiveRecordTest::Vehicle'
301
+ end
302
+ ActiveRecordTest.const_set('Owner', owner_model)
303
+
304
+ owner = owner_model.create
305
+ record = @model.create(:state => 'idling', :owner_id => owner.id)
306
+ assert_equal 'idling', owner.vehicle.state
307
+ end
308
+
309
+ def test_should_use_stored_values_when_loading_for_belongs_to_association
310
+ @machine.state :idling
311
+
312
+ ActiveRecordTest.const_set('Vehicle', @model)
313
+
314
+ driver_model = new_model(:driver) do
315
+ connection.add_column table_name, :vehicle_id, :integer
316
+
317
+ belongs_to :vehicle, :class_name => 'ActiveRecordTest::Vehicle'
318
+ end
319
+
320
+ ActiveRecordTest.const_set('Driver', driver_model)
321
+
322
+ record = @model.create(:state => 'idling')
323
+ driver = driver_model.create(:vehicle_id => record.id)
324
+ assert_equal 'idling', driver.vehicle.state
325
+ end
326
+
327
+ def teardown
328
+ ActiveRecordTest.class_eval do
329
+ remove_const('Vehicle') if defined?(ActiveRecordTest::Vehicle)
330
+ remove_const('Owner') if defined?(ActiveRecordTest::Owner)
331
+ remove_const('Driver') if defined?(ActiveRecordTest::Driver)
332
+ end
333
+ ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies)
334
+ super
335
+ end
336
+ end
337
+
338
+ class MachineWithDynamicInitialStateTest < BaseTestCase
339
+ def setup
340
+ @model = new_model do
341
+ attr_accessor :value
342
+ end
343
+ @machine = EnumStateMachine::Machine.new(@model, :initial => lambda {|object| :parked})
344
+ @machine.state :parked
345
+ end
346
+
347
+ def test_should_set_initial_state_on_created_object
348
+ record = @model.new
349
+ assert_equal 'parked', record.state
350
+ end
351
+
352
+ def test_should_still_set_attributes
353
+ record = @model.new(:value => 1)
354
+ assert_equal 1, record.value
355
+ end
356
+
357
+ def test_should_still_allow_initialize_blocks
358
+ block_args = nil
359
+ record = @model.new do |*args|
360
+ block_args = args
361
+ end
362
+
363
+ assert_equal [record], block_args
364
+ end
365
+
366
+ def test_should_set_attributes_prior_to_initialize_block
367
+ state = nil
368
+ @model.new do |record|
369
+ state = record.state
370
+ end
371
+
372
+ assert_equal 'parked', state
373
+ end
374
+
375
+ def test_should_set_attributes_prior_to_after_initialize_hook
376
+ state = nil
377
+ @model.class_eval {define_method(:after_initialize) {}} if ActiveRecord::VERSION::MAJOR <= 2
378
+ @model.after_initialize do |record|
379
+ state = record.state
380
+ end
381
+ @model.new
382
+ assert_equal 'parked', state
383
+ end
384
+
385
+ def test_should_set_initial_state_after_setting_attributes
386
+ @model.class_eval do
387
+ attr_accessor :state_during_setter
388
+
389
+ remove_method :value=
390
+ define_method(:value=) do |value|
391
+ self.state_during_setter = state || 'nil'
392
+ end
393
+ end
394
+
395
+ record = @model.new(:value => 1)
396
+ assert_equal 'nil', record.state_during_setter
397
+ end
398
+
399
+ def test_should_not_set_initial_state_after_already_initialized
400
+ record = @model.new(:value => 1)
401
+ assert_equal 'parked', record.state
402
+
403
+ record.state = 'idling'
404
+ record.attributes = {}
405
+ assert_equal 'idling', record.state
406
+ end
407
+
408
+ def test_should_persist_initial_state
409
+ record = @model.new
410
+ record.save
411
+ record.reload
412
+ assert_equal 'parked', record.state
413
+ end
414
+
415
+ unless ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0
416
+ def test_should_persist_initial_state_on_dup
417
+ record = @model.create.dup
418
+ record.save
419
+ record.reload
420
+ assert_equal 'parked', record.state
421
+ end
422
+ end
423
+
424
+ def test_should_use_stored_values_when_loading_from_database
425
+ @machine.state :idling
426
+
427
+ record = @model.find(@model.create(:state => 'idling').id)
428
+ assert_equal 'idling', record.state
429
+ end
430
+
431
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
432
+ @machine.state nil
433
+
434
+ record = @model.find(@model.create(:state => nil).id)
435
+ assert_nil record.state
436
+ end
437
+ end
438
+
439
+ class MachineWithEventsTest < BaseTestCase
440
+ def setup
441
+ @model = new_model
442
+ @machine = EnumStateMachine::Machine.new(@model)
443
+ @machine.event :shift_up
444
+ end
445
+
446
+ def test_should_humanize_name
447
+ assert_equal 'shift up', @machine.event(:shift_up).human_name
448
+ end
449
+ end
450
+
451
+ class MachineWithSameColumnDefaultTest < BaseTestCase
452
+ def setup
453
+ @original_stderr, $stderr = $stderr, StringIO.new
454
+
455
+ @model = new_model do
456
+ connection.add_column table_name, :status, :string, :default => 'parked'
457
+ end
458
+ @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked)
459
+ @record = @model.new
460
+ end
461
+
462
+ def test_should_use_machine_default
463
+ assert_equal 'parked', @record.status
464
+ end
465
+
466
+ def test_should_not_generate_a_warning
467
+ assert_no_match(/have defined a different default/, $stderr.string)
468
+ end
469
+
470
+ def teardown
471
+ $stderr = @original_stderr
472
+ super
473
+ end
474
+ end
475
+
476
+ class MachineWithDifferentColumnDefaultTest < BaseTestCase
477
+ def setup
478
+ @original_stderr, $stderr = $stderr, StringIO.new
479
+
480
+ @model = new_model do
481
+ connection.add_column table_name, :status, :string, :default => 'idling'
482
+ end
483
+ @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked)
484
+ @record = @model.new
485
+ end
486
+
487
+ def test_should_use_machine_default
488
+ assert_equal 'parked', @record.status
489
+ end
490
+
491
+ def test_should_generate_a_warning
492
+ assert_match(/Both ActiveRecordTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string)
493
+ end
494
+
495
+ def teardown
496
+ $stderr = @original_stderr
497
+ super
498
+ end
499
+ end
500
+
501
+ class MachineWithDifferentIntegerColumnDefaultTest < BaseTestCase
502
+ def setup
503
+ @original_stderr, $stderr = $stderr, StringIO.new
504
+
505
+ @model = new_model do
506
+ connection.add_column table_name, :status, :integer, :default => 0
507
+ end
508
+ @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked)
509
+ @machine.state :parked, :value => 1
510
+ @record = @model.new
511
+ end
512
+
513
+ def test_should_use_machine_default
514
+ assert_equal 1, @record.status
515
+ end
516
+
517
+ def test_should_generate_a_warning
518
+ assert_match(/Both ActiveRecordTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string)
519
+ end
520
+
521
+ def teardown
522
+ $stderr = @original_stderr
523
+ super
524
+ end
525
+ end
526
+
527
+ class MachineWithConflictingPredicateTest < BaseTestCase
528
+ def setup
529
+ @model = new_model do
530
+ def state?(*args)
531
+ true
532
+ end
533
+ end
534
+
535
+ @machine = EnumStateMachine::Machine.new(@model)
536
+ @record = @model.new
537
+ end
538
+
539
+ def test_should_not_define_attribute_predicate
540
+ assert @record.state?
541
+ end
542
+ end
543
+
544
+ class MachineWithConflictingStateNameTest < BaseTestCase
545
+ def setup
546
+ require 'stringio'
547
+ @original_stderr, $stderr = $stderr, StringIO.new
548
+
549
+ @model = new_model
550
+ end
551
+
552
+ def test_should_output_warning_with_same_machine_name
553
+ @machine = EnumStateMachine::Machine.new(@model)
554
+ @machine.state :state
555
+
556
+ assert_match(/^Instance method "state\?" is already defined in ActiveRecordTest::Foo, use generic helper instead.*\n$/, $stderr.string)
557
+ end
558
+
559
+ def test_should_output_warning_with_same_machine_attribute
560
+ @machine = EnumStateMachine::Machine.new(@model, :public_state, :attribute => :state)
561
+ @machine.state :state
562
+
563
+ assert_match(/^Instance method "state\?" is already defined in ActiveRecordTest::Foo, use generic helper instead.*\n$/, $stderr.string)
564
+ end
565
+
566
+ def teardown
567
+ $stderr = @original_stderr
568
+ super
569
+ end
570
+ end
571
+
572
+ class MachineWithColumnStateAttributeTest < BaseTestCase
573
+ def setup
574
+ @model = new_model
575
+ @machine = EnumStateMachine::Machine.new(@model, :initial => :parked)
576
+ @machine.other_states(:idling)
577
+
578
+ @record = @model.new
579
+ end
580
+
581
+ def test_should_not_override_the_column_reader
582
+ @record[:state] = 'parked'
583
+ assert_equal 'parked', @record.state
584
+ end
585
+
586
+ def test_should_not_override_the_column_writer
587
+ @record.state = 'parked'
588
+ assert_equal 'parked', @record[:state]
589
+ end
590
+
591
+ def test_should_have_an_attribute_predicate
592
+ assert @record.respond_to?(:state?)
593
+ end
594
+
595
+ def test_should_test_for_existence_on_predicate_without_parameters
596
+ assert @record.state?
597
+
598
+ @record.state = nil
599
+ assert !@record.state?
600
+ end
601
+
602
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
603
+ assert !@record.state?(:idling)
604
+ end
605
+
606
+ def test_should_return_true_for_predicate_if_matches_current_value
607
+ assert @record.state?(:parked)
608
+ end
609
+
610
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
611
+ assert_raise(IndexError) { @record.state?(:invalid) }
612
+ end
613
+ end
614
+
615
+ class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
616
+ def setup
617
+ @model = new_model do
618
+ def initialize
619
+ # Skip attribute initialization
620
+ @initialized_state_machines = true
621
+ super
622
+ end
623
+ end
624
+
625
+ @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked)
626
+ @machine.other_states(:idling)
627
+ @record = @model.new
628
+ end
629
+
630
+ def test_should_not_define_a_column_for_the_attribute
631
+ assert_nil @model.columns_hash['status']
632
+ end
633
+
634
+ def test_should_define_a_reader_attribute_for_the_attribute
635
+ assert @record.respond_to?(:status)
636
+ end
637
+
638
+ def test_should_define_a_writer_attribute_for_the_attribute
639
+ assert @record.respond_to?(:status=)
640
+ end
641
+
642
+ def test_should_define_an_attribute_predicate
643
+ assert @record.respond_to?(:status?)
644
+ end
645
+ end
646
+
647
+ class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase
648
+ def setup
649
+ @model = new_model do
650
+ def status=(value)
651
+ self['status'] = value
652
+ end
653
+
654
+ def status
655
+ self['status']
656
+ end
657
+ end
658
+
659
+ @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked)
660
+ @machine.other_states(:idling)
661
+ @record = @model.new
662
+ end
663
+
664
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
665
+ assert !@record.status?(:idling)
666
+ end
667
+
668
+ def test_should_return_true_for_predicate_if_matches_current_value
669
+ assert @record.status?(:parked)
670
+ end
671
+
672
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
673
+ assert_raise(IndexError) { @record.status?(:invalid) }
674
+ end
675
+
676
+ def test_should_set_initial_state_on_created_object
677
+ assert_equal 'parked', @record.status
678
+ end
679
+ end
680
+
681
+ class MachineWithAliasedAttributeTest < BaseTestCase
682
+ def setup
683
+ @model = new_model do
684
+ alias_attribute :vehicle_status, :state
685
+ end
686
+
687
+ @machine = EnumStateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
688
+ @machine.state :parked
689
+
690
+ @record = @model.new
691
+ end
692
+
693
+ def test_should_check_custom_attribute_for_predicate
694
+ @record.vehicle_status = nil
695
+ assert !@record.status?(:parked)
696
+
697
+ @record.vehicle_status = 'parked'
698
+ assert @record.status?(:parked)
699
+ end
700
+ end
701
+
702
+ class MachineWithCustomAttributeTest < BaseTestCase
703
+ def setup
704
+ require 'stringio'
705
+ @original_stderr, $stderr = $stderr, StringIO.new
706
+
707
+ @model = new_model
708
+ @machine = EnumStateMachine::Machine.new(@model, :public_state, :attribute => :state)
709
+ @record = @model.new
710
+ end
711
+
712
+ def test_should_not_delegate_attribute_predicate_with_different_attribute
713
+ assert_raise(ArgumentError) { @record.public_state? }
714
+ end
715
+
716
+ def teardown
717
+ $stderr = @original_stderr
718
+ super
719
+ end
720
+ end
721
+
722
+ class MachineWithInitializedStateTest < BaseTestCase
723
+ def setup
724
+ @model = new_model
725
+ @machine = EnumStateMachine::Machine.new(@model, :initial => :parked)
726
+ @machine.state :idling
727
+ end
728
+
729
+ def test_should_allow_nil_initial_state_when_static
730
+ @machine.state nil
731
+
732
+ record = @model.new(:state => nil)
733
+ assert_nil record.state
734
+ end
735
+
736
+ def test_should_allow_nil_initial_state_when_dynamic
737
+ @machine.state nil
738
+
739
+ @machine.initial_state = lambda {:parked}
740
+ record = @model.new(:state => nil)
741
+ assert_nil record.state
742
+ end
743
+
744
+ def test_should_allow_different_initial_state_when_static
745
+ record = @model.new(:state => 'idling')
746
+ assert_equal 'idling', record.state
747
+ end
748
+
749
+ def test_should_allow_different_initial_state_when_dynamic
750
+ @machine.initial_state = lambda {:parked}
751
+ record = @model.new(:state => 'idling')
752
+ assert_equal 'idling', record.state
753
+ end
754
+
755
+ def test_should_use_default_state_if_protected
756
+ @model.class_eval do
757
+ attr_protected :state
758
+ end
759
+
760
+ record = @model.new(:state => 'idling')
761
+ assert_equal 'parked', record.state
762
+ end
763
+ end
764
+
765
+ class MachineMultipleTest < BaseTestCase
766
+ def setup
767
+ @model = new_model do
768
+ connection.add_column table_name, :status, :string
769
+ end
770
+ @state_machine = EnumStateMachine::Machine.new(@model, :initial => :parked)
771
+ @status_machine = EnumStateMachine::Machine.new(@model, :status, :initial => :idling)
772
+ end
773
+
774
+ def test_should_should_initialize_each_state
775
+ record = @model.new
776
+ assert_equal 'parked', record.state
777
+ assert_equal 'idling', record.status
778
+ end
779
+ end
780
+
781
+ class MachineWithLoopbackTest < BaseTestCase
782
+ def setup
783
+ @model = new_model do
784
+ connection.add_column table_name, :updated_at, :datetime
785
+ end
786
+
787
+ @machine = EnumStateMachine::Machine.new(@model, :initial => :parked)
788
+ @machine.event :park
789
+
790
+ @record = @model.create(:updated_at => Time.now - 1)
791
+ @transition = EnumStateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
792
+
793
+ @timestamp = @record.updated_at
794
+ @transition.perform
795
+ end
796
+
797
+ if ActiveRecord.const_defined?(:Dirty) || ActiveRecord::AttributeMethods.const_defined?(:Dirty)
798
+ def test_should_not_update_record
799
+ assert_equal @timestamp, @record.updated_at
800
+ end
801
+ else
802
+ def test_should_update_record
803
+ assert_not_equal @timestamp, @record.updated_at
804
+ end
805
+ end
806
+ end
807
+
808
+ if ActiveRecord.const_defined?(:Dirty) || ActiveRecord::AttributeMethods.const_defined?(:Dirty)
809
+ class MachineWithDirtyAttributesTest < BaseTestCase
810
+ def setup
811
+ @model = new_model
812
+ @machine = EnumStateMachine::Machine.new(@model, :initial => :parked)
813
+ @machine.event :ignite
814
+ @machine.state :idling
815
+
816
+ @record = @model.create
817
+
818
+ @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
819
+ @transition.perform(false)
820
+ end
821
+
822
+ def test_should_include_state_in_changed_attributes
823
+ assert_equal %w(state), @record.changed
824
+ end
825
+
826
+ def test_should_track_attribute_change
827
+ assert_equal %w(parked idling), @record.changes['state']
828
+ end
829
+
830
+ def test_should_not_reset_changes_on_multiple_transitions
831
+ transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
832
+ transition.perform(false)
833
+
834
+ assert_equal %w(parked idling), @record.changes['state']
835
+ end
836
+
837
+ def test_should_not_have_changes_when_loaded_from_database
838
+ record = @model.find(@record.id)
839
+ assert !record.changed?
840
+ end
841
+ end
842
+
843
+ class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase
844
+ def setup
845
+ @model = new_model
846
+ @machine = EnumStateMachine::Machine.new(@model, :initial => :parked)
847
+ @machine.event :park
848
+
849
+ @record = @model.create
850
+
851
+ @transition = EnumStateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
852
+ @transition.perform(false)
853
+ end
854
+
855
+ def test_should_not_include_state_in_changed_attributes
856
+ assert_equal [], @record.changed
857
+ end
858
+
859
+ def test_should_not_track_attribute_changes
860
+ assert_equal nil, @record.changes['state']
861
+ end
862
+ end
863
+
864
+ class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase
865
+ def setup
866
+ @model = new_model do
867
+ connection.add_column table_name, :status, :string
868
+ end
869
+ @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked)
870
+ @machine.event :ignite
871
+ @machine.state :idling
872
+
873
+ @record = @model.create
874
+
875
+ @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
876
+ @transition.perform(false)
877
+ end
878
+
879
+ def test_should_include_state_in_changed_attributes
880
+ assert_equal %w(status), @record.changed
881
+ end
882
+
883
+ def test_should_track_attribute_change
884
+ assert_equal %w(parked idling), @record.changes['status']
885
+ end
886
+
887
+ def test_should_not_reset_changes_on_multiple_transitions
888
+ transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
889
+ transition.perform(false)
890
+
891
+ assert_equal %w(parked idling), @record.changes['status']
892
+ end
893
+ end
894
+
895
+ class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase
896
+ def setup
897
+ @model = new_model do
898
+ connection.add_column table_name, :status, :string
899
+ end
900
+ @machine = EnumStateMachine::Machine.new(@model, :status, :initial => :parked)
901
+ @machine.event :park
902
+
903
+ @record = @model.create
904
+
905
+ @transition = EnumStateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
906
+ @transition.perform(false)
907
+ end
908
+
909
+ def test_should_not_include_state_in_changed_attributes
910
+ assert_equal [], @record.changed
911
+ end
912
+
913
+ def test_should_not_track_attribute_changes
914
+ assert_equal nil, @record.changes['status']
915
+ end
916
+ end
917
+
918
+ class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase
919
+ def setup
920
+ @model = new_model
921
+ @machine = EnumStateMachine::Machine.new(@model, :initial => :parked)
922
+ @machine.event :ignite
923
+
924
+ @record = @model.create
925
+ @record.state_event = 'ignite'
926
+ end
927
+
928
+ def test_should_not_include_state_in_changed_attributes
929
+ assert_equal [], @record.changed
930
+ end
931
+
932
+ def test_should_not_track_attribute_change
933
+ assert_equal nil, @record.changes['state']
934
+ end
935
+ end
936
+ else
937
+ $stderr.puts 'Skipping ActiveRecord Dirty tests.'
938
+ end
939
+
940
+ class MachineWithoutTransactionsTest < BaseTestCase
941
+ def setup
942
+ @model = new_model
943
+ @machine = EnumStateMachine::Machine.new(@model, :use_transactions => false)
944
+ end
945
+
946
+ def test_should_not_rollback_transaction_if_false
947
+ @machine.within_transaction(@model.new) do
948
+ @model.create
949
+ false
950
+ end
951
+
952
+ assert_equal 1, @model.count
953
+ end
954
+
955
+ def test_should_not_rollback_transaction_if_true
956
+ @machine.within_transaction(@model.new) do
957
+ @model.create
958
+ true
959
+ end
960
+
961
+ assert_equal 1, @model.count
962
+ end
963
+ end
964
+
965
+ class MachineWithTransactionsTest < BaseTestCase
966
+ def setup
967
+ @model = new_model
968
+ @machine = EnumStateMachine::Machine.new(@model, :use_transactions => true)
969
+ end
970
+
971
+ def test_should_rollback_transaction_if_false
972
+ @machine.within_transaction(@model.new) do
973
+ @model.create
974
+ false
975
+ end
976
+
977
+ assert_equal 0, @model.count
978
+ end
979
+
980
+ def test_should_not_rollback_transaction_if_true
981
+ @machine.within_transaction(@model.new) do
982
+ @model.create
983
+ true
984
+ end
985
+
986
+ assert_equal 1, @model.count
987
+ end
988
+ end
989
+
990
+ class MachineWithCallbacksTest < BaseTestCase
991
+ def setup
992
+ @model = new_model
993
+ @machine = EnumStateMachine::Machine.new(@model, :initial => :parked)
994
+ @machine.other_states :idling
995
+ @machine.event :ignite
996
+
997
+ @record = @model.new(:state => 'parked')
998
+ @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
999
+ end
1000
+
1001
+ def test_should_run_before_callbacks
1002
+ called = false
1003
+ @machine.before_transition {called = true}
1004
+
1005
+ @transition.perform
1006
+ assert called
1007
+ end
1008
+
1009
+ def test_should_pass_record_to_before_callbacks_with_one_argument
1010
+ record = nil
1011
+ @machine.before_transition {|arg| record = arg}
1012
+
1013
+ @transition.perform
1014
+ assert_equal @record, record
1015
+ end
1016
+
1017
+ def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments
1018
+ callback_args = nil
1019
+ @machine.before_transition {|*args| callback_args = args}
1020
+
1021
+ @transition.perform
1022
+ assert_equal [@record, @transition], callback_args
1023
+ end
1024
+
1025
+ def test_should_run_before_callbacks_outside_the_context_of_the_record
1026
+ context = nil
1027
+ @machine.before_transition {context = self}
1028
+
1029
+ @transition.perform
1030
+ assert_equal self, context
1031
+ end
1032
+
1033
+ def test_should_run_after_callbacks
1034
+ called = false
1035
+ @machine.after_transition {called = true}
1036
+
1037
+ @transition.perform
1038
+ assert called
1039
+ end
1040
+
1041
+ def test_should_pass_record_to_after_callbacks_with_one_argument
1042
+ record = nil
1043
+ @machine.after_transition {|arg| record = arg}
1044
+
1045
+ @transition.perform
1046
+ assert_equal @record, record
1047
+ end
1048
+
1049
+ def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments
1050
+ callback_args = nil
1051
+ @machine.after_transition {|*args| callback_args = args}
1052
+
1053
+ @transition.perform
1054
+ assert_equal [@record, @transition], callback_args
1055
+ end
1056
+
1057
+ def test_should_run_after_callbacks_outside_the_context_of_the_record
1058
+ context = nil
1059
+ @machine.after_transition {context = self}
1060
+
1061
+ @transition.perform
1062
+ assert_equal self, context
1063
+ end
1064
+
1065
+ def test_should_run_after_callbacks_if_model_callback_added_prior_to_state_machine_definition
1066
+ model = new_model do
1067
+ after_save { nil }
1068
+ end
1069
+ machine = EnumStateMachine::Machine.new(model, :initial => :parked)
1070
+ machine.other_states :idling
1071
+ machine.event :ignite
1072
+ after_called = false
1073
+ machine.after_transition {after_called = true}
1074
+
1075
+ record = model.new(:state => 'parked')
1076
+ transition = EnumStateMachine::Transition.new(record, machine, :ignite, :parked, :idling)
1077
+ transition.perform
1078
+ assert_equal true, after_called
1079
+ end
1080
+
1081
+ def test_should_run_around_callbacks
1082
+ before_called = false
1083
+ after_called = false
1084
+ ensure_called = 0
1085
+ @machine.around_transition do |block|
1086
+ before_called = true
1087
+ begin
1088
+ block.call
1089
+ ensure
1090
+ ensure_called += 1
1091
+ end
1092
+ after_called = true
1093
+ end
1094
+
1095
+ @transition.perform
1096
+ assert before_called
1097
+ assert after_called
1098
+ assert_equal ensure_called, 1
1099
+ end
1100
+
1101
+ def test_should_include_transition_states_in_known_states
1102
+ @machine.before_transition :to => :first_gear, :do => lambda {}
1103
+
1104
+ assert_equal [:parked, :idling, :first_gear], @machine.states.map {|state| state.name}
1105
+ end
1106
+
1107
+ def test_should_allow_symbolic_callbacks
1108
+ callback_args = nil
1109
+
1110
+ klass = class << @record; self; end
1111
+ klass.send(:define_method, :after_ignite) do |*args|
1112
+ callback_args = args
1113
+ end
1114
+
1115
+ @machine.before_transition(:after_ignite)
1116
+
1117
+ @transition.perform
1118
+ assert_equal [@transition], callback_args
1119
+ end
1120
+
1121
+ def test_should_allow_string_callbacks
1122
+ class << @record
1123
+ attr_reader :callback_result
1124
+ end
1125
+
1126
+ @machine.before_transition('@callback_result = [1, 2, 3]')
1127
+ @transition.perform
1128
+
1129
+ assert_equal [1, 2, 3], @record.callback_result
1130
+ end
1131
+
1132
+ def test_should_run_in_expected_order
1133
+ expected = [
1134
+ :before_transition, :before_validation, :after_validation,
1135
+ :before_save, :before_create, :after_create, :after_save,
1136
+ :after_transition
1137
+ ]
1138
+
1139
+ callbacks = []
1140
+ @model.before_validation { callbacks << :before_validation }
1141
+ @model.after_validation { callbacks << :after_validation }
1142
+ @model.before_save { callbacks << :before_save }
1143
+ @model.before_create { callbacks << :before_create }
1144
+ @model.after_create { callbacks << :after_create }
1145
+ @model.after_save { callbacks << :after_save }
1146
+ if @model.respond_to?(:after_commit)
1147
+ @model.after_commit { callbacks << :after_commit }
1148
+ expected << :after_commit
1149
+ end
1150
+
1151
+ @machine.before_transition { callbacks << :before_transition }
1152
+ @machine.after_transition { callbacks << :after_transition }
1153
+
1154
+ @transition.perform
1155
+
1156
+ assert_equal expected, callbacks
1157
+ end
1158
+ end
1159
+
1160
+ class MachineWithFailedBeforeCallbacksTest < BaseTestCase
1161
+ def setup
1162
+ @callbacks = []
1163
+
1164
+ @model = new_model
1165
+ @machine = EnumStateMachine::Machine.new(@model)
1166
+ @machine.state :parked, :idling
1167
+ @machine.event :ignite
1168
+ @machine.before_transition {@callbacks << :before_1; false}
1169
+ @machine.before_transition {@callbacks << :before_2}
1170
+ @machine.after_transition {@callbacks << :after}
1171
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
1172
+
1173
+ @record = @model.new(:state => 'parked')
1174
+ @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
1175
+ @result = @transition.perform
1176
+ end
1177
+
1178
+ def test_should_not_be_successful
1179
+ assert !@result
1180
+ end
1181
+
1182
+ def test_should_not_change_current_state
1183
+ assert_equal 'parked', @record.state
1184
+ end
1185
+
1186
+ def test_should_not_run_action
1187
+ assert @record.new_record?
1188
+ end
1189
+
1190
+ def test_should_not_run_further_callbacks
1191
+ assert_equal [:before_1], @callbacks
1192
+ end
1193
+ end
1194
+
1195
+ class MachineNestedActionTest < BaseTestCase
1196
+ def setup
1197
+ @callbacks = []
1198
+
1199
+ @model = new_model
1200
+ @machine = EnumStateMachine::Machine.new(@model)
1201
+ @machine.event :ignite do
1202
+ transition :parked => :idling
1203
+ end
1204
+
1205
+ @record = @model.new(:state => 'parked')
1206
+ end
1207
+
1208
+ def test_should_allow_transition_prior_to_creation_if_skipping_action
1209
+ record = @record
1210
+ @model.before_create { record.ignite(false) }
1211
+ result = @record.save
1212
+
1213
+ assert_equal true, result
1214
+ assert_equal "idling", @record.state
1215
+ @record.reload
1216
+ assert_equal "idling", @record.state
1217
+ end
1218
+
1219
+ def test_should_allow_transition_after_creation
1220
+ record = @record
1221
+ @model.after_create { record.ignite }
1222
+ result = @record.save
1223
+
1224
+ assert_equal true, result
1225
+ assert_equal "idling", @record.state
1226
+ @record.reload
1227
+ assert_equal "idling", @record.state
1228
+ end
1229
+ end
1230
+
1231
+ class MachineWithFailedActionTest < BaseTestCase
1232
+ def setup
1233
+ @model = new_model do
1234
+ validates_inclusion_of :state, :in => %w(first_gear)
1235
+ end
1236
+
1237
+ @machine = EnumStateMachine::Machine.new(@model)
1238
+ @machine.state :parked, :idling
1239
+ @machine.event :ignite
1240
+
1241
+ @callbacks = []
1242
+ @machine.before_transition {@callbacks << :before}
1243
+ @machine.after_transition {@callbacks << :after}
1244
+ @machine.after_failure {@callbacks << :after_failure}
1245
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
1246
+
1247
+ @record = @model.new(:state => 'parked')
1248
+ @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
1249
+ @result = @transition.perform
1250
+ end
1251
+
1252
+ def test_should_not_be_successful
1253
+ assert !@result
1254
+ end
1255
+
1256
+ def test_should_not_change_current_state
1257
+ assert_equal 'parked', @record.state
1258
+ end
1259
+
1260
+ def test_should_not_save_record
1261
+ assert @record.new_record?
1262
+ end
1263
+
1264
+ def test_should_run_before_callbacks_and_after_callbacks_with_failures
1265
+ assert_equal [:before, :around_before, :after_failure], @callbacks
1266
+ end
1267
+ end
1268
+
1269
+ class MachineWithFailedAfterCallbacksTest < BaseTestCase
1270
+ def setup
1271
+ @callbacks = []
1272
+
1273
+ @model = new_model
1274
+ @machine = EnumStateMachine::Machine.new(@model)
1275
+ @machine.state :parked, :idling
1276
+ @machine.event :ignite
1277
+ @machine.after_transition {@callbacks << :after_1; false}
1278
+ @machine.after_transition {@callbacks << :after_2}
1279
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
1280
+
1281
+ @record = @model.new(:state => 'parked')
1282
+ @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
1283
+ @result = @transition.perform
1284
+ end
1285
+
1286
+ def test_should_be_successful
1287
+ assert @result
1288
+ end
1289
+
1290
+ def test_should_change_current_state
1291
+ assert_equal 'idling', @record.state
1292
+ end
1293
+
1294
+ def test_should_save_record
1295
+ assert !@record.new_record?
1296
+ end
1297
+
1298
+ def test_should_not_run_further_after_callbacks
1299
+ assert_equal [:around_before, :around_after, :after_1], @callbacks
1300
+ end
1301
+ end
1302
+
1303
+ class MachineWithValidationsTest < BaseTestCase
1304
+ def setup
1305
+ @model = new_model
1306
+ @machine = EnumStateMachine::Machine.new(@model)
1307
+ @machine.state :parked
1308
+
1309
+ @record = @model.new
1310
+ end
1311
+
1312
+ def test_should_invalidate_using_errors
1313
+ I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:I18n)
1314
+ @record.state = 'parked'
1315
+
1316
+ @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']])
1317
+ assert_equal ['State cannot transition via "park"'], @record.errors.full_messages
1318
+ end
1319
+
1320
+ def test_should_auto_prefix_custom_attributes_on_invalidation
1321
+ @machine.invalidate(@record, :event, :invalid)
1322
+
1323
+ assert_equal ['State event is invalid'], @record.errors.full_messages
1324
+ end
1325
+
1326
+ def test_should_clear_errors_on_reset
1327
+ @record.state = 'parked'
1328
+ @record.errors.add(:state, 'is invalid')
1329
+
1330
+ @machine.reset(@record)
1331
+ assert_equal [], @record.errors.full_messages
1332
+ end
1333
+
1334
+ def test_should_be_valid_if_state_is_known
1335
+ @record.state = 'parked'
1336
+
1337
+ assert @record.valid?
1338
+ end
1339
+
1340
+ def test_should_not_be_valid_if_state_is_unknown
1341
+ @record.state = 'invalid'
1342
+
1343
+ assert !@record.valid?
1344
+ assert_equal ['State is invalid'], @record.errors.full_messages
1345
+ end
1346
+ end
1347
+
1348
+ class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
1349
+ def setup
1350
+ @model = new_model
1351
+ @machine = EnumStateMachine::Machine.new(@model, :status, :attribute => :state)
1352
+ @machine.state :parked
1353
+
1354
+ @record = @model.new
1355
+ end
1356
+
1357
+ def test_should_add_validation_errors_to_custom_attribute
1358
+ @record.state = 'invalid'
1359
+
1360
+ assert !@record.valid?
1361
+ assert_equal ['State is invalid'], @record.errors.full_messages
1362
+
1363
+ @record.state = 'parked'
1364
+ assert @record.valid?
1365
+ end
1366
+ end
1367
+
1368
+ class MachineErrorsTest < BaseTestCase
1369
+ def setup
1370
+ @model = new_model
1371
+ @machine = EnumStateMachine::Machine.new(@model)
1372
+ @record = @model.new
1373
+ end
1374
+
1375
+ def test_should_be_able_to_describe_current_errors
1376
+ @record.errors.add(:id, 'cannot be blank')
1377
+ @record.errors.add(:state, 'is invalid')
1378
+ assert_equal ['Id cannot be blank', 'State is invalid'], @machine.errors_for(@record).split(', ').sort
1379
+ end
1380
+
1381
+ def test_should_describe_as_halted_with_no_errors
1382
+ assert_equal 'Transition halted', @machine.errors_for(@record)
1383
+ end
1384
+ end
1385
+
1386
+ class MachineWithStateDrivenValidationsTest < BaseTestCase
1387
+ def setup
1388
+ @model = new_model do
1389
+ attr_accessor :seatbelt
1390
+ end
1391
+
1392
+ @machine = EnumStateMachine::Machine.new(@model)
1393
+ @machine.state :first_gear, :second_gear do
1394
+ validates_presence_of :seatbelt
1395
+ end
1396
+ @machine.other_states :parked
1397
+ end
1398
+
1399
+ def test_should_be_valid_if_validation_fails_outside_state_scope
1400
+ record = @model.new(:state => 'parked', :seatbelt => nil)
1401
+ assert record.valid?
1402
+ end
1403
+
1404
+ def test_should_be_invalid_if_validation_fails_within_state_scope
1405
+ record = @model.new(:state => 'first_gear', :seatbelt => nil)
1406
+ assert !record.valid?
1407
+ end
1408
+
1409
+ def test_should_be_valid_if_validation_succeeds_within_state_scope
1410
+ record = @model.new(:state => 'second_gear', :seatbelt => true)
1411
+ assert record.valid?
1412
+ end
1413
+ end
1414
+
1415
+ class MachineWithEventAttributesOnValidationTest < BaseTestCase
1416
+ def setup
1417
+ @model = new_model
1418
+ @machine = EnumStateMachine::Machine.new(@model)
1419
+ @machine.event :ignite do
1420
+ transition :parked => :idling
1421
+ end
1422
+
1423
+ @record = @model.new
1424
+ @record.state = 'parked'
1425
+ @record.state_event = 'ignite'
1426
+ end
1427
+
1428
+ def test_should_fail_if_event_is_invalid
1429
+ @record.state_event = 'invalid'
1430
+ assert !@record.valid?
1431
+ assert_equal ['State event is invalid'], @record.errors.full_messages
1432
+ end
1433
+
1434
+ def test_should_fail_if_event_has_no_transition
1435
+ @record.state = 'idling'
1436
+ assert !@record.valid?
1437
+ assert_equal ['State event cannot transition when idling'], @record.errors.full_messages
1438
+ end
1439
+
1440
+ def test_should_be_successful_if_event_has_transition
1441
+ assert @record.valid?
1442
+ end
1443
+
1444
+ def test_should_run_before_callbacks
1445
+ ran_callback = false
1446
+ @machine.before_transition { ran_callback = true }
1447
+
1448
+ @record.valid?
1449
+ assert ran_callback
1450
+ end
1451
+
1452
+ def test_should_run_around_callbacks_before_yield
1453
+ ran_callback = false
1454
+ @machine.around_transition {|block| ran_callback = true; block.call }
1455
+
1456
+ begin
1457
+ @record.valid?
1458
+ rescue ArgumentError
1459
+ raise if EnumStateMachine::Transition.pause_supported?
1460
+ end
1461
+ assert ran_callback
1462
+ end
1463
+
1464
+ def test_should_persist_new_state
1465
+ @record.valid?
1466
+ assert_equal 'idling', @record.state
1467
+ end
1468
+
1469
+ def test_should_not_run_after_callbacks
1470
+ ran_callback = false
1471
+ @machine.after_transition { ran_callback = true }
1472
+
1473
+ @record.valid?
1474
+ assert !ran_callback
1475
+ end
1476
+
1477
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
1478
+ @model.class_eval do
1479
+ attr_accessor :seatbelt
1480
+ validates_presence_of :seatbelt
1481
+ end
1482
+
1483
+ ran_callback = false
1484
+ @machine.after_transition { ran_callback = true }
1485
+
1486
+ @record.valid?
1487
+ assert !ran_callback
1488
+ end
1489
+
1490
+ def test_should_run_after_callbacks_if_validation_fails
1491
+ @model.class_eval do
1492
+ attr_accessor :seatbelt
1493
+ validates_presence_of :seatbelt
1494
+ end
1495
+
1496
+ ran_callback = false
1497
+ @machine.after_failure { ran_callback = true }
1498
+
1499
+ @record.valid?
1500
+ assert ran_callback
1501
+ end
1502
+
1503
+ def test_should_not_run_around_callbacks_after_yield
1504
+ ran_callback = false
1505
+ @machine.around_transition {|block| block.call; ran_callback = true }
1506
+
1507
+ begin
1508
+ @record.valid?
1509
+ rescue ArgumentError
1510
+ raise if EnumStateMachine::Transition.pause_supported?
1511
+ end
1512
+ assert !ran_callback
1513
+ end
1514
+
1515
+ def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails
1516
+ @model.class_eval do
1517
+ attr_accessor :seatbelt
1518
+ validates_presence_of :seatbelt
1519
+ end
1520
+
1521
+ ran_callback = false
1522
+ @machine.around_transition {|block| block.call; ran_callback = true }
1523
+
1524
+ @record.valid?
1525
+ assert !ran_callback
1526
+ end
1527
+
1528
+ def test_should_not_run_before_transitions_within_transaction
1529
+ @machine.before_transition { @model.create; raise ActiveRecord::Rollback }
1530
+
1531
+ begin
1532
+ @record.valid?
1533
+ rescue Exception
1534
+ end
1535
+
1536
+ assert_equal 1, @model.count
1537
+ end
1538
+ end
1539
+
1540
+ class MachineWithEventAttributesOnSaveTest < BaseTestCase
1541
+ def setup
1542
+ @model = new_model
1543
+ @machine = EnumStateMachine::Machine.new(@model)
1544
+ @machine.event :ignite do
1545
+ transition :parked => :idling
1546
+ end
1547
+
1548
+ @record = @model.new
1549
+ @record.state = 'parked'
1550
+ @record.state_event = 'ignite'
1551
+ end
1552
+
1553
+ def test_should_fail_if_event_is_invalid
1554
+ @record.state_event = 'invalid'
1555
+ assert_equal false, @record.save
1556
+ end
1557
+
1558
+ def test_should_fail_if_event_has_no_transition
1559
+ @record.state = 'idling'
1560
+ assert_equal false, @record.save
1561
+ end
1562
+
1563
+ def test_should_run_before_callbacks
1564
+ ran_callback = false
1565
+ @machine.before_transition { ran_callback = true }
1566
+
1567
+ @record.save
1568
+ assert ran_callback
1569
+ end
1570
+
1571
+ def test_should_run_before_callbacks_once
1572
+ before_count = 0
1573
+ @machine.before_transition { before_count += 1 }
1574
+
1575
+ @record.save
1576
+ assert_equal 1, before_count
1577
+ end
1578
+
1579
+ def test_should_run_around_callbacks_before_yield
1580
+ ran_callback = false
1581
+ @machine.around_transition {|block| ran_callback = true; block.call }
1582
+
1583
+ @record.save
1584
+ assert ran_callback
1585
+ end
1586
+
1587
+ def test_should_run_around_callbacks_before_yield_once
1588
+ around_before_count = 0
1589
+ @machine.around_transition {|block| around_before_count += 1; block.call }
1590
+
1591
+ @record.save
1592
+ assert_equal 1, around_before_count
1593
+ end
1594
+
1595
+ def test_should_persist_new_state
1596
+ @record.save
1597
+ assert_equal 'idling', @record.state
1598
+ end
1599
+
1600
+ def test_should_run_after_callbacks
1601
+ ran_callback = false
1602
+ @machine.after_transition { ran_callback = true }
1603
+
1604
+ @record.save
1605
+ assert ran_callback
1606
+ end
1607
+
1608
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
1609
+ @model.before_create {|record| false}
1610
+
1611
+ ran_callback = false
1612
+ @machine.after_transition { ran_callback = true }
1613
+
1614
+ begin; @record.save; rescue; end
1615
+ assert !ran_callback
1616
+ end
1617
+
1618
+ def test_should_run_failure_callbacks__if_fails
1619
+ @model.before_create {|record| false}
1620
+
1621
+ ran_callback = false
1622
+ @machine.after_failure { ran_callback = true }
1623
+
1624
+ begin; @record.save; rescue; end
1625
+ assert ran_callback
1626
+ end
1627
+
1628
+ def test_should_not_run_around_callbacks_if_fails
1629
+ @model.before_create {|record| false}
1630
+
1631
+ ran_callback = false
1632
+ @machine.around_transition {|block| block.call; ran_callback = true }
1633
+
1634
+ begin; @record.save; rescue; end
1635
+ assert !ran_callback
1636
+ end
1637
+
1638
+ def test_should_run_around_callbacks_after_yield
1639
+ ran_callback = false
1640
+ @machine.around_transition {|block| block.call; ran_callback = true }
1641
+
1642
+ @record.save
1643
+ assert ran_callback
1644
+ end
1645
+
1646
+ def test_should_run_before_transitions_within_transaction
1647
+ @machine.before_transition { @model.create; raise ActiveRecord::Rollback }
1648
+
1649
+ begin
1650
+ @record.save
1651
+ rescue Exception
1652
+ end
1653
+
1654
+ assert_equal 0, @model.count
1655
+ end
1656
+
1657
+ def test_should_run_after_transitions_within_transaction
1658
+ @machine.after_transition { @model.create; raise ActiveRecord::Rollback }
1659
+
1660
+ begin
1661
+ @record.save
1662
+ rescue Exception
1663
+ end
1664
+
1665
+ assert_equal 0, @model.count
1666
+ end
1667
+
1668
+ def test_should_run_around_transition_within_transaction
1669
+ @machine.around_transition { @model.create; raise ActiveRecord::Rollback }
1670
+
1671
+ begin
1672
+ @record.save
1673
+ rescue Exception
1674
+ end
1675
+
1676
+ assert_equal 0, @model.count
1677
+ end
1678
+
1679
+ def test_should_allow_additional_transitions_to_new_state_in_after_transitions
1680
+ @machine.event :park do
1681
+ transition :idling => :parked
1682
+ end
1683
+
1684
+ @machine.after_transition(:on => :ignite) { @record.park }
1685
+
1686
+ @record.save
1687
+ assert_equal 'parked', @record.state
1688
+
1689
+ @record.reload
1690
+ assert_equal 'parked', @record.state
1691
+ end
1692
+
1693
+ def test_should_allow_additional_transitions_to_previous_state_in_after_transitions
1694
+ @machine.event :shift_up do
1695
+ transition :idling => :first_gear
1696
+ end
1697
+
1698
+ @machine.after_transition(:on => :ignite) { @record.shift_up }
1699
+
1700
+ @record.save
1701
+ assert_equal 'first_gear', @record.state
1702
+
1703
+ @record.reload
1704
+ assert_equal 'first_gear', @record.state
1705
+ end
1706
+
1707
+ def test_should_return_nil_on_manual_rollback
1708
+ @machine.before_transition { raise ActiveRecord::Rollback }
1709
+
1710
+ assert_equal nil, @record.save
1711
+ end
1712
+ end
1713
+
1714
+ if ActiveRecord::VERSION::MAJOR >= 3 || ActiveRecord::VERSION::MINOR >= 3
1715
+ class MachineWithEventAttributesOnAutosaveTest < BaseTestCase
1716
+ def setup
1717
+ @vehicle_model = new_model(:vehicle) do
1718
+ connection.add_column table_name, :owner_id, :integer
1719
+ end
1720
+ ActiveRecordTest.const_set('Vehicle', @vehicle_model)
1721
+
1722
+ @owner_model = new_model(:owner)
1723
+ ActiveRecordTest.const_set('Owner', @owner_model)
1724
+
1725
+ machine = EnumStateMachine::Machine.new(@vehicle_model)
1726
+ machine.event :ignite do
1727
+ transition :parked => :idling
1728
+ end
1729
+
1730
+ @owner = @owner_model.create
1731
+ @vehicle = @vehicle_model.create(:state => 'parked', :owner_id => @owner.id)
1732
+ end
1733
+
1734
+ def test_should_persist_has_one_autosave
1735
+ @owner_model.has_one :vehicle, :class_name => 'ActiveRecordTest::Vehicle', :autosave => true
1736
+ @owner.vehicle.state_event = 'ignite'
1737
+ @owner.save
1738
+
1739
+ @vehicle.reload
1740
+ assert_equal 'idling', @vehicle.state
1741
+ end
1742
+
1743
+ def test_should_persist_has_many_autosave
1744
+ @owner_model.has_many :vehicles, :class_name => 'ActiveRecordTest::Vehicle', :autosave => true
1745
+ @owner.vehicles[0].state_event = 'ignite'
1746
+ @owner.save
1747
+
1748
+ @vehicle.reload
1749
+ assert_equal 'idling', @vehicle.state
1750
+ end
1751
+
1752
+ def teardown
1753
+ ActiveRecordTest.class_eval do
1754
+ remove_const('Vehicle')
1755
+ remove_const('Owner')
1756
+ end
1757
+ ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies)
1758
+ super
1759
+ end
1760
+ end
1761
+ end
1762
+
1763
+ class MachineWithEventAttributesOnSaveBangTest < BaseTestCase
1764
+ def setup
1765
+ @model = new_model
1766
+ @machine = EnumStateMachine::Machine.new(@model)
1767
+ @machine.event :ignite do
1768
+ transition :parked => :idling
1769
+ end
1770
+
1771
+ @record = @model.new
1772
+ @record.state = 'parked'
1773
+ @record.state_event = 'ignite'
1774
+ end
1775
+
1776
+ def test_should_fail_if_event_is_invalid
1777
+ @record.state_event = 'invalid'
1778
+ assert_raise(ActiveRecord::RecordInvalid) { @record.save! }
1779
+ end
1780
+
1781
+ def test_should_fail_if_event_has_no_transition
1782
+ @record.state = 'idling'
1783
+ assert_raise(ActiveRecord::RecordInvalid) { @record.save! }
1784
+ end
1785
+
1786
+ def test_should_be_successful_if_event_has_transition
1787
+ assert_equal true, @record.save!
1788
+ end
1789
+
1790
+ def test_should_run_before_callbacks
1791
+ ran_callback = false
1792
+ @machine.before_transition { ran_callback = true }
1793
+
1794
+ @record.save!
1795
+ assert ran_callback
1796
+ end
1797
+
1798
+ def test_should_run_before_callbacks_once
1799
+ before_count = 0
1800
+ @machine.before_transition { before_count += 1 }
1801
+
1802
+ @record.save!
1803
+ assert_equal 1, before_count
1804
+ end
1805
+
1806
+ def test_should_run_around_callbacks_before_yield
1807
+ ran_callback = false
1808
+ @machine.around_transition {|block| ran_callback = true; block.call }
1809
+
1810
+ @record.save!
1811
+ assert ran_callback
1812
+ end
1813
+
1814
+ def test_should_run_around_callbacks_before_yield_once
1815
+ around_before_count = 0
1816
+ @machine.around_transition {|block| around_before_count += 1; block.call }
1817
+
1818
+ @record.save!
1819
+ assert_equal 1, around_before_count
1820
+ end
1821
+
1822
+ def test_should_persist_new_state
1823
+ @record.save!
1824
+ assert_equal 'idling', @record.state
1825
+ end
1826
+
1827
+ def test_should_run_after_callbacks
1828
+ ran_callback = false
1829
+ @machine.after_transition { ran_callback = true }
1830
+
1831
+ @record.save!
1832
+ assert ran_callback
1833
+ end
1834
+
1835
+ def test_should_run_around_callbacks_after_yield
1836
+ ran_callback = false
1837
+ @machine.around_transition {|block| block.call; ran_callback = true }
1838
+
1839
+ @record.save!
1840
+ assert ran_callback
1841
+ end
1842
+ end
1843
+
1844
+ class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
1845
+ def setup
1846
+ @superclass = new_model do
1847
+ def persist
1848
+ create_or_update
1849
+ end
1850
+ end
1851
+ @model = Class.new(@superclass)
1852
+ @machine = EnumStateMachine::Machine.new(@model, :action => :persist)
1853
+ @machine.event :ignite do
1854
+ transition :parked => :idling
1855
+ end
1856
+
1857
+ @record = @model.new
1858
+ @record.state = 'parked'
1859
+ @record.state_event = 'ignite'
1860
+ end
1861
+
1862
+ def test_should_not_transition_on_valid?
1863
+ @record.valid?
1864
+ assert_equal 'parked', @record.state
1865
+ end
1866
+
1867
+ def test_should_not_transition_on_save
1868
+ @record.save
1869
+ assert_equal 'parked', @record.state
1870
+ end
1871
+
1872
+ def test_should_not_transition_on_save!
1873
+ @record.save!
1874
+ assert_equal 'parked', @record.state
1875
+ end
1876
+
1877
+ def test_should_transition_on_custom_action
1878
+ @record.persist
1879
+ assert_equal 'idling', @record.state
1880
+ end
1881
+ end
1882
+
1883
+ class MachineWithObserversTest < BaseTestCase
1884
+ def setup
1885
+ @model = new_model
1886
+ @machine = EnumStateMachine::Machine.new(@model)
1887
+ @machine.state :parked, :idling
1888
+ @machine.event :ignite
1889
+ @record = @model.new(:state => 'parked')
1890
+ @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
1891
+ end
1892
+
1893
+ def test_should_call_all_transition_callback_permutations
1894
+ callbacks = [
1895
+ :before_ignite_from_parked_to_idling,
1896
+ :before_ignite_from_parked,
1897
+ :before_ignite_to_idling,
1898
+ :before_ignite,
1899
+ :before_transition_state_from_parked_to_idling,
1900
+ :before_transition_state_from_parked,
1901
+ :before_transition_state_to_idling,
1902
+ :before_transition_state,
1903
+ :before_transition
1904
+ ]
1905
+
1906
+ observer = new_observer(@model) do
1907
+ callbacks.each do |callback|
1908
+ define_method(callback) do |*args|
1909
+ notifications << callback
1910
+ end
1911
+ end
1912
+ end
1913
+
1914
+ instance = observer.instance
1915
+
1916
+ @transition.perform
1917
+ assert_equal callbacks, instance.notifications
1918
+ end
1919
+
1920
+ def test_should_call_no_transition_callbacks_when_observers_disabled
1921
+ return unless ActiveRecord::VERSION::MAJOR >= 3 && ActiveRecord::VERSION::MINOR >= 1
1922
+
1923
+ callbacks = [
1924
+ :before_ignite,
1925
+ :before_transition
1926
+ ]
1927
+
1928
+ observer = new_observer(@model) do
1929
+ callbacks.each do |callback|
1930
+ define_method(callback) do |*args|
1931
+ notifications << callback
1932
+ end
1933
+ end
1934
+ end
1935
+
1936
+ instance = observer.instance
1937
+
1938
+ @model.observers.disable(observer) do
1939
+ @transition.perform
1940
+ end
1941
+
1942
+ assert_equal [], instance.notifications
1943
+ end
1944
+
1945
+ def test_should_pass_record_and_transition_to_before_callbacks
1946
+ observer = new_observer(@model) do
1947
+ def before_transition(*args)
1948
+ notifications << args
1949
+ end
1950
+ end
1951
+ instance = observer.instance
1952
+
1953
+ @transition.perform
1954
+ assert_equal [[@record, @transition]], instance.notifications
1955
+ end
1956
+
1957
+ def test_should_pass_record_and_transition_to_after_callbacks
1958
+ observer = new_observer(@model) do
1959
+ def after_transition(*args)
1960
+ notifications << args
1961
+ end
1962
+ end
1963
+ instance = observer.instance
1964
+
1965
+ @transition.perform
1966
+ assert_equal [[@record, @transition]], instance.notifications
1967
+ end
1968
+
1969
+ def test_should_call_methods_outside_the_context_of_the_record
1970
+ observer = new_observer(@model) do
1971
+ def before_ignite(*args)
1972
+ notifications << self
1973
+ end
1974
+ end
1975
+ instance = observer.instance
1976
+
1977
+ @transition.perform
1978
+ assert_equal [instance], instance.notifications
1979
+ end
1980
+
1981
+ def test_should_continue_to_handle_non_state_machine_callbacks
1982
+ observer = new_observer(@model) do
1983
+ def before_save(object)
1984
+ notifications << [:before_save, object]
1985
+ end
1986
+
1987
+ def before_ignite(*args)
1988
+ notifications << :before_ignite
1989
+ end
1990
+ end
1991
+
1992
+ instance = observer.instance
1993
+
1994
+ @transition.perform
1995
+ assert_equal [:before_ignite, [:before_save, @record]], instance.notifications
1996
+ end
1997
+
1998
+ def test_should_support_nil_from_states
1999
+ callbacks = [
2000
+ :before_ignite_from_nil_to_idling,
2001
+ :before_ignite_from_nil,
2002
+ :before_transition_state_from_nil_to_idling,
2003
+ :before_transition_state_from_nil
2004
+ ]
2005
+
2006
+ observer = new_observer(@model) do
2007
+ callbacks.each do |callback|
2008
+ define_method(callback) do |*args|
2009
+ notifications << callback
2010
+ end
2011
+ end
2012
+ end
2013
+
2014
+ instance = observer.instance
2015
+
2016
+ transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, nil, :idling)
2017
+ transition.perform
2018
+ assert_equal callbacks, instance.notifications
2019
+ end
2020
+
2021
+ def test_should_support_nil_to_states
2022
+ callbacks = [
2023
+ :before_ignite_from_parked_to_nil,
2024
+ :before_ignite_to_nil,
2025
+ :before_transition_state_from_parked_to_nil,
2026
+ :before_transition_state_to_nil
2027
+ ]
2028
+
2029
+ observer = new_observer(@model) do
2030
+ callbacks.each do |callback|
2031
+ define_method(callback) do |*args|
2032
+ notifications << callback
2033
+ end
2034
+ end
2035
+ end
2036
+
2037
+ instance = observer.instance
2038
+
2039
+ transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, nil)
2040
+ transition.perform
2041
+ assert_equal callbacks, instance.notifications
2042
+ end
2043
+ end
2044
+
2045
+ class MachineWithNamespacedObserversTest < BaseTestCase
2046
+ def setup
2047
+ @model = new_model
2048
+ @machine = EnumStateMachine::Machine.new(@model, :state, :namespace => 'alarm')
2049
+ @machine.state :active, :off
2050
+ @machine.event :enable
2051
+ @record = @model.new(:state => 'off')
2052
+ @transition = EnumStateMachine::Transition.new(@record, @machine, :enable, :off, :active)
2053
+ end
2054
+
2055
+ def test_should_call_namespaced_before_event_method
2056
+ observer = new_observer(@model) do
2057
+ def before_enable_alarm(*args)
2058
+ notifications << args
2059
+ end
2060
+ end
2061
+ instance = observer.instance
2062
+
2063
+ @transition.perform
2064
+ assert_equal [[@record, @transition]], instance.notifications
2065
+ end
2066
+
2067
+ def test_should_call_namespaced_after_event_method
2068
+ observer = new_observer(@model) do
2069
+ def after_enable_alarm(*args)
2070
+ notifications << args
2071
+ end
2072
+ end
2073
+ instance = observer.instance
2074
+
2075
+ @transition.perform
2076
+ assert_equal [[@record, @transition]], instance.notifications
2077
+ end
2078
+ end
2079
+
2080
+ class MachineWithFailureCallbacksTest < BaseTestCase
2081
+ def setup
2082
+ @model = new_model
2083
+ @machine = EnumStateMachine::Machine.new(@model)
2084
+ @machine.state :parked, :idling
2085
+ @machine.event :ignite
2086
+ @record = @model.new(:state => 'parked')
2087
+ @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
2088
+
2089
+ @notifications = []
2090
+
2091
+ # Create callbacks
2092
+ @machine.before_transition {false}
2093
+ @machine.after_failure {@notifications << :callback_after_failure}
2094
+
2095
+ # Create observer callbacks
2096
+ observer = new_observer(@model) do
2097
+ def after_failure_to_ignite(*args)
2098
+ notifications << :observer_after_failure_ignite
2099
+ end
2100
+
2101
+ def after_failure_to_transition(*args)
2102
+ notifications << :observer_after_failure_transition
2103
+ end
2104
+ end
2105
+ instance = observer.instance
2106
+ instance.notifications = @notifications
2107
+
2108
+ @transition.perform
2109
+ end
2110
+
2111
+ def test_should_invoke_callbacks_in_specific_order
2112
+ expected = [
2113
+ :callback_after_failure,
2114
+ :observer_after_failure_ignite,
2115
+ :observer_after_failure_transition
2116
+ ]
2117
+
2118
+ assert_equal expected, @notifications
2119
+ end
2120
+ end
2121
+
2122
+ class MachineWithMixedCallbacksTest < BaseTestCase
2123
+ def setup
2124
+ @model = new_model
2125
+ @machine = EnumStateMachine::Machine.new(@model)
2126
+ @machine.state :parked, :idling
2127
+ @machine.event :ignite
2128
+ @record = @model.new(:state => 'parked')
2129
+ @transition = EnumStateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
2130
+
2131
+ @notifications = []
2132
+
2133
+ # Create callbacks
2134
+ @machine.before_transition {@notifications << :callback_before_transition}
2135
+ @machine.after_transition {@notifications << :callback_after_transition}
2136
+ @machine.around_transition do |block|
2137
+ @notifications << :callback_around_before_transition
2138
+ block.call
2139
+ @notifications << :callback_arond_after_transition
2140
+ end
2141
+
2142
+ # Create observer callbacks
2143
+ observer = new_observer(@model) do
2144
+ def before_ignite(*args)
2145
+ notifications << :observer_before_ignite
2146
+ end
2147
+
2148
+ def before_transition(*args)
2149
+ notifications << :observer_before_transition
2150
+ end
2151
+
2152
+ def after_ignite(*args)
2153
+ notifications << :observer_after_ignite
2154
+ end
2155
+
2156
+ def after_transition(*args)
2157
+ notifications << :observer_after_transition
2158
+ end
2159
+ end
2160
+ instance = observer.instance
2161
+ instance.notifications = @notifications
2162
+
2163
+ @transition.perform
2164
+ end
2165
+
2166
+ def test_should_invoke_callbacks_in_specific_order
2167
+ expected = [
2168
+ :callback_before_transition,
2169
+ :callback_around_before_transition,
2170
+ :observer_before_ignite,
2171
+ :observer_before_transition,
2172
+ :callback_arond_after_transition,
2173
+ :callback_after_transition,
2174
+ :observer_after_ignite,
2175
+ :observer_after_transition
2176
+ ]
2177
+
2178
+ assert_equal expected, @notifications
2179
+ end
2180
+ end
2181
+
2182
+ if ActiveRecord.const_defined?(:NamedScope)
2183
+ class MachineWithScopesTest < BaseTestCase
2184
+ def setup
2185
+ @model = new_model
2186
+ @machine = EnumStateMachine::Machine.new(@model)
2187
+ @machine.state :parked, :first_gear
2188
+ @machine.state :idling, :value => lambda {'idling'}
2189
+ end
2190
+
2191
+ def test_should_create_singular_with_scope
2192
+ assert @model.respond_to?(:with_state)
2193
+ end
2194
+
2195
+ def test_should_only_include_records_with_state_in_singular_with_scope
2196
+ parked = @model.create :state => 'parked'
2197
+ @model.create :state => 'idling'
2198
+
2199
+ assert_equal [parked], @model.with_state(:parked).find(:all)
2200
+ end
2201
+
2202
+ def test_should_create_plural_with_scope
2203
+ assert @model.respond_to?(:with_states)
2204
+ end
2205
+
2206
+ def test_should_only_include_records_with_states_in_plural_with_scope
2207
+ parked = @model.create :state => 'parked'
2208
+ idling = @model.create :state => 'idling'
2209
+
2210
+ assert_equal [parked, idling], @model.with_states(:parked, :idling).find(:all)
2211
+ end
2212
+
2213
+ def test_should_allow_lookup_by_string_name
2214
+ parked = @model.create :state => 'parked'
2215
+ idling = @model.create :state => 'idling'
2216
+
2217
+ assert_equal [parked, idling], @model.with_states('parked', 'idling').find(:all)
2218
+ end
2219
+
2220
+ def test_should_create_singular_without_scope
2221
+ assert @model.respond_to?(:without_state)
2222
+ end
2223
+
2224
+ def test_should_only_include_records_without_state_in_singular_without_scope
2225
+ parked = @model.create :state => 'parked'
2226
+ idling = @model.create :state => 'idling'
2227
+
2228
+ assert_equal [parked], @model.without_state(:idling).find(:all)
2229
+ end
2230
+
2231
+ def test_should_create_plural_without_scope
2232
+ assert @model.respond_to?(:without_states)
2233
+ end
2234
+
2235
+ def test_should_only_include_records_without_states_in_plural_without_scope
2236
+ parked = @model.create :state => 'parked'
2237
+ idling = @model.create :state => 'idling'
2238
+ first_gear = @model.create :state => 'first_gear'
2239
+
2240
+ assert_equal [parked, idling], @model.without_states(:first_gear).find(:all)
2241
+ end
2242
+
2243
+ def test_should_allow_chaining_scopes
2244
+ parked = @model.create :state => 'parked'
2245
+ idling = @model.create :state => 'idling'
2246
+
2247
+ assert_equal [idling], @model.without_state(:parked).with_state(:idling).find(:all)
2248
+ end
2249
+ end
2250
+
2251
+ class MachineWithScopesAndOwnerSubclassTest < BaseTestCase
2252
+ def setup
2253
+ @model = new_model
2254
+ @machine = EnumStateMachine::Machine.new(@model, :state)
2255
+
2256
+ @subclass = Class.new(@model)
2257
+ @subclass_machine = @subclass.state_machine(:state) {}
2258
+ @subclass_machine.state :parked, :idling, :first_gear
2259
+ end
2260
+
2261
+ def test_should_only_include_records_with_subclass_states_in_with_scope
2262
+ parked = @subclass.create :state => 'parked'
2263
+ idling = @subclass.create :state => 'idling'
2264
+
2265
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling).find(:all)
2266
+ end
2267
+
2268
+ def test_should_only_include_records_without_subclass_states_in_without_scope
2269
+ parked = @subclass.create :state => 'parked'
2270
+ idling = @subclass.create :state => 'idling'
2271
+ first_gear = @subclass.create :state => 'first_gear'
2272
+
2273
+ assert_equal [parked, idling], @subclass.without_states(:first_gear).find(:all)
2274
+ end
2275
+ end
2276
+
2277
+ class MachineWithComplexPluralizationScopesTest < BaseTestCase
2278
+ def setup
2279
+ @model = new_model
2280
+ @machine = EnumStateMachine::Machine.new(@model, :status)
2281
+ end
2282
+
2283
+ def test_should_create_singular_with_scope
2284
+ assert @model.respond_to?(:with_status)
2285
+ end
2286
+
2287
+ def test_should_create_plural_with_scope
2288
+ assert @model.respond_to?(:with_statuses)
2289
+ end
2290
+ end
2291
+
2292
+ class MachineWithScopesAndJoinsTest < BaseTestCase
2293
+ def setup
2294
+ @company = new_model(:company)
2295
+ ActiveRecordTest.const_set('Company', @company)
2296
+
2297
+ @vehicle = new_model(:vehicle) do
2298
+ connection.add_column table_name, :company_id, :integer
2299
+ belongs_to :company, :class_name => 'ActiveRecordTest::Company'
2300
+ end
2301
+ ActiveRecordTest.const_set('Vehicle', @vehicle)
2302
+
2303
+ @company_machine = EnumStateMachine::Machine.new(@company, :initial => :active)
2304
+ @vehicle_machine = EnumStateMachine::Machine.new(@vehicle, :initial => :parked)
2305
+ @vehicle_machine.state :idling
2306
+
2307
+ @ford = @company.create
2308
+ @mustang = @vehicle.create(:company => @ford)
2309
+ end
2310
+
2311
+ def test_should_find_records_in_with_scope
2312
+ assert_equal [@mustang], @vehicle.with_states(:parked).find(:all, :joins => :company, :conditions => "#{@company.table_name}.state = \"active\"")
2313
+ end
2314
+
2315
+ def test_should_find_records_in_without_scope
2316
+ assert_equal [@mustang], @vehicle.without_states(:idling).find(:all, :joins => :company, :conditions => "#{@company.table_name}.state = \"active\"")
2317
+ end
2318
+
2319
+ def teardown
2320
+ ActiveRecordTest.class_eval do
2321
+ remove_const('Vehicle')
2322
+ remove_const('Company')
2323
+ end
2324
+ ActiveSupport::Dependencies.clear if defined?(ActiveSupport::Dependencies)
2325
+ super
2326
+ end
2327
+ end
2328
+ else
2329
+ $stderr.puts 'Skipping ActiveRecord Scope tests.'
2330
+ end
2331
+
2332
+ if ActiveRecord.const_defined?(:Relation)
2333
+ class MachineWithDefaultScope < BaseTestCase
2334
+ def setup
2335
+ @model = new_model
2336
+ @machine = EnumStateMachine::Machine.new(@model, :initial => :parked)
2337
+ @machine.state :idling
2338
+
2339
+ @model.class_eval do
2340
+ default_scope { with_state(:parked, :idling) }
2341
+ end
2342
+ end
2343
+
2344
+ def test_should_set_initial_state_on_created_object
2345
+ object = @model.new
2346
+ assert_equal 'parked', object.state
2347
+ end
2348
+ end
2349
+ else
2350
+ $stderr.puts 'Skipping ActiveRecord Default Scope tests.'
2351
+ end
2352
+
2353
+ if Object.const_defined?(:I18n)
2354
+ class MachineWithInternationalizationTest < BaseTestCase
2355
+ def setup
2356
+ I18n.backend = I18n::Backend::Simple.new
2357
+
2358
+ # Initialize the backend
2359
+ EnumStateMachine::Machine.new(new_model)
2360
+ I18n.backend.translate(:en, 'activerecord.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling')
2361
+
2362
+ @model = new_model
2363
+ end
2364
+
2365
+ def test_should_use_defaults
2366
+ I18n.backend.store_translations(:en, {
2367
+ :activerecord => {:errors => {:messages => {:invalid_transition => "cannot #{interpolation_key('event')}"}}}
2368
+ })
2369
+
2370
+ machine = EnumStateMachine::Machine.new(@model)
2371
+ machine.state :parked, :idling
2372
+ machine.event :ignite
2373
+
2374
+ record = @model.new(:state => 'idling')
2375
+
2376
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
2377
+ assert_equal ['State cannot ignite'], record.errors.full_messages
2378
+ end
2379
+
2380
+ def test_should_allow_customized_error_key
2381
+ I18n.backend.store_translations(:en, {
2382
+ :activerecord => {:errors => {:messages => {:bad_transition => "cannot #{interpolation_key('event')}"}}}
2383
+ })
2384
+
2385
+ machine = EnumStateMachine::Machine.new(@model, :messages => {:invalid_transition => :bad_transition})
2386
+ machine.state :parked, :idling
2387
+
2388
+ record = @model.new(:state => 'idling')
2389
+
2390
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
2391
+ assert_equal ['State cannot ignite'], record.errors.full_messages
2392
+ end
2393
+
2394
+ def test_should_allow_customized_error_string
2395
+ machine = EnumStateMachine::Machine.new(@model, :messages => {:invalid_transition => "cannot #{interpolation_key('event')}"})
2396
+ machine.state :parked, :idling
2397
+
2398
+ record = @model.new(:state => 'idling')
2399
+
2400
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
2401
+ assert_equal ['State cannot ignite'], record.errors.full_messages
2402
+ end
2403
+
2404
+ def test_should_allow_customized_state_key_scoped_to_class_and_machine
2405
+ I18n.backend.store_translations(:en, {
2406
+ :activerecord => {:state_machines => {:'active_record_test/foo' => {:state => {:states => {:parked => 'shutdown'}}}}}
2407
+ })
2408
+
2409
+ machine = EnumStateMachine::Machine.new(@model)
2410
+ machine.state :parked
2411
+
2412
+ assert_equal 'shutdown', machine.state(:parked).human_name
2413
+ end
2414
+
2415
+ def test_should_allow_customized_state_key_scoped_to_class
2416
+ I18n.backend.store_translations(:en, {
2417
+ :activerecord => {:state_machines => {:'active_record_test/foo' => {:states => {:parked => 'shutdown'}}}}
2418
+ })
2419
+
2420
+ machine = EnumStateMachine::Machine.new(@model)
2421
+ machine.state :parked
2422
+
2423
+ assert_equal 'shutdown', machine.state(:parked).human_name
2424
+ end
2425
+
2426
+ def test_should_allow_customized_state_key_scoped_to_machine
2427
+ I18n.backend.store_translations(:en, {
2428
+ :activerecord => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}}
2429
+ })
2430
+
2431
+ machine = EnumStateMachine::Machine.new(@model)
2432
+ machine.state :parked
2433
+
2434
+ assert_equal 'shutdown', machine.state(:parked).human_name
2435
+ end
2436
+
2437
+ def test_should_allow_customized_state_key_unscoped
2438
+ I18n.backend.store_translations(:en, {
2439
+ :activerecord => {:state_machines => {:states => {:parked => 'shutdown'}}}
2440
+ })
2441
+
2442
+ machine = EnumStateMachine::Machine.new(@model)
2443
+ machine.state :parked
2444
+
2445
+ assert_equal 'shutdown', machine.state(:parked).human_name
2446
+ end
2447
+
2448
+ def test_should_support_nil_state_key
2449
+ I18n.backend.store_translations(:en, {
2450
+ :activerecord => {:state_machines => {:states => {:nil => 'empty'}}}
2451
+ })
2452
+
2453
+ machine = EnumStateMachine::Machine.new(@model)
2454
+
2455
+ assert_equal 'empty', machine.state(nil).human_name
2456
+ end
2457
+
2458
+ def test_should_allow_customized_event_key_scoped_to_class_and_machine
2459
+ I18n.backend.store_translations(:en, {
2460
+ :activerecord => {:state_machines => {:'active_record_test/foo' => {:state => {:events => {:park => 'stop'}}}}}
2461
+ })
2462
+
2463
+ machine = EnumStateMachine::Machine.new(@model)
2464
+ machine.event :park
2465
+
2466
+ assert_equal 'stop', machine.event(:park).human_name
2467
+ end
2468
+
2469
+ def test_should_allow_customized_event_key_scoped_to_class
2470
+ I18n.backend.store_translations(:en, {
2471
+ :activerecord => {:state_machines => {:'active_record_test/foo' => {:events => {:park => 'stop'}}}}
2472
+ })
2473
+
2474
+ machine = EnumStateMachine::Machine.new(@model)
2475
+ machine.event :park
2476
+
2477
+ assert_equal 'stop', machine.event(:park).human_name
2478
+ end
2479
+
2480
+ def test_should_allow_customized_event_key_scoped_to_machine
2481
+ I18n.backend.store_translations(:en, {
2482
+ :activerecord => {:state_machines => {:state => {:events => {:park => 'stop'}}}}
2483
+ })
2484
+
2485
+ machine = EnumStateMachine::Machine.new(@model)
2486
+ machine.event :park
2487
+
2488
+ assert_equal 'stop', machine.event(:park).human_name
2489
+ end
2490
+
2491
+ def test_should_allow_customized_event_key_unscoped
2492
+ I18n.backend.store_translations(:en, {
2493
+ :activerecord => {:state_machines => {:events => {:park => 'stop'}}}
2494
+ })
2495
+
2496
+ machine = EnumStateMachine::Machine.new(@model)
2497
+ machine.event :park
2498
+
2499
+ assert_equal 'stop', machine.event(:park).human_name
2500
+ end
2501
+
2502
+ def test_should_only_add_locale_once_in_load_path
2503
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{active_record/locale\.rb$}}.length
2504
+
2505
+ # Create another ActiveRecord model that will triger the i18n feature
2506
+ new_model
2507
+
2508
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{active_record/locale\.rb$}}.length
2509
+ end
2510
+
2511
+ def test_should_add_locale_to_beginning_of_load_path
2512
+ @original_load_path = I18n.load_path
2513
+ I18n.backend = I18n::Backend::Simple.new
2514
+
2515
+ app_locale = File.dirname(__FILE__) + '/../../files/en.yml'
2516
+ default_locale = File.dirname(__FILE__) + '/../../../lib/state_machine/integrations/active_record/locale.rb'
2517
+ I18n.load_path = [app_locale]
2518
+
2519
+ EnumStateMachine::Machine.new(@model)
2520
+
2521
+ assert_equal [default_locale, app_locale].map {|path| File.expand_path(path)}, I18n.load_path.map {|path| File.expand_path(path)}
2522
+ ensure
2523
+ I18n.load_path = @original_load_path
2524
+ end
2525
+
2526
+ def test_should_prefer_other_locales_first
2527
+ @original_load_path = I18n.load_path
2528
+ I18n.backend = I18n::Backend::Simple.new
2529
+ I18n.load_path = [File.dirname(__FILE__) + '/../../files/en.yml']
2530
+
2531
+ machine = EnumStateMachine::Machine.new(@model)
2532
+ machine.state :parked, :idling
2533
+ machine.event :ignite
2534
+
2535
+ record = @model.new(:state => 'idling')
2536
+
2537
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
2538
+ assert_equal ['State cannot transition'], record.errors.full_messages
2539
+ ensure
2540
+ I18n.load_path = @original_load_path
2541
+ end
2542
+
2543
+ private
2544
+ def interpolation_key(key)
2545
+ !defined?(I18n::VERSION) || I18n::VERSION < '0.4.0' ? "{{#{key}}}" : "%{#{key}}"
2546
+ end
2547
+ end
2548
+ else
2549
+ $stderr.puts 'Skipping ActiveRecord I18n tests.'
2550
+ end
2551
+ end