joelind-state_machine 0.8.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 +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