joelind-state_machine 0.8.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 (78) hide show
  1. data/CHANGELOG.rdoc +297 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +466 -0
  4. data/Rakefile +98 -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 +388 -0
  28. data/lib/state_machine/assertions.rb +36 -0
  29. data/lib/state_machine/callback.rb +189 -0
  30. data/lib/state_machine/condition_proxy.rb +94 -0
  31. data/lib/state_machine/eval_helpers.rb +67 -0
  32. data/lib/state_machine/event.rb +252 -0
  33. data/lib/state_machine/event_collection.rb +122 -0
  34. data/lib/state_machine/extensions.rb +149 -0
  35. data/lib/state_machine/guard.rb +230 -0
  36. data/lib/state_machine/integrations.rb +68 -0
  37. data/lib/state_machine/integrations/active_record.rb +492 -0
  38. data/lib/state_machine/integrations/active_record/locale.rb +11 -0
  39. data/lib/state_machine/integrations/active_record/observer.rb +41 -0
  40. data/lib/state_machine/integrations/data_mapper.rb +351 -0
  41. data/lib/state_machine/integrations/data_mapper/observer.rb +139 -0
  42. data/lib/state_machine/integrations/sequel.rb +322 -0
  43. data/lib/state_machine/machine.rb +1467 -0
  44. data/lib/state_machine/machine_collection.rb +155 -0
  45. data/lib/state_machine/matcher.rb +123 -0
  46. data/lib/state_machine/matcher_helpers.rb +54 -0
  47. data/lib/state_machine/node_collection.rb +152 -0
  48. data/lib/state_machine/state.rb +249 -0
  49. data/lib/state_machine/state_collection.rb +112 -0
  50. data/lib/state_machine/transition.rb +394 -0
  51. data/tasks/state_machine.rake +1 -0
  52. data/tasks/state_machine.rb +30 -0
  53. data/test/classes/switch.rb +11 -0
  54. data/test/functional/state_machine_test.rb +941 -0
  55. data/test/test_helper.rb +4 -0
  56. data/test/unit/assertions_test.rb +40 -0
  57. data/test/unit/callback_test.rb +455 -0
  58. data/test/unit/condition_proxy_test.rb +328 -0
  59. data/test/unit/eval_helpers_test.rb +120 -0
  60. data/test/unit/event_collection_test.rb +326 -0
  61. data/test/unit/event_test.rb +743 -0
  62. data/test/unit/guard_test.rb +908 -0
  63. data/test/unit/integrations/active_record_test.rb +1374 -0
  64. data/test/unit/integrations/data_mapper_test.rb +962 -0
  65. data/test/unit/integrations/sequel_test.rb +859 -0
  66. data/test/unit/integrations_test.rb +42 -0
  67. data/test/unit/invalid_event_test.rb +7 -0
  68. data/test/unit/invalid_transition_test.rb +7 -0
  69. data/test/unit/machine_collection_test.rb +938 -0
  70. data/test/unit/machine_test.rb +2004 -0
  71. data/test/unit/matcher_helpers_test.rb +37 -0
  72. data/test/unit/matcher_test.rb +155 -0
  73. data/test/unit/node_collection_test.rb +207 -0
  74. data/test/unit/state_collection_test.rb +280 -0
  75. data/test/unit/state_machine_test.rb +31 -0
  76. data/test/unit/state_test.rb +795 -0
  77. data/test/unit/transition_test.rb +1212 -0
  78. metadata +163 -0
@@ -0,0 +1,1374 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
2
+
3
+ begin
4
+ # Load library
5
+ require 'rubygems'
6
+
7
+ gem 'activerecord', ENV['AR_VERSION'] ? "=#{ENV['AR_VERSION']}" : '>=2.1.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
+ require 'active_record/test_case'
16
+
17
+ # Establish database connection
18
+ ActiveRecord::Base.establish_connection({'adapter' => 'sqlite3', 'database' => ':memory:'})
19
+ ActiveRecord::Base.logger = Logger.new("#{File.dirname(__FILE__)}/../../active_record.log")
20
+
21
+ # Add model/observer creation helpers
22
+ ActiveRecord::TestCase.class_eval do
23
+ # Creates a new ActiveRecord model (and the associated table)
24
+ def new_model(create_table = true, &block)
25
+ model = Class.new(ActiveRecord::Base) do
26
+ connection.create_table(:foo, :force => true) {|t| t.string(:state)} if create_table
27
+ set_table_name('foo')
28
+
29
+ def self.name; 'ActiveRecordTest::Foo'; end
30
+ end
31
+ model.class_eval(&block) if block_given?
32
+ model
33
+ end
34
+
35
+ # Creates a new ActiveRecord observer
36
+ def new_observer(model, &block)
37
+ observer = Class.new(ActiveRecord::Observer) do
38
+ attr_accessor :notifications
39
+
40
+ def initialize
41
+ super
42
+ @notifications = []
43
+ end
44
+ end
45
+ observer.observe(model)
46
+ observer.class_eval(&block) if block_given?
47
+ observer
48
+ end
49
+ end
50
+
51
+ module ActiveRecordTest
52
+ class IntegrationTest < ActiveRecord::TestCase
53
+ def test_should_match_if_class_inherits_from_active_record
54
+ assert StateMachine::Integrations::ActiveRecord.matches?(new_model)
55
+ end
56
+
57
+ def test_should_not_match_if_class_does_not_inherit_from_active_record
58
+ assert !StateMachine::Integrations::ActiveRecord.matches?(Class.new)
59
+ end
60
+ end
61
+
62
+ class MachineByDefaultTest < ActiveRecord::TestCase
63
+ def setup
64
+ @model = new_model
65
+ @machine = StateMachine::Machine.new(@model)
66
+ end
67
+
68
+ def test_should_use_save_as_action
69
+ assert_equal :save, @machine.action
70
+ end
71
+
72
+ def test_should_use_transactions
73
+ assert_equal true, @machine.use_transactions
74
+ end
75
+
76
+ def test_should_create_notifier_before_callback
77
+ assert_equal 1, @machine.callbacks[:before].size
78
+ end
79
+
80
+ def test_should_create_notifier_after_callback
81
+ assert_equal 1, @machine.callbacks[:after].size
82
+ end
83
+ end
84
+
85
+ class MachineTest < ActiveRecord::TestCase
86
+ def setup
87
+ @model = new_model
88
+ @machine = StateMachine::Machine.new(@model)
89
+ @machine.state :parked, :first_gear
90
+ @machine.state :idling, :value => lambda {'idling'}
91
+ end
92
+
93
+ def test_should_create_singular_with_scope
94
+ assert @model.respond_to?(:with_state)
95
+ end
96
+
97
+ def test_should_only_include_records_with_state_in_singular_with_scope
98
+ parked = @model.create :state => 'parked'
99
+ idling = @model.create :state => 'idling'
100
+
101
+ assert_equal [parked], @model.with_state(:parked)
102
+ end
103
+
104
+ def test_should_create_plural_with_scope
105
+ assert @model.respond_to?(:with_states)
106
+ end
107
+
108
+ def test_should_only_include_records_with_states_in_plural_with_scope
109
+ parked = @model.create :state => 'parked'
110
+ idling = @model.create :state => 'idling'
111
+
112
+ assert_equal [parked, idling], @model.with_states(:parked, :idling)
113
+ end
114
+
115
+ def test_should_create_singular_without_scope
116
+ assert @model.respond_to?(:without_state)
117
+ end
118
+
119
+ def test_should_only_include_records_without_state_in_singular_without_scope
120
+ parked = @model.create :state => 'parked'
121
+ idling = @model.create :state => 'idling'
122
+
123
+ assert_equal [parked], @model.without_state(:idling)
124
+ end
125
+
126
+ def test_should_create_plural_without_scope
127
+ assert @model.respond_to?(:without_states)
128
+ end
129
+
130
+ def test_should_only_include_records_without_states_in_plural_without_scope
131
+ parked = @model.create :state => 'parked'
132
+ idling = @model.create :state => 'idling'
133
+ first_gear = @model.create :state => 'first_gear'
134
+
135
+ assert_equal [parked, idling], @model.without_states(:first_gear)
136
+ end
137
+
138
+ def test_should_allow_chaining_scopes
139
+ parked = @model.create :state => 'parked'
140
+ idling = @model.create :state => 'idling'
141
+
142
+ assert_equal [idling], @model.without_state(:parked).with_state(:idling)
143
+ end
144
+
145
+ def test_should_rollback_transaction_if_false
146
+ @machine.within_transaction(@model.new) do
147
+ @model.create
148
+ false
149
+ end
150
+
151
+ assert_equal 0, @model.count
152
+ end
153
+
154
+ def test_should_not_rollback_transaction_if_true
155
+ @machine.within_transaction(@model.new) do
156
+ @model.create
157
+ true
158
+ end
159
+
160
+ assert_equal 1, @model.count
161
+ end
162
+
163
+ def test_should_invalidate_using_errors
164
+ I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:I18n)
165
+
166
+ record = @model.new
167
+ record.state = 'parked'
168
+
169
+ @machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
170
+
171
+ assert_equal ['State cannot transition via "park"'], record.errors.full_messages
172
+ end
173
+
174
+ def test_should_auto_prefix_custom_attributes_on_invalidation
175
+ record = @model.new
176
+ @machine.invalidate(record, :event, :invalid)
177
+
178
+ assert_equal ['State event is invalid'], record.errors.full_messages
179
+ end
180
+
181
+ def test_should_clear_errors_on_reset
182
+ record = @model.new
183
+ record.state = 'parked'
184
+ record.errors.add(:state, 'is invalid')
185
+
186
+ @machine.reset(record)
187
+ assert_equal [], record.errors.full_messages
188
+ end
189
+
190
+ def test_should_not_override_the_column_reader
191
+ record = @model.new
192
+ record[:state] = 'parked'
193
+ assert_equal 'parked', record.state
194
+ end
195
+
196
+ def test_should_not_override_the_column_writer
197
+ record = @model.new
198
+ record.state = 'parked'
199
+ assert_equal 'parked', record[:state]
200
+ end
201
+ end
202
+
203
+ class MachineWithoutDatabaseTest < ActiveRecord::TestCase
204
+ def setup
205
+ @model = new_model(false) do
206
+ # Simulate the database not being available entirely
207
+ def self.connection
208
+ raise ActiveRecord::ConnectionNotEstablished
209
+ end
210
+ end
211
+ end
212
+
213
+ def test_should_allow_machine_creation
214
+ assert_nothing_raised { StateMachine::Machine.new(@model) }
215
+ end
216
+ end
217
+
218
+ class MachineUnmigratedTest < ActiveRecord::TestCase
219
+ def setup
220
+ @model = new_model(false)
221
+
222
+ # Drop the table so that it definitely doesn't exist
223
+ @model.connection.drop_table(:foo) if @model.connection.table_exists?(:foo)
224
+ end
225
+
226
+ def test_should_allow_machine_creation
227
+ assert_nothing_raised { StateMachine::Machine.new(@model) }
228
+ end
229
+ end
230
+
231
+ class MachineWithStaticInitialStateTest < ActiveRecord::TestCase
232
+ def setup
233
+ @model = new_model do
234
+ attr_accessor :value
235
+ end
236
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
237
+ end
238
+
239
+ def test_should_set_initial_state_on_created_object
240
+ record = @model.new
241
+ assert_equal 'parked', record.state
242
+ end
243
+
244
+ def test_should_set_initial_state_with_nil_attributes
245
+ record = @model.new(nil)
246
+ assert_equal 'parked', record.state
247
+ end
248
+
249
+ def test_should_still_set_attributes
250
+ record = @model.new(:value => 1)
251
+ assert_equal 1, record.value
252
+ end
253
+
254
+ def test_should_still_allow_initialize_blocks
255
+ block_args = nil
256
+ record = @model.new do |*args|
257
+ block_args = args
258
+ end
259
+
260
+ assert_equal [record], block_args
261
+ end
262
+
263
+ def test_should_set_attributes_prior_to_after_initialize_hook
264
+ state = nil
265
+ @model.class_eval do
266
+ define_method(:after_initialize) do
267
+ state = self.state
268
+ end
269
+ end
270
+ @model.new
271
+ assert_equal 'parked', state
272
+ end
273
+
274
+ def test_should_set_initial_state_before_setting_attributes
275
+ @model.class_eval do
276
+ attr_accessor :state_during_setter
277
+
278
+ define_method(:value=) do |value|
279
+ self.state_during_setter = state
280
+ end
281
+ end
282
+
283
+ record = @model.new(:value => 1)
284
+ assert_equal 'parked', record.state_during_setter
285
+ end
286
+
287
+ def test_should_not_set_initial_state_after_already_initialized
288
+ record = @model.new(:value => 1)
289
+ assert_equal 'parked', record.state
290
+
291
+ record.state = 'idling'
292
+ record.attributes = {}
293
+ assert_equal 'idling', record.state
294
+ end
295
+ end
296
+
297
+ class MachineWithDynamicInitialStateTest < ActiveRecord::TestCase
298
+ def setup
299
+ @model = new_model do
300
+ attr_accessor :value
301
+ end
302
+ @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked})
303
+ @machine.state :parked
304
+ end
305
+
306
+ def test_should_set_initial_state_on_created_object
307
+ record = @model.new
308
+ assert_equal 'parked', record.state
309
+ end
310
+
311
+ def test_should_still_set_attributes
312
+ record = @model.new(:value => 1)
313
+ assert_equal 1, record.value
314
+ end
315
+
316
+ def test_should_still_allow_initialize_blocks
317
+ block_args = nil
318
+ record = @model.new do |*args|
319
+ block_args = args
320
+ end
321
+
322
+ assert_equal [record], block_args
323
+ end
324
+
325
+ def test_should_set_attributes_prior_to_after_initialize_hook
326
+ state = nil
327
+ @model.class_eval do
328
+ define_method(:after_initialize) do
329
+ state = self.state
330
+ end
331
+ end
332
+ @model.new
333
+ assert_equal 'parked', state
334
+ end
335
+
336
+ def test_should_set_initial_state_after_setting_attributes
337
+ @model.class_eval do
338
+ attr_accessor :state_during_setter
339
+
340
+ define_method(:value=) do |value|
341
+ self.state_during_setter = state || 'nil'
342
+ end
343
+ end
344
+
345
+ record = @model.new(:value => 1)
346
+ assert_equal 'nil', record.state_during_setter
347
+ end
348
+
349
+ def test_should_not_set_initial_state_after_already_initialized
350
+ record = @model.new(:value => 1)
351
+ assert_equal 'parked', record.state
352
+
353
+ record.state = 'idling'
354
+ record.attributes = {}
355
+ assert_equal 'idling', record.state
356
+ end
357
+ end
358
+
359
+ class MachineWithColumnDefaultTest < ActiveRecord::TestCase
360
+ def setup
361
+ @model = new_model do
362
+ connection.change_table(:foo) {|t| t.string(:status, :default => 'idling')}
363
+ end
364
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
365
+ @record = @model.new
366
+ end
367
+
368
+ def test_should_use_machine_default
369
+ assert_equal 'parked', @record.status
370
+ end
371
+ end
372
+
373
+ class MachineWithConflictingPredicateTest < ActiveRecord::TestCase
374
+ def setup
375
+ @model = new_model do
376
+ def state?(*args)
377
+ true
378
+ end
379
+ end
380
+
381
+ @machine = StateMachine::Machine.new(@model)
382
+ @record = @model.new
383
+ end
384
+
385
+ def test_should_not_define_attribute_predicate
386
+ assert @record.state?
387
+ end
388
+ end
389
+
390
+ class MachineWithColumnStateAttributeTest < ActiveRecord::TestCase
391
+ def setup
392
+ @model = new_model
393
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
394
+ @machine.other_states(:idling)
395
+
396
+ @record = @model.new
397
+ end
398
+
399
+ def test_should_have_an_attribute_predicate
400
+ assert @record.respond_to?(:state?)
401
+ end
402
+
403
+ def test_should_test_for_existence_on_predicate_without_parameters
404
+ assert @record.state?
405
+
406
+ @record.state = nil
407
+ assert !@record.state?
408
+ end
409
+
410
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
411
+ assert !@record.state?(:idling)
412
+ end
413
+
414
+ def test_should_return_true_for_predicate_if_matches_current_value
415
+ assert @record.state?(:parked)
416
+ end
417
+
418
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
419
+ assert_raise(IndexError) { @record.state?(:invalid) }
420
+ end
421
+ end
422
+
423
+ class MachineWithNonColumnStateAttributeUndefinedTest < ActiveRecord::TestCase
424
+ def setup
425
+ @model = new_model do
426
+ def initialize
427
+ # Skip attribute initialization
428
+ @initialized_state_machines = true
429
+ super
430
+ end
431
+ end
432
+
433
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
434
+ @machine.other_states(:idling)
435
+ @record = @model.new
436
+ end
437
+
438
+ def test_should_not_define_a_reader_attribute_for_the_attribute
439
+ assert !@record.respond_to?(:status)
440
+ end
441
+
442
+ def test_should_not_define_a_writer_attribute_for_the_attribute
443
+ assert !@record.respond_to?(:status=)
444
+ end
445
+
446
+ def test_should_define_an_attribute_predicate
447
+ assert @record.respond_to?(:status?)
448
+ end
449
+
450
+ def test_should_raise_exception_on_predicate_without_parameters
451
+ old_verbose, $VERBOSE = $VERBOSE, nil
452
+ assert_raise(NoMethodError) { @record.status? }
453
+ ensure
454
+ $VERBOSE = old_verbose
455
+ end
456
+ end
457
+
458
+ class MachineWithNonColumnStateAttributeDefinedTest < ActiveRecord::TestCase
459
+ def setup
460
+ @model = new_model do
461
+ attr_accessor :status
462
+ end
463
+
464
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
465
+ @machine.other_states(:idling)
466
+ @record = @model.new
467
+ end
468
+
469
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
470
+ assert !@record.status?(:idling)
471
+ end
472
+
473
+ def test_should_return_true_for_predicate_if_matches_current_value
474
+ assert @record.status?(:parked)
475
+ end
476
+
477
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
478
+ assert_raise(IndexError) { @record.status?(:invalid) }
479
+ end
480
+
481
+ def test_should_set_initial_state_on_created_object
482
+ assert_equal 'parked', @record.status
483
+ end
484
+ end
485
+
486
+ class MachineWithComplexPluralizationTest < ActiveRecord::TestCase
487
+ def setup
488
+ @model = new_model
489
+ @machine = StateMachine::Machine.new(@model, :status)
490
+ end
491
+
492
+ def test_should_create_singular_with_scope
493
+ assert @model.respond_to?(:with_status)
494
+ end
495
+
496
+ def test_should_create_plural_with_scope
497
+ assert @model.respond_to?(:with_statuses)
498
+ end
499
+ end
500
+
501
+ class MachineWithOwnerSubclassTest < ActiveRecord::TestCase
502
+ def setup
503
+ @model = new_model
504
+ @machine = StateMachine::Machine.new(@model, :state)
505
+
506
+ @subclass = Class.new(@model)
507
+ @subclass_machine = @subclass.state_machine(:state) {}
508
+ @subclass_machine.state :parked, :idling, :first_gear
509
+ end
510
+
511
+ def test_should_only_include_records_with_subclass_states_in_with_scope
512
+ parked = @subclass.create :state => 'parked'
513
+ idling = @subclass.create :state => 'idling'
514
+
515
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling)
516
+ end
517
+
518
+ def test_should_only_include_records_without_subclass_states_in_without_scope
519
+ parked = @subclass.create :state => 'parked'
520
+ idling = @subclass.create :state => 'idling'
521
+ first_gear = @subclass.create :state => 'first_gear'
522
+
523
+ assert_equal [parked, idling], @subclass.without_states(:first_gear)
524
+ end
525
+ end
526
+
527
+ class MachineWithCustomAttributeTest < ActiveRecord::TestCase
528
+ def setup
529
+ @model = new_model do
530
+ alias_attribute :vehicle_status, :state
531
+ end
532
+
533
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
534
+ @machine.state :parked
535
+
536
+ @record = @model.new
537
+ end
538
+
539
+ def test_should_add_validation_errors_to_custom_attribute
540
+ @record.vehicle_status = 'invalid'
541
+
542
+ assert !@record.valid?
543
+ assert_equal ['Vehicle status is invalid'], @record.errors.full_messages
544
+
545
+ @record.vehicle_status = 'parked'
546
+ assert @record.valid?
547
+ end
548
+
549
+ def test_should_check_custom_attribute_for_predicate
550
+ @record.vehicle_status = nil
551
+ assert !@record.status?(:parked)
552
+
553
+ @record.vehicle_status = 'parked'
554
+ assert @record.status?(:parked)
555
+ end
556
+ end
557
+
558
+ class MachineWithInitializedStateTest < ActiveRecord::TestCase
559
+ def setup
560
+ @model = new_model
561
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
562
+ @machine.state nil, :idling
563
+ end
564
+
565
+ def test_should_allow_nil_initial_state_when_static
566
+ record = @model.new(:state => nil)
567
+ assert_nil record.state
568
+ end
569
+
570
+ def test_should_allow_nil_initial_state_when_dynamic
571
+ @machine.initial_state = lambda {:parked}
572
+ record = @model.new(:state => nil)
573
+ assert_nil record.state
574
+ end
575
+
576
+ def test_should_allow_different_initial_state_when_static
577
+ record = @model.new(:state => 'idling')
578
+ assert_equal 'idling', record.state
579
+ end
580
+
581
+ def test_should_allow_different_initial_state_when_dynamic
582
+ @machine.initial_state = lambda {:parked}
583
+ record = @model.new(:state => 'idling')
584
+ assert_equal 'idling', record.state
585
+ end
586
+
587
+ def test_should_use_default_state_if_protected
588
+ @model.class_eval do
589
+ attr_protected :state
590
+ end
591
+
592
+ record = @model.new(:state => 'idling')
593
+ assert_equal 'parked', record.state
594
+ end
595
+ end
596
+
597
+ class MachineWithCallbacksTest < ActiveRecord::TestCase
598
+ def setup
599
+ @model = new_model
600
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
601
+ @machine.other_states :idling
602
+ @machine.event :ignite
603
+
604
+ @record = @model.new(:state => 'parked')
605
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
606
+ end
607
+
608
+ def test_should_run_before_callbacks
609
+ called = false
610
+ @machine.before_transition(lambda {called = true})
611
+
612
+ @transition.perform
613
+ assert called
614
+ end
615
+
616
+ def test_should_pass_record_to_before_callbacks_with_one_argument
617
+ record = nil
618
+ @machine.before_transition(lambda {|arg| record = arg})
619
+
620
+ @transition.perform
621
+ assert_equal @record, record
622
+ end
623
+
624
+ def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments
625
+ callback_args = nil
626
+ @machine.before_transition(lambda {|*args| callback_args = args})
627
+
628
+ @transition.perform
629
+ assert_equal [@record, @transition], callback_args
630
+ end
631
+
632
+ def test_should_run_before_callbacks_outside_the_context_of_the_record
633
+ context = nil
634
+ @machine.before_transition(lambda {context = self})
635
+
636
+ @transition.perform
637
+ assert_equal self, context
638
+ end
639
+
640
+ def test_should_run_after_callbacks
641
+ called = false
642
+ @machine.after_transition(lambda {called = true})
643
+
644
+ @transition.perform
645
+ assert called
646
+ end
647
+
648
+ def test_should_pass_record_to_after_callbacks_with_one_argument
649
+ record = nil
650
+ @machine.after_transition(lambda {|arg| record = arg})
651
+
652
+ @transition.perform
653
+ assert_equal @record, record
654
+ end
655
+
656
+ def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments
657
+ callback_args = nil
658
+ @machine.after_transition(lambda {|*args| callback_args = args})
659
+
660
+ @transition.perform
661
+ assert_equal [@record, @transition], callback_args
662
+ end
663
+
664
+ def test_should_run_after_callbacks_outside_the_context_of_the_record
665
+ context = nil
666
+ @machine.after_transition(lambda {context = self})
667
+
668
+ @transition.perform
669
+ assert_equal self, context
670
+ end
671
+
672
+ def test_should_include_transition_states_in_known_states
673
+ @machine.before_transition :to => :first_gear, :do => lambda {}
674
+
675
+ assert_equal [:parked, :idling, :first_gear], @machine.states.map {|state| state.name}
676
+ end
677
+
678
+ def test_should_allow_symbolic_callbacks
679
+ callback_args = nil
680
+
681
+ klass = class << @record; self; end
682
+ klass.send(:define_method, :after_ignite) do |*args|
683
+ callback_args = args
684
+ end
685
+
686
+ @machine.before_transition(:after_ignite)
687
+
688
+ @transition.perform
689
+ assert_equal [@transition], callback_args
690
+ end
691
+
692
+ def test_should_allow_string_callbacks
693
+ class << @record
694
+ attr_reader :callback_result
695
+ end
696
+
697
+ @machine.before_transition('@callback_result = [1, 2, 3]')
698
+ @transition.perform
699
+
700
+ assert_equal [1, 2, 3], @record.callback_result
701
+ end
702
+ end
703
+
704
+ class DirtyAttributeTest < ActiveRecord::TestCase
705
+ def setup
706
+ changes_hash = nil
707
+
708
+ @model = new_model do
709
+ connection.change_table(:foo) {|t| t.datetime(:updated_at)}
710
+
711
+ define_method(:before_update) do
712
+ changes_hash = changes.dup
713
+ end
714
+ end
715
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
716
+ @machine.event :reverse
717
+ @machine.other_states :in_reverse
718
+
719
+
720
+ @record = @model.create(:updated_at => Time.now - 1)
721
+ @timestamp = @record.updated_at
722
+
723
+ @transition = StateMachine::Transition.new(@record, @machine, :reverse, :parked, :in_reverse)
724
+ @transition.perform
725
+
726
+ @changes_hash = changes_hash
727
+ end
728
+
729
+ def test_state_attribute_is_dirty
730
+ assert_equal %w(parked in_reverse), @changes_hash["state"]
731
+ end
732
+ end
733
+
734
+ class MachineWithLoopbackTest < ActiveRecord::TestCase
735
+ def setup
736
+ changed_attrs = nil
737
+
738
+ @model = new_model do
739
+ connection.change_table(:foo) {|t| t.datetime(:updated_at)}
740
+
741
+ define_method(:before_update) do
742
+ changed_attrs = changed_attributes.dup
743
+ end
744
+ end
745
+
746
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
747
+ @machine.event :park
748
+
749
+ @record = @model.create(:updated_at => Time.now - 1)
750
+ @timestamp = @record.updated_at
751
+
752
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
753
+ @transition.perform
754
+
755
+ @changed_attrs = changed_attrs
756
+ end
757
+
758
+ def test_should_include_state_in_changed_attributes
759
+ @changed_attrs.delete('updated_at')
760
+
761
+ expected = {'state' => 'parked'}
762
+ assert_equal expected, @changed_attrs
763
+ end
764
+
765
+ def test_should_update_record
766
+ assert_not_equal @timestamp, @record.updated_at
767
+ end
768
+ end
769
+
770
+ class MachineWithFailedBeforeCallbacksTest < ActiveRecord::TestCase
771
+ def setup
772
+ @before_count = 0
773
+ @after_count = 0
774
+
775
+ @model = new_model
776
+ @machine = StateMachine::Machine.new(@model)
777
+ @machine.state :parked, :idling
778
+ @machine.event :ignite
779
+ @machine.before_transition(lambda {@before_count += 1; false})
780
+ @machine.before_transition(lambda {@before_count += 1})
781
+ @machine.after_transition(lambda {@after_count += 1})
782
+
783
+ @record = @model.new(:state => 'parked')
784
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
785
+ @result = @transition.perform
786
+ end
787
+
788
+ def test_should_not_be_successful
789
+ assert !@result
790
+ end
791
+
792
+ def test_should_not_change_current_state
793
+ assert_equal 'parked', @record.state
794
+ end
795
+
796
+ def test_should_not_run_action
797
+ assert @record.new_record?
798
+ end
799
+
800
+ def test_should_not_run_further_before_callbacks
801
+ assert_equal 1, @before_count
802
+ end
803
+
804
+ def test_should_not_run_after_callbacks
805
+ assert_equal 0, @after_count
806
+ end
807
+ end
808
+
809
+ class MachineWithFailedActionTest < ActiveRecord::TestCase
810
+ def setup
811
+ @model = new_model do
812
+ validates_inclusion_of :state, :in => %w(first_gear)
813
+ end
814
+
815
+ @machine = StateMachine::Machine.new(@model)
816
+ @machine.state :parked, :idling
817
+ @machine.event :ignite
818
+
819
+ @before_transition_called = false
820
+ @after_transition_called = false
821
+ @after_transition_with_failures_called = false
822
+ @machine.before_transition(lambda {@before_transition_called = true})
823
+ @machine.after_transition(lambda {@after_transition_called = true})
824
+ @machine.after_transition(lambda {@after_transition_with_failures_called = true}, :include_failures => true)
825
+
826
+ @record = @model.new(:state => 'parked')
827
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
828
+ @result = @transition.perform
829
+ end
830
+
831
+ def test_should_not_be_successful
832
+ assert !@result
833
+ end
834
+
835
+ def test_should_not_change_current_state
836
+ assert_equal 'parked', @record.state
837
+ end
838
+
839
+ def test_should_not_save_record
840
+ assert @record.new_record?
841
+ end
842
+
843
+ def test_should_run_before_callback
844
+ assert @before_transition_called
845
+ end
846
+
847
+ def test_should_not_run_after_callback_if_not_including_failures
848
+ assert !@after_transition_called
849
+ end
850
+
851
+ def test_should_run_after_callback_if_including_failures
852
+ assert @after_transition_with_failures_called
853
+ end
854
+ end
855
+
856
+ class MachineWithValidationsTest < ActiveRecord::TestCase
857
+ def setup
858
+ @model = new_model
859
+ @machine = StateMachine::Machine.new(@model)
860
+ @machine.state :parked
861
+
862
+ @record = @model.new
863
+ end
864
+
865
+ def test_should_be_valid_if_state_is_known
866
+ @record.state = 'parked'
867
+
868
+ assert @record.valid?
869
+ end
870
+
871
+ def test_should_not_be_valid_if_state_is_unknown
872
+ @record.state = 'invalid'
873
+
874
+ assert !@record.valid?
875
+ assert_equal ['State is invalid'], @record.errors.full_messages
876
+ end
877
+ end
878
+
879
+ class MachineWithStateDrivenValidationsTest < ActiveRecord::TestCase
880
+ def setup
881
+ @model = new_model do
882
+ attr_accessor :seatbelt
883
+ end
884
+
885
+ @machine = StateMachine::Machine.new(@model)
886
+ @machine.state :first_gear, :second_gear do
887
+ validates_presence_of :seatbelt
888
+ end
889
+ @machine.other_states :parked
890
+ end
891
+
892
+ def test_should_be_valid_if_validation_fails_outside_state_scope
893
+ record = @model.new(:state => 'parked', :seatbelt => nil)
894
+ assert record.valid?
895
+ end
896
+
897
+ def test_should_be_invalid_if_validation_fails_within_state_scope
898
+ record = @model.new(:state => 'first_gear', :seatbelt => nil)
899
+ assert !record.valid?
900
+ end
901
+
902
+ def test_should_be_valid_if_validation_succeeds_within_state_scope
903
+ record = @model.new(:state => 'second_gear', :seatbelt => true)
904
+ assert record.valid?
905
+ end
906
+ end
907
+
908
+ class MachineWithFailedAfterCallbacksTest < ActiveRecord::TestCase
909
+ def setup
910
+ @after_count = 0
911
+
912
+ @model = new_model
913
+ @machine = StateMachine::Machine.new(@model)
914
+ @machine.state :parked, :idling
915
+ @machine.event :ignite
916
+ @machine.after_transition(lambda {@after_count += 1; false})
917
+ @machine.after_transition(lambda {@after_count += 1})
918
+
919
+ @record = @model.new(:state => 'parked')
920
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
921
+ @result = @transition.perform
922
+ end
923
+
924
+ def test_should_be_successful
925
+ assert @result
926
+ end
927
+
928
+ def test_should_change_current_state
929
+ assert_equal 'idling', @record.state
930
+ end
931
+
932
+ def test_should_save_record
933
+ assert !@record.new_record?
934
+ end
935
+
936
+ def test_should_not_run_further_after_callbacks
937
+ assert_equal 1, @after_count
938
+ end
939
+ end
940
+
941
+ class MachineWithEventAttributesOnValidationTest < ActiveRecord::TestCase
942
+ def setup
943
+ @model = new_model
944
+ @machine = StateMachine::Machine.new(@model)
945
+ @machine.event :ignite do
946
+ transition :parked => :idling
947
+ end
948
+
949
+ @record = @model.new
950
+ @record.state = 'parked'
951
+ @record.state_event = 'ignite'
952
+ end
953
+
954
+ def test_should_fail_if_event_is_invalid
955
+ @record.state_event = 'invalid'
956
+ assert !@record.valid?
957
+ assert_equal ['State event is invalid'], @record.errors.full_messages
958
+ end
959
+
960
+ def test_should_fail_if_event_has_no_transition
961
+ @record.state = 'idling'
962
+ assert !@record.valid?
963
+ assert_equal ['State event cannot transition when idling'], @record.errors.full_messages
964
+ end
965
+
966
+ def test_should_be_successful_if_event_has_transition
967
+ assert @record.valid?
968
+ end
969
+
970
+ def test_should_run_before_callbacks
971
+ ran_callback = false
972
+ @machine.before_transition { ran_callback = true }
973
+
974
+ @record.valid?
975
+ assert ran_callback
976
+ end
977
+
978
+ def test_should_persist_new_state
979
+ @record.valid?
980
+ assert_equal 'idling', @record.state
981
+ end
982
+
983
+ def test_should_not_run_after_callbacks
984
+ ran_callback = false
985
+ @machine.after_transition { ran_callback = true }
986
+
987
+ @record.valid?
988
+ assert !ran_callback
989
+ end
990
+
991
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
992
+ @model.class_eval do
993
+ attr_accessor :seatbelt
994
+ validates_presence_of :seatbelt
995
+ end
996
+
997
+ ran_callback = false
998
+ @machine.after_transition { ran_callback = true }
999
+
1000
+ @record.valid?
1001
+ assert !ran_callback
1002
+ end
1003
+
1004
+ def test_should_run_after_callbacks_with_failures_enabled_if_validation_fails
1005
+ @model.class_eval do
1006
+ attr_accessor :seatbelt
1007
+ validates_presence_of :seatbelt
1008
+ end
1009
+
1010
+ ran_callback = false
1011
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
1012
+
1013
+ @record.valid?
1014
+ assert ran_callback
1015
+ end
1016
+ end
1017
+
1018
+ class MachineWithEventAttributesOnSaveBangTest < ActiveRecord::TestCase
1019
+ def setup
1020
+ @model = new_model
1021
+ @machine = StateMachine::Machine.new(@model)
1022
+ @machine.event :ignite do
1023
+ transition :parked => :idling
1024
+ end
1025
+
1026
+ @record = @model.new
1027
+ @record.state = 'parked'
1028
+ @record.state_event = 'ignite'
1029
+ end
1030
+
1031
+ def test_should_fail_if_event_is_invalid
1032
+ @record.state_event = 'invalid'
1033
+ assert_raise(ActiveRecord::RecordInvalid) { @record.save! }
1034
+ end
1035
+
1036
+ def test_should_fail_if_event_has_no_transition
1037
+ @record.state = 'idling'
1038
+ assert_raise(ActiveRecord::RecordInvalid) { @record.save! }
1039
+ end
1040
+
1041
+ def test_should_be_successful_if_event_has_transition
1042
+ assert_equal true, @record.save!
1043
+ end
1044
+
1045
+ def test_should_run_before_callbacks
1046
+ ran_callback = false
1047
+ @machine.before_transition { ran_callback = true }
1048
+
1049
+ @record.save!
1050
+ assert ran_callback
1051
+ end
1052
+
1053
+ def test_should_run_before_callbacks_once
1054
+ before_count = 0
1055
+ @machine.before_transition { before_count += 1 }
1056
+
1057
+ @record.save!
1058
+ assert_equal 1, before_count
1059
+ end
1060
+
1061
+ def test_should_persist_new_state
1062
+ @record.save!
1063
+ assert_equal 'idling', @record.state
1064
+ end
1065
+
1066
+ def test_should_run_after_callbacks
1067
+ ran_callback = false
1068
+ @machine.after_transition { ran_callback = true }
1069
+
1070
+ @record.save!
1071
+ assert ran_callback
1072
+ end
1073
+
1074
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
1075
+ @model.before_create {|record| false}
1076
+
1077
+ ran_callback = false
1078
+ @machine.after_transition { ran_callback = true }
1079
+
1080
+ begin; @record.save!; rescue; end
1081
+ assert !ran_callback
1082
+ end
1083
+
1084
+ def test_should_run_after_callbacks_with_failures_enabled_if_fails
1085
+ @model.before_create {|record| false}
1086
+
1087
+ ran_callback = false
1088
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
1089
+
1090
+ begin; @record.save!; rescue; end
1091
+ assert ran_callback
1092
+ end
1093
+ end
1094
+
1095
+ class MachineWithEventAttributesOnCustomActionTest < ActiveRecord::TestCase
1096
+ def setup
1097
+ @superclass = new_model do
1098
+ def persist
1099
+ create_or_update
1100
+ end
1101
+ end
1102
+ @model = Class.new(@superclass)
1103
+ @machine = StateMachine::Machine.new(@model, :action => :persist)
1104
+ @machine.event :ignite do
1105
+ transition :parked => :idling
1106
+ end
1107
+
1108
+ @record = @model.new
1109
+ @record.state = 'parked'
1110
+ @record.state_event = 'ignite'
1111
+ end
1112
+
1113
+ def test_should_not_transition_on_valid?
1114
+ @record.valid?
1115
+ assert_equal 'parked', @record.state
1116
+ end
1117
+
1118
+ def test_should_not_transition_on_save
1119
+ @record.save
1120
+ assert_equal 'parked', @record.state
1121
+ end
1122
+
1123
+ def test_should_not_transition_on_save!
1124
+ @record.save!
1125
+ assert_equal 'parked', @record.state
1126
+ end
1127
+
1128
+ def test_should_transition_on_custom_action
1129
+ @record.persist
1130
+ assert_equal 'idling', @record.state
1131
+ end
1132
+ end
1133
+
1134
+ class MachineWithObserversTest < ActiveRecord::TestCase
1135
+ def setup
1136
+ @model = new_model
1137
+ @machine = StateMachine::Machine.new(@model)
1138
+ @machine.state :parked, :idling
1139
+ @machine.event :ignite
1140
+ @record = @model.new(:state => 'parked')
1141
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
1142
+ end
1143
+
1144
+ def test_should_call_all_transition_callback_permutations
1145
+ callbacks = [
1146
+ :before_ignite_from_parked_to_idling,
1147
+ :before_ignite_from_parked,
1148
+ :before_ignite_to_idling,
1149
+ :before_ignite,
1150
+ :before_transition_state_from_parked_to_idling,
1151
+ :before_transition_state_from_parked,
1152
+ :before_transition_state_to_idling,
1153
+ :before_transition_state,
1154
+ :before_transition
1155
+ ]
1156
+
1157
+ notified = false
1158
+ observer = new_observer(@model) do
1159
+ callbacks.each do |callback|
1160
+ define_method(callback) do |*args|
1161
+ notifications << callback
1162
+ end
1163
+ end
1164
+ end
1165
+
1166
+ instance = observer.instance
1167
+
1168
+ @transition.perform
1169
+ assert_equal callbacks, instance.notifications
1170
+ end
1171
+
1172
+ def test_should_pass_record_and_transition_to_before_callbacks
1173
+ observer = new_observer(@model) do
1174
+ def before_transition(*args)
1175
+ notifications << args
1176
+ end
1177
+ end
1178
+ instance = observer.instance
1179
+
1180
+ @transition.perform
1181
+ assert_equal [[@record, @transition]], instance.notifications
1182
+ end
1183
+
1184
+ def test_should_pass_record_and_transition_to_after_callbacks
1185
+ observer = new_observer(@model) do
1186
+ def after_transition(*args)
1187
+ notifications << args
1188
+ end
1189
+ end
1190
+ instance = observer.instance
1191
+
1192
+ @transition.perform
1193
+ assert_equal [[@record, @transition]], instance.notifications
1194
+ end
1195
+
1196
+ def test_should_call_methods_outside_the_context_of_the_record
1197
+ observer = new_observer(@model) do
1198
+ def before_ignite(*args)
1199
+ notifications << self
1200
+ end
1201
+ end
1202
+ instance = observer.instance
1203
+
1204
+ @transition.perform
1205
+ assert_equal [instance], instance.notifications
1206
+ end
1207
+ end
1208
+
1209
+ class MachineWithNamespacedObserversTest < ActiveRecord::TestCase
1210
+ def setup
1211
+ @model = new_model
1212
+ @machine = StateMachine::Machine.new(@model, :state, :namespace => 'alarm')
1213
+ @machine.state :active, :off
1214
+ @machine.event :enable
1215
+ @record = @model.new(:state => 'off')
1216
+ @transition = StateMachine::Transition.new(@record, @machine, :enable, :off, :active)
1217
+ end
1218
+
1219
+ def test_should_call_namespaced_before_event_method
1220
+ observer = new_observer(@model) do
1221
+ def before_enable_alarm(*args)
1222
+ notifications << args
1223
+ end
1224
+ end
1225
+ instance = observer.instance
1226
+
1227
+ @transition.perform
1228
+ assert_equal [[@record, @transition]], instance.notifications
1229
+ end
1230
+
1231
+ def test_should_call_namespaced_after_event_method
1232
+ observer = new_observer(@model) do
1233
+ def after_enable_alarm(*args)
1234
+ notifications << args
1235
+ end
1236
+ end
1237
+ instance = observer.instance
1238
+
1239
+ @transition.perform
1240
+ assert_equal [[@record, @transition]], instance.notifications
1241
+ end
1242
+ end
1243
+
1244
+ class MachineWithMixedCallbacksTest < ActiveRecord::TestCase
1245
+ def setup
1246
+ @model = new_model
1247
+ @machine = StateMachine::Machine.new(@model)
1248
+ @machine.state :parked, :idling
1249
+ @machine.event :ignite
1250
+ @record = @model.new(:state => 'parked')
1251
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
1252
+
1253
+ @notifications = []
1254
+
1255
+ # Create callbacks
1256
+ @machine.before_transition(lambda {@notifications << :callback_before_transition})
1257
+ @machine.after_transition(lambda {@notifications << :callback_after_transition})
1258
+
1259
+ # Create observer callbacks
1260
+ observer = new_observer(@model) do
1261
+ def before_ignite(*args)
1262
+ notifications << :observer_before_ignite
1263
+ end
1264
+
1265
+ def before_transition(*args)
1266
+ notifications << :observer_before_transition
1267
+ end
1268
+
1269
+ def after_ignite(*args)
1270
+ notifications << :observer_after_ignite
1271
+ end
1272
+
1273
+ def after_transition(*args)
1274
+ notifications << :observer_after_transition
1275
+ end
1276
+ end
1277
+ instance = observer.instance
1278
+ instance.notifications = @notifications
1279
+
1280
+ @transition.perform
1281
+ end
1282
+
1283
+ def test_should_invoke_callbacks_in_specific_order
1284
+ expected = [
1285
+ :callback_before_transition,
1286
+ :observer_before_ignite,
1287
+ :observer_before_transition,
1288
+ :callback_after_transition,
1289
+ :observer_after_ignite,
1290
+ :observer_after_transition
1291
+ ]
1292
+
1293
+ assert_equal expected, @notifications
1294
+ end
1295
+ end
1296
+
1297
+ if Object.const_defined?(:I18n)
1298
+ class MachineWithInternationalizationTest < ActiveRecord::TestCase
1299
+ def setup
1300
+ I18n.backend = I18n::Backend::Simple.new
1301
+
1302
+ # Initialize the backend
1303
+ I18n.backend.translate(:en, 'activerecord.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling')
1304
+
1305
+ @model = new_model
1306
+ end
1307
+
1308
+ def test_should_invalidate_using_i18n_default
1309
+ I18n.backend.store_translations(:en, {
1310
+ :activerecord => {
1311
+ :errors => {
1312
+ :messages => {
1313
+ :invalid_transition => 'cannot {{event}}'
1314
+ }
1315
+ }
1316
+ }
1317
+ })
1318
+
1319
+ machine = StateMachine::Machine.new(@model)
1320
+ machine.state :parked, :idling
1321
+ event = StateMachine::Event.new(machine, :ignite)
1322
+
1323
+ record = @model.new(:state => 'idling')
1324
+
1325
+ machine.invalidate(record, :state, :invalid_transition, [[:event, :ignite]])
1326
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1327
+ end
1328
+
1329
+ def test_should_invalidate_using_customized_i18n_key_if_specified
1330
+ I18n.backend.store_translations(:en, {
1331
+ :activerecord => {
1332
+ :errors => {
1333
+ :messages => {
1334
+ :bad_transition => 'cannot {{event}}'
1335
+ }
1336
+ }
1337
+ }
1338
+ })
1339
+
1340
+ machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => :bad_transition})
1341
+ machine.state :parked, :idling
1342
+
1343
+ record = @model.new(:state => 'idling')
1344
+
1345
+ machine.invalidate(record, :state, :invalid_transition, [[:event, :ignite]])
1346
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1347
+ end
1348
+
1349
+ def test_should_invalidate_using_customized_i18n_string_if_specified
1350
+ machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => 'cannot {{event}}'})
1351
+ machine.state :parked, :idling
1352
+
1353
+ record = @model.new(:state => 'idling')
1354
+
1355
+ machine.invalidate(record, :state, :invalid_transition, [[:event, :ignite]])
1356
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1357
+ end
1358
+
1359
+ def test_should_only_add_locale_once_in_load_path
1360
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{state_machine/integrations/active_record/locale\.rb$}}.length
1361
+
1362
+ # Create another ActiveRecord model that will triger the i18n feature
1363
+ new_model
1364
+
1365
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{state_machine/integrations/active_record/locale\.rb$}}.length
1366
+ end
1367
+ end
1368
+ else
1369
+ $stderr.puts 'Skipping ActiveRecord I18n tests. `gem install active_record` >= v2.2.0 and try again.'
1370
+ end
1371
+ end
1372
+ rescue LoadError
1373
+ $stderr.puts "Skipping ActiveRecord tests. `gem install activerecord#{" -v #{ENV['AR_VERSION']}" if ENV['AR_VERSION']}` and try again."
1374
+ end