hsume2-state_machine 1.0.1

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