mattscilipoti-state_machine 0.8.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 (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