mattscilipoti-state_machine 0.8.0.1

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