state_machine 0.9.4 → 0.10.0

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 (68) hide show
  1. data/CHANGELOG.rdoc +20 -0
  2. data/LICENSE +1 -1
  3. data/README.rdoc +74 -4
  4. data/Rakefile +3 -3
  5. data/lib/state_machine.rb +51 -24
  6. data/lib/state_machine/{guard.rb → branch.rb} +34 -40
  7. data/lib/state_machine/callback.rb +13 -18
  8. data/lib/state_machine/error.rb +13 -0
  9. data/lib/state_machine/eval_helpers.rb +3 -0
  10. data/lib/state_machine/event.rb +67 -30
  11. data/lib/state_machine/event_collection.rb +20 -3
  12. data/lib/state_machine/extensions.rb +3 -3
  13. data/lib/state_machine/integrations.rb +7 -0
  14. data/lib/state_machine/integrations/active_model.rb +149 -59
  15. data/lib/state_machine/integrations/active_model/versions.rb +30 -0
  16. data/lib/state_machine/integrations/active_record.rb +74 -148
  17. data/lib/state_machine/integrations/active_record/locale.rb +0 -7
  18. data/lib/state_machine/integrations/active_record/versions.rb +149 -0
  19. data/lib/state_machine/integrations/base.rb +64 -0
  20. data/lib/state_machine/integrations/data_mapper.rb +50 -39
  21. data/lib/state_machine/integrations/data_mapper/observer.rb +47 -12
  22. data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
  23. data/lib/state_machine/integrations/mongo_mapper.rb +37 -64
  24. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  25. data/lib/state_machine/integrations/mongo_mapper/versions.rb +102 -0
  26. data/lib/state_machine/integrations/mongoid.rb +297 -0
  27. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  28. data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
  29. data/lib/state_machine/integrations/sequel.rb +99 -55
  30. data/lib/state_machine/integrations/sequel/versions.rb +40 -0
  31. data/lib/state_machine/machine.rb +273 -136
  32. data/lib/state_machine/machine_collection.rb +21 -13
  33. data/lib/state_machine/node_collection.rb +6 -1
  34. data/lib/state_machine/path.rb +120 -0
  35. data/lib/state_machine/path_collection.rb +90 -0
  36. data/lib/state_machine/state.rb +28 -9
  37. data/lib/state_machine/state_collection.rb +1 -1
  38. data/lib/state_machine/transition.rb +65 -6
  39. data/lib/state_machine/transition_collection.rb +1 -1
  40. data/test/files/en.yml +8 -0
  41. data/test/functional/state_machine_test.rb +15 -2
  42. data/test/unit/branch_test.rb +890 -0
  43. data/test/unit/callback_test.rb +9 -36
  44. data/test/unit/error_test.rb +43 -0
  45. data/test/unit/event_collection_test.rb +67 -33
  46. data/test/unit/event_test.rb +165 -38
  47. data/test/unit/integrations/active_model_test.rb +103 -3
  48. data/test/unit/integrations/active_record_test.rb +90 -43
  49. data/test/unit/integrations/base_test.rb +87 -0
  50. data/test/unit/integrations/data_mapper_test.rb +105 -44
  51. data/test/unit/integrations/mongo_mapper_test.rb +261 -64
  52. data/test/unit/integrations/mongoid_test.rb +1529 -0
  53. data/test/unit/integrations/sequel_test.rb +33 -49
  54. data/test/unit/integrations_test.rb +4 -0
  55. data/test/unit/invalid_event_test.rb +15 -2
  56. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  57. data/test/unit/invalid_transition_test.rb +72 -2
  58. data/test/unit/machine_collection_test.rb +55 -61
  59. data/test/unit/machine_test.rb +388 -26
  60. data/test/unit/node_collection_test.rb +14 -4
  61. data/test/unit/path_collection_test.rb +266 -0
  62. data/test/unit/path_test.rb +485 -0
  63. data/test/unit/state_collection_test.rb +30 -0
  64. data/test/unit/state_test.rb +82 -35
  65. data/test/unit/transition_collection_test.rb +48 -44
  66. data/test/unit/transition_test.rb +198 -41
  67. metadata +111 -74
  68. data/test/unit/guard_test.rb +0 -909
@@ -0,0 +1,1529 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
2
+
3
+ # Load library
4
+ require 'rubygems'
5
+
6
+ gem 'mongoid', ENV['VERSION'] ? "=#{ENV['VERSION']}" : '>=2.0.0.rc.7'
7
+ require 'mongoid'
8
+
9
+ # Establish database connection
10
+ Mongoid.configure do |config|
11
+ config.master = Mongo::Connection.new('127.0.0.1', 27017, :slave_ok => true).db('test')
12
+ end
13
+
14
+ module MongoidTest
15
+ class BaseTestCase < Test::Unit::TestCase
16
+ def default_test
17
+ end
18
+
19
+ protected
20
+ # Creates a new Mongoid model (and the associated table)
21
+ def new_model(table_name = :foo, &block)
22
+
23
+ model = Class.new do
24
+ (class << self; self; end).class_eval do
25
+ define_method(:name) { "MongoidTest::#{table_name.to_s.capitalize}" }
26
+ define_method(:to_s) { name }
27
+ end
28
+ end
29
+
30
+ model.class_eval do
31
+ include Mongoid::Document
32
+ self.collection_name = table_name
33
+
34
+ field :state, :type => String
35
+ end
36
+ model.class_eval(&block) if block_given?
37
+ model.collection.remove
38
+ model
39
+ end
40
+ end
41
+
42
+ class IntegrationTest < BaseTestCase
43
+ def test_should_match_if_class_includes_mongoid
44
+ assert StateMachine::Integrations::Mongoid.matches?(new_model)
45
+ end
46
+
47
+ def test_should_not_match_if_class_does_not_include_mongoid
48
+ assert !StateMachine::Integrations::Mongoid.matches?(Class.new)
49
+ end
50
+
51
+ def test_should_have_defaults
52
+ assert_equal e = {:action => :save}, StateMachine::Integrations::Mongoid.defaults
53
+ end
54
+ end
55
+
56
+ class MachineByDefaultTest < BaseTestCase
57
+ def setup
58
+ @model = new_model
59
+ @machine = StateMachine::Machine.new(@model)
60
+ end
61
+
62
+ def test_should_use_save_as_action
63
+ assert_equal :save, @machine.action
64
+ end
65
+
66
+ def test_should_not_have_any_before_callbacks
67
+ assert_equal 0, @machine.callbacks[:before].size
68
+ end
69
+
70
+ def test_should_not_have_any_after_callbacks
71
+ assert_equal 0, @machine.callbacks[:after].size
72
+ end
73
+ end
74
+
75
+ class MachineWithStatesTest < BaseTestCase
76
+ def setup
77
+ @model = new_model
78
+ @machine = StateMachine::Machine.new(@model)
79
+ @machine.state :first_gear
80
+ end
81
+
82
+ def test_should_humanize_name
83
+ assert_equal 'first gear', @machine.state(:first_gear).human_name
84
+ end
85
+ end
86
+
87
+ class MachineWithStaticInitialStateTest < BaseTestCase
88
+ def setup
89
+ @model = new_model do
90
+ attr_accessor :value
91
+ end
92
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
93
+ end
94
+
95
+ def test_should_set_initial_state_on_created_object
96
+ record = @model.new
97
+ assert_equal 'parked', record.state
98
+ end
99
+
100
+ def test_should_set_initial_state_with_nil_attributes
101
+ record = @model.new(nil)
102
+ assert_equal 'parked', record.state
103
+ end
104
+
105
+ def test_should_still_set_attributes
106
+ record = @model.new(:value => 1)
107
+ assert_equal 1, record.value
108
+ end
109
+
110
+ def test_should_still_allow_initialize_blocks
111
+ block_args = nil
112
+ record = @model.new do |*args|
113
+ block_args = args
114
+ end
115
+
116
+ assert_equal [record], block_args
117
+ end
118
+
119
+ def test_should_set_initial_state_before_setting_attributes
120
+ @model.class_eval do
121
+ attr_accessor :state_during_setter
122
+
123
+ define_method(:value=) do |value|
124
+ self.state_during_setter = state
125
+ end
126
+ end
127
+
128
+ record = @model.new(:value => 1)
129
+ assert_equal 'parked', record.state_during_setter
130
+ end
131
+
132
+ def test_should_not_set_initial_state_after_already_initialized
133
+ record = @model.new(:value => 1)
134
+ assert_equal 'parked', record.state
135
+
136
+ record.state = 'idling'
137
+ record.process({})
138
+ assert_equal 'idling', record.state
139
+ end
140
+
141
+ def test_should_use_stored_values_when_loading_from_database
142
+ @machine.state :idling
143
+
144
+ record = @model.find(@model.create(:state => 'idling').id)
145
+ assert_equal 'idling', record.state
146
+ end
147
+
148
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
149
+ @machine.state nil
150
+
151
+ record = @model.find(@model.create(:state => nil).id)
152
+ assert_nil record.state
153
+ end
154
+ end
155
+
156
+ class MachineWithDynamicInitialStateTest < BaseTestCase
157
+ def setup
158
+ @model = new_model do
159
+ attr_accessor :value
160
+ end
161
+ @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked})
162
+ @machine.state :parked
163
+ end
164
+
165
+ def test_should_set_initial_state_on_created_object
166
+ record = @model.new
167
+ assert_equal 'parked', record.state
168
+ end
169
+
170
+ def test_should_still_set_attributes
171
+ record = @model.new(:value => 1)
172
+ assert_equal 1, record.value
173
+ end
174
+
175
+ def test_should_still_allow_initialize_blocks
176
+ block_args = nil
177
+ record = @model.new do |*args|
178
+ block_args = args
179
+ end
180
+
181
+ assert_equal [record], block_args
182
+ end
183
+
184
+ def test_should_set_initial_state_after_setting_attributes
185
+ @model.class_eval do
186
+ attr_accessor :state_during_setter
187
+
188
+ define_method(:value=) do |value|
189
+ self.state_during_setter = state || 'nil'
190
+ end
191
+ end
192
+
193
+ record = @model.new(:value => 1)
194
+ assert_equal 'nil', record.state_during_setter
195
+ end
196
+
197
+ def test_should_not_set_initial_state_after_already_initialized
198
+ record = @model.new(:value => 1)
199
+ assert_equal 'parked', record.state
200
+
201
+ record.state = 'idling'
202
+ record.process({})
203
+ assert_equal 'idling', record.state
204
+ end
205
+
206
+ def test_should_use_stored_values_when_loading_from_database
207
+ @machine.state :idling
208
+
209
+ record = @model.find(@model.create(:state => 'idling').id)
210
+ assert_equal 'idling', record.state
211
+ end
212
+
213
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
214
+ @machine.state nil
215
+
216
+ record = @model.find(@model.create(:state => nil).id)
217
+ assert_nil record.state
218
+ end
219
+ end
220
+
221
+ class MachineWithEventsTest < BaseTestCase
222
+ def setup
223
+ @model = new_model
224
+ @machine = StateMachine::Machine.new(@model)
225
+ @machine.event :shift_up
226
+ end
227
+
228
+ def test_should_humanize_name
229
+ assert_equal 'shift up', @machine.event(:shift_up).human_name
230
+ end
231
+ end
232
+
233
+ class MachineWithColumnDefaultTest < BaseTestCase
234
+ def setup
235
+ @model = new_model do
236
+ field :status, :type => String, :default => 'idling'
237
+ end
238
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
239
+ @record = @model.new
240
+ end
241
+
242
+ def test_should_use_machine_default
243
+ assert_equal 'parked', @record.status
244
+ end
245
+ end
246
+
247
+ class MachineWithConflictingPredicateTest < BaseTestCase
248
+ def setup
249
+ @model = new_model do
250
+ def state?(*args)
251
+ true
252
+ end
253
+ end
254
+
255
+ @machine = StateMachine::Machine.new(@model)
256
+ @record = @model.new
257
+ end
258
+
259
+ def test_should_not_define_attribute_predicate
260
+ assert @record.state?
261
+ end
262
+ end
263
+
264
+ class MachineWithColumnStateAttributeTest < BaseTestCase
265
+ def setup
266
+ @model = new_model
267
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
268
+ @machine.other_states(:idling)
269
+
270
+ @record = @model.new
271
+ end
272
+
273
+ def test_should_not_override_the_column_reader
274
+ @record[:state] = 'parked'
275
+ assert_equal 'parked', @record.state
276
+ end
277
+
278
+ def test_should_not_override_the_column_writer
279
+ @record.state = 'parked'
280
+ assert_equal 'parked', @record[:state]
281
+ end
282
+
283
+ def test_should_have_an_attribute_predicate
284
+ assert @record.respond_to?(:state?)
285
+ end
286
+
287
+ def test_should_test_for_existence_on_predicate_without_parameters
288
+ assert @record.state?
289
+
290
+ @record.state = nil
291
+ assert !@record.state?
292
+ end
293
+
294
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
295
+ assert !@record.state?(:idling)
296
+ end
297
+
298
+ def test_should_return_true_for_predicate_if_matches_current_value
299
+ assert @record.state?(:parked)
300
+ end
301
+
302
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
303
+ assert_raise(IndexError) { @record.state?(:invalid) }
304
+ end
305
+ end
306
+
307
+ class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
308
+ def setup
309
+ @model = new_model do
310
+ def initialize
311
+ # Skip attribute initialization
312
+ @initialized_state_machines = true
313
+ super
314
+ end
315
+ end
316
+
317
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
318
+ @machine.other_states(:idling)
319
+ @record = @model.new
320
+ end
321
+
322
+ def test_should_define_a_new_key_for_the_attribute
323
+ assert_not_nil @model.fields['status']
324
+ end
325
+
326
+ def test_should_define_a_reader_attribute_for_the_attribute
327
+ assert @record.respond_to?(:status)
328
+ end
329
+
330
+ def test_should_define_a_writer_attribute_for_the_attribute
331
+ assert @record.respond_to?(:status=)
332
+ end
333
+
334
+ def test_should_define_an_attribute_predicate
335
+ assert @record.respond_to?(:status?)
336
+ end
337
+ end
338
+
339
+ class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase
340
+ def setup
341
+ @model = new_model do
342
+ attr_accessor :status
343
+ end
344
+
345
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
346
+ @machine.other_states(:idling)
347
+ @record = @model.new
348
+ end
349
+
350
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
351
+ assert !@record.status?(:idling)
352
+ end
353
+
354
+ def test_should_return_true_for_predicate_if_matches_current_value
355
+ assert @record.status?(:parked)
356
+ end
357
+
358
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
359
+ assert_raise(IndexError) { @record.status?(:invalid) }
360
+ end
361
+
362
+ def test_should_set_initial_state_on_created_object
363
+ assert_equal 'parked', @record.status
364
+ end
365
+ end
366
+
367
+ class MachineWithAliasedAttributeTest < BaseTestCase
368
+ def setup
369
+ @model = new_model do
370
+ alias_attribute :vehicle_status, :state
371
+ end
372
+
373
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
374
+ @machine.state :parked
375
+
376
+ @record = @model.new
377
+ end
378
+
379
+ def test_should_check_custom_attribute_for_predicate
380
+ @record.vehicle_status = nil
381
+ assert !@record.status?(:parked)
382
+
383
+ @record.vehicle_status = 'parked'
384
+ assert @record.status?(:parked)
385
+ end
386
+ end
387
+
388
+ class MachineWithInitializedStateTest < BaseTestCase
389
+ def setup
390
+ @model = new_model
391
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
392
+ @machine.state nil, :idling
393
+ end
394
+
395
+ def test_should_allow_nil_initial_state_when_static
396
+ record = @model.new(:state => nil)
397
+ assert_nil record.state
398
+ end
399
+
400
+ def test_should_allow_nil_initial_state_when_dynamic
401
+ @machine.initial_state = lambda {:parked}
402
+ record = @model.new(:state => nil)
403
+ assert_nil record.state
404
+ end
405
+
406
+ def test_should_allow_different_initial_state_when_static
407
+ record = @model.new(:state => 'idling')
408
+ assert_equal 'idling', record.state
409
+ end
410
+
411
+ def test_should_allow_different_initial_state_when_dynamic
412
+ @machine.initial_state = lambda {:parked}
413
+ record = @model.new(:state => 'idling')
414
+ assert_equal 'idling', record.state
415
+ end
416
+
417
+ def test_should_use_default_state_if_protected
418
+ @model.class_eval do
419
+ attr_protected :state
420
+ end
421
+
422
+ record = @model.new(:state => 'idling')
423
+ assert_equal 'parked', record.state
424
+ end
425
+ end
426
+
427
+ class MachineWithLoopbackTest < BaseTestCase
428
+ def setup
429
+ @model = new_model do
430
+ field :updated_at, :type => Time
431
+
432
+ before_update do |record|
433
+ record.updated_at = Time.now
434
+ end
435
+ end
436
+
437
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
438
+ @machine.event :park
439
+
440
+ @record = @model.create(:updated_at => Time.now - 1)
441
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
442
+
443
+ @timestamp = @record.updated_at
444
+ @transition.perform
445
+ end
446
+
447
+ def test_should_update_record
448
+ assert_not_equal @timestamp, @record.updated_at
449
+ end
450
+ end
451
+
452
+ class MachineWithDirtyAttributesTest < BaseTestCase
453
+ def setup
454
+ @model = new_model
455
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
456
+ @machine.event :ignite
457
+ @machine.state :idling
458
+
459
+ @record = @model.create
460
+
461
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
462
+ @transition.perform(false)
463
+ end
464
+
465
+ def test_should_include_state_in_changed_attributes
466
+ assert_equal %w(state), @record.changed
467
+ end
468
+
469
+ def test_should_track_attribute_change
470
+ assert_equal %w(parked idling), @record.changes['state']
471
+ end
472
+
473
+ def test_should_not_reset_changes_on_multiple_transitions
474
+ transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
475
+ transition.perform(false)
476
+
477
+ assert_equal %w(parked idling), @record.changes['state']
478
+ end
479
+
480
+ def test_should_not_have_changes_when_loaded_from_database
481
+ record = @model.find(@record.id)
482
+ assert !record.changed?
483
+ end
484
+ end
485
+
486
+ class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase
487
+ def setup
488
+ @model = new_model
489
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
490
+ @machine.event :park
491
+
492
+ @record = @model.create
493
+
494
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
495
+ @transition.perform(false)
496
+ end
497
+
498
+ def test_should_include_state_in_changed_attributes
499
+ assert_equal %w(state), @record.changed
500
+ end
501
+
502
+ def test_should_track_attribute_changes
503
+ assert_equal %w(parked parked), @record.changes['state']
504
+ end
505
+ end
506
+
507
+ class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase
508
+ def setup
509
+ @model = new_model do
510
+ key :status, String, :default => 'idling'
511
+ end
512
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
513
+ @machine.event :ignite
514
+ @machine.state :idling
515
+
516
+ @record = @model.create
517
+
518
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
519
+ @transition.perform(false)
520
+ end
521
+
522
+ def test_should_include_state_in_changed_attributes
523
+ assert_equal %w(status), @record.changed
524
+ end
525
+
526
+ def test_should_track_attribute_change
527
+ assert_equal %w(parked idling), @record.changes['status']
528
+ end
529
+
530
+ def test_should_not_reset_changes_on_multiple_transitions
531
+ transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
532
+ transition.perform(false)
533
+
534
+ assert_equal %w(parked idling), @record.changes['status']
535
+ end
536
+ end
537
+
538
+ class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase
539
+ def setup
540
+ @model = new_model do
541
+ key :status, String, :default => 'idling'
542
+ end
543
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
544
+ @machine.event :park
545
+
546
+ @record = @model.create
547
+
548
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
549
+ @transition.perform(false)
550
+ end
551
+
552
+ def test_should_include_state_in_changed_attributes
553
+ assert_equal %w(status), @record.changed
554
+ end
555
+
556
+ def test_should_track_attribute_changes
557
+ assert_equal %w(parked parked), @record.changes['status']
558
+ end
559
+ end
560
+
561
+ class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase
562
+ def setup
563
+ @model = new_model
564
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
565
+ @machine.event :ignite
566
+
567
+ @record = @model.create
568
+ @record.state_event = 'ignite'
569
+ end
570
+
571
+ def test_should_include_state_in_changed_attributes
572
+ assert_equal %w(state), @record.changed
573
+ end
574
+
575
+ def test_should_track_attribute_change
576
+ assert_equal %w(parked parked), @record.changes['state']
577
+ end
578
+
579
+ def test_should_not_reset_changes_on_multiple_changes
580
+ @record.state_event = 'ignite'
581
+ assert_equal %w(parked parked), @record.changes['state']
582
+ end
583
+
584
+ def test_should_not_include_state_in_changed_attributes_if_nil
585
+ @record = @model.create
586
+ @record.state_event = nil
587
+
588
+ assert_equal [], @record.changed
589
+ end
590
+ end
591
+
592
+ class MachineWithCallbacksTest < BaseTestCase
593
+ def setup
594
+ @model = new_model
595
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
596
+ @machine.other_states :idling
597
+ @machine.event :ignite
598
+
599
+ @record = @model.new(:state => 'parked')
600
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
601
+ end
602
+
603
+ def test_should_run_before_callbacks
604
+ called = false
605
+ @machine.before_transition {called = true}
606
+
607
+ @transition.perform
608
+ assert called
609
+ end
610
+
611
+ def test_should_pass_record_to_before_callbacks_with_one_argument
612
+ record = nil
613
+ @machine.before_transition {|arg| record = arg}
614
+
615
+ @transition.perform
616
+ assert_equal @record, record
617
+ end
618
+
619
+ def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments
620
+ callback_args = nil
621
+ @machine.before_transition {|*args| callback_args = args}
622
+
623
+ @transition.perform
624
+ assert_equal [@record, @transition], callback_args
625
+ end
626
+
627
+ def test_should_run_before_callbacks_outside_the_context_of_the_record
628
+ context = nil
629
+ @machine.before_transition {context = self}
630
+
631
+ @transition.perform
632
+ assert_equal self, context
633
+ end
634
+
635
+ def test_should_run_after_callbacks
636
+ called = false
637
+ @machine.after_transition {called = true}
638
+
639
+ @transition.perform
640
+ assert called
641
+ end
642
+
643
+ def test_should_pass_record_to_after_callbacks_with_one_argument
644
+ record = nil
645
+ @machine.after_transition {|arg| record = arg}
646
+
647
+ @transition.perform
648
+ assert_equal @record, record
649
+ end
650
+
651
+ def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments
652
+ callback_args = nil
653
+ @machine.after_transition {|*args| callback_args = args}
654
+
655
+ @transition.perform
656
+ assert_equal [@record, @transition], callback_args
657
+ end
658
+
659
+ def test_should_run_after_callbacks_outside_the_context_of_the_record
660
+ context = nil
661
+ @machine.after_transition {context = self}
662
+
663
+ @transition.perform
664
+ assert_equal self, context
665
+ end
666
+
667
+ def test_should_run_around_callbacks
668
+ before_called = false
669
+ after_called = false
670
+ @machine.around_transition {|block| before_called = true; block.call; after_called = true}
671
+
672
+ @transition.perform
673
+ assert before_called
674
+ assert after_called
675
+ end
676
+
677
+ def test_should_include_transition_states_in_known_states
678
+ @machine.before_transition :to => :first_gear, :do => lambda {}
679
+
680
+ assert_equal [:parked, :idling, :first_gear], @machine.states.map {|state| state.name}
681
+ end
682
+
683
+ def test_should_allow_symbolic_callbacks
684
+ callback_args = nil
685
+
686
+ klass = class << @record; self; end
687
+ klass.send(:define_method, :after_ignite) do |*args|
688
+ callback_args = args
689
+ end
690
+
691
+ @machine.before_transition(:after_ignite)
692
+
693
+ @transition.perform
694
+ assert_equal [@transition], callback_args
695
+ end
696
+
697
+ def test_should_allow_string_callbacks
698
+ class << @record
699
+ attr_reader :callback_result
700
+ end
701
+
702
+ @machine.before_transition('@callback_result = [1, 2, 3]')
703
+ @transition.perform
704
+
705
+ assert_equal [1, 2, 3], @record.callback_result
706
+ end
707
+ end
708
+
709
+ class MachineWithFailedBeforeCallbacksTest < BaseTestCase
710
+ def setup
711
+ @callbacks = []
712
+
713
+ @model = new_model
714
+ @machine = StateMachine::Machine.new(@model)
715
+ @machine.state :parked, :idling
716
+ @machine.event :ignite
717
+ @machine.before_transition {@callbacks << :before_1; false}
718
+ @machine.before_transition {@callbacks << :before_2}
719
+ @machine.after_transition {@callbacks << :after}
720
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
721
+
722
+ @record = @model.new(:state => 'parked')
723
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
724
+ @result = @transition.perform
725
+ end
726
+
727
+ def test_should_not_be_successful
728
+ assert !@result
729
+ end
730
+
731
+ def test_should_not_change_current_state
732
+ assert_equal 'parked', @record.state
733
+ end
734
+
735
+ def test_should_not_run_action
736
+ assert @record.new_record?
737
+ end
738
+
739
+ def test_should_not_run_further_callbacks
740
+ assert_equal [:before_1], @callbacks
741
+ end
742
+ end
743
+
744
+ class MachineWithFailedActionTest < BaseTestCase
745
+ def setup
746
+ @model = new_model do
747
+ validates_numericality_of :state
748
+ end
749
+
750
+ @machine = StateMachine::Machine.new(@model)
751
+ @machine.state :parked, :idling
752
+ @machine.event :ignite
753
+
754
+ @callbacks = []
755
+ @machine.before_transition {@callbacks << :before}
756
+ @machine.after_transition {@callbacks << :after}
757
+ @machine.after_failure {@callbacks << :after_failure}
758
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
759
+
760
+ @record = @model.new(:state => 'parked')
761
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
762
+ @result = @transition.perform
763
+ end
764
+
765
+ def test_should_not_be_successful
766
+ assert !@result
767
+ end
768
+
769
+ def test_should_not_change_current_state
770
+ assert_equal 'parked', @record.state
771
+ end
772
+
773
+ def test_should_not_save_record
774
+ assert @record.new_record?
775
+ end
776
+
777
+ def test_should_run_before_callbacks_and_after_callbacks_with_failures
778
+ assert_equal [:before, :around_before, :after_failure], @callbacks
779
+ end
780
+ end
781
+
782
+ class MachineWithFailedAfterCallbacksTest < BaseTestCase
783
+ def setup
784
+ @callbacks = []
785
+
786
+ @model = new_model
787
+ @machine = StateMachine::Machine.new(@model)
788
+ @machine.state :parked, :idling
789
+ @machine.event :ignite
790
+ @machine.after_transition {@callbacks << :after_1; false}
791
+ @machine.after_transition {@callbacks << :after_2}
792
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
793
+
794
+ @record = @model.new(:state => 'parked')
795
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
796
+ @result = @transition.perform
797
+ end
798
+
799
+ def test_should_be_successful
800
+ assert @result
801
+ end
802
+
803
+ def test_should_change_current_state
804
+ assert_equal 'idling', @record.state
805
+ end
806
+
807
+ def test_should_save_record
808
+ assert !@record.new_record?
809
+ end
810
+
811
+ def test_should_not_run_further_after_callbacks
812
+ assert_equal [:around_before, :around_after, :after_1], @callbacks
813
+ end
814
+ end
815
+
816
+ class MachineWithValidationsTest < BaseTestCase
817
+ def setup
818
+ @model = new_model
819
+ @machine = StateMachine::Machine.new(@model)
820
+ @machine.state :parked
821
+
822
+ @record = @model.new
823
+ end
824
+
825
+ def test_should_invalidate_using_errors
826
+ I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:ActiveModel)
827
+ @record.state = 'parked'
828
+
829
+ @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']])
830
+ assert_equal ['State cannot transition via "park"'], @record.errors.full_messages
831
+ end
832
+
833
+ def test_should_auto_prefix_custom_attributes_on_invalidation
834
+ @machine.invalidate(@record, :event, :invalid)
835
+
836
+ assert_equal ['State event is invalid'], @record.errors.full_messages
837
+ end
838
+
839
+ def test_should_clear_errors_on_reset
840
+ @record.state = 'parked'
841
+ @record.errors.add(:state, 'is invalid')
842
+
843
+ @machine.reset(@record)
844
+ assert_equal [], @record.errors.full_messages
845
+ end
846
+
847
+ def test_should_be_valid_if_state_is_known
848
+ @record.state = 'parked'
849
+
850
+ assert @record.valid?
851
+ end
852
+
853
+ def test_should_not_be_valid_if_state_is_unknown
854
+ @record.state = 'invalid'
855
+
856
+ assert !@record.valid?
857
+ assert_equal ['State is invalid'], @record.errors.full_messages
858
+ end
859
+ end
860
+
861
+ class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
862
+ def setup
863
+ @model = new_model do
864
+ alias_attribute :status, :state
865
+ end
866
+
867
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :state)
868
+ @machine.state :parked
869
+
870
+ @record = @model.new
871
+ end
872
+
873
+ def test_should_add_validation_errors_to_custom_attribute
874
+ @record.state = 'invalid'
875
+
876
+ assert !@record.valid?
877
+ assert_equal ['State is invalid'], @record.errors.full_messages
878
+
879
+ @record.state = 'parked'
880
+ assert @record.valid?
881
+ end
882
+ end
883
+
884
+ class MachineWithStateDrivenValidationsTest < BaseTestCase
885
+ def setup
886
+ @model = new_model do
887
+ attr_accessor :seatbealt
888
+ end
889
+
890
+ @machine = StateMachine::Machine.new(@model)
891
+ @machine.state :first_gear do
892
+ validates_presence_of :seatbelt, :key => :first_gear
893
+ end
894
+ @machine.state :second_gear do
895
+ validates_presence_of :seatbelt, :key => :second_gear
896
+ end
897
+ @machine.other_states :parked
898
+ end
899
+
900
+ def test_should_be_valid_if_validation_fails_outside_state_scope
901
+ record = @model.new(:state => 'parked', :seatbelt => nil)
902
+ assert record.valid?
903
+ end
904
+
905
+ def test_should_be_invalid_if_validation_fails_within_state_scope
906
+ record = @model.new(:state => 'first_gear', :seatbelt => nil)
907
+ assert !record.valid?
908
+ end
909
+
910
+ def test_should_be_valid_if_validation_succeeds_within_state_scope
911
+ record = @model.new(:state => 'second_gear', :seatbelt => true)
912
+ assert record.valid?
913
+ end
914
+ end
915
+
916
+ class MachineWithEventAttributesOnValidationTest < BaseTestCase
917
+ def setup
918
+ @model = new_model
919
+ @machine = StateMachine::Machine.new(@model)
920
+ @machine.event :ignite do
921
+ transition :parked => :idling
922
+ end
923
+
924
+ @record = @model.new
925
+ @record.state = 'parked'
926
+ @record.state_event = 'ignite'
927
+ end
928
+
929
+ def test_should_fail_if_event_is_invalid
930
+ @record.state_event = 'invalid'
931
+ assert !@record.valid?
932
+ assert_equal ['State event is invalid'], @record.errors.full_messages
933
+ end
934
+
935
+ def test_should_fail_if_event_has_no_transition
936
+ @record.state = 'idling'
937
+ assert !@record.valid?
938
+ assert_equal ['State event cannot transition when idling'], @record.errors.full_messages
939
+ end
940
+
941
+ def test_should_be_successful_if_event_has_transition
942
+ assert @record.valid?
943
+ end
944
+
945
+ def test_should_run_before_callbacks
946
+ ran_callback = false
947
+ @machine.before_transition { ran_callback = true }
948
+
949
+ @record.valid?
950
+ assert ran_callback
951
+ end
952
+
953
+ def test_should_run_around_callbacks_before_yield
954
+ ran_callback = false
955
+ @machine.around_transition {|block| ran_callback = true; block.call }
956
+
957
+ @record.valid?
958
+ assert ran_callback
959
+ end
960
+
961
+ def test_should_persist_new_state
962
+ @record.valid?
963
+ assert_equal 'idling', @record.state
964
+ end
965
+
966
+ def test_should_not_run_after_callbacks
967
+ ran_callback = false
968
+ @machine.after_transition { ran_callback = true }
969
+
970
+ @record.valid?
971
+ assert !ran_callback
972
+ end
973
+
974
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
975
+ @model.class_eval do
976
+ attr_accessor :seatbelt
977
+ validates_presence_of :seatbelt
978
+ end
979
+
980
+ ran_callback = false
981
+ @machine.after_transition { ran_callback = true }
982
+
983
+ @record.valid?
984
+ assert !ran_callback
985
+ end
986
+
987
+ def test_should_not_run_around_callbacks_after_yield
988
+ ran_callback = false
989
+ @machine.around_transition {|block| block.call; ran_callback = true }
990
+
991
+ @record.valid?
992
+ assert !ran_callback
993
+ end
994
+
995
+ def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails
996
+ @model.class_eval do
997
+ attr_accessor :seatbelt
998
+ validates_presence_of :seatbelt
999
+ end
1000
+
1001
+ ran_callback = false
1002
+ @machine.around_transition {|block| block.call; ran_callback = true }
1003
+
1004
+ @record.valid?
1005
+ assert !ran_callback
1006
+ end
1007
+
1008
+ def test_should_run_failure_callbacks_if_validation_fails
1009
+ @model.class_eval do
1010
+ attr_accessor :seatbelt
1011
+ validates_presence_of :seatbelt
1012
+ end
1013
+
1014
+ ran_callback = false
1015
+ @machine.after_failure { ran_callback = true }
1016
+
1017
+ @record.valid?
1018
+ assert ran_callback
1019
+ end
1020
+ end
1021
+
1022
+ class MachineWithEventAttributesOnSaveTest < BaseTestCase
1023
+ def setup
1024
+ @model = new_model
1025
+ @machine = StateMachine::Machine.new(@model)
1026
+ @machine.event :ignite do
1027
+ transition :parked => :idling
1028
+ end
1029
+
1030
+ @record = @model.new
1031
+ @record.state = 'parked'
1032
+ @record.state_event = 'ignite'
1033
+ end
1034
+
1035
+ def test_should_fail_if_event_is_invalid
1036
+ @record.state_event = 'invalid'
1037
+ assert_equal false, @record.save
1038
+ end
1039
+
1040
+ def test_should_fail_if_event_has_no_transition
1041
+ @record.state = 'idling'
1042
+ assert_equal false, @record.save
1043
+ end
1044
+
1045
+ def test_should_be_successful_if_event_has_transition
1046
+ assert_equal true, @record.save
1047
+ end
1048
+
1049
+ def test_should_run_before_callbacks
1050
+ ran_callback = false
1051
+ @machine.before_transition { ran_callback = true }
1052
+
1053
+ @record.save
1054
+ assert ran_callback
1055
+ end
1056
+
1057
+ def test_should_run_before_callbacks_once
1058
+ before_count = 0
1059
+ @machine.before_transition { before_count += 1 }
1060
+
1061
+ @record.save
1062
+ assert_equal 1, before_count
1063
+ end
1064
+
1065
+ def test_should_run_around_callbacks_before_yield
1066
+ ran_callback = false
1067
+ @machine.around_transition {|block| ran_callback = true; block.call }
1068
+
1069
+ @record.save
1070
+ assert ran_callback
1071
+ end
1072
+
1073
+ def test_should_run_around_callbacks_before_yield_once
1074
+ around_before_count = 0
1075
+ @machine.around_transition {|block| around_before_count += 1; block.call }
1076
+
1077
+ @record.save
1078
+ assert_equal 1, around_before_count
1079
+ end
1080
+
1081
+ def test_should_persist_new_state
1082
+ @record.save
1083
+ assert_equal 'idling', @record.state
1084
+ end
1085
+
1086
+ def test_should_run_after_callbacks
1087
+ ran_callback = false
1088
+ @machine.after_transition { ran_callback = true }
1089
+
1090
+ @record.save
1091
+ assert ran_callback
1092
+ end
1093
+
1094
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
1095
+ @model.class_eval do
1096
+ validates_numericality_of :state
1097
+ end
1098
+
1099
+ ran_callback = false
1100
+ @machine.after_transition { ran_callback = true }
1101
+
1102
+ begin; @record.save; rescue; end
1103
+ assert !ran_callback
1104
+ end
1105
+
1106
+ def test_should_run_failure_callbacks__if_fails
1107
+ @model.class_eval do
1108
+ validates_numericality_of :state
1109
+ end
1110
+
1111
+ ran_callback = false
1112
+ @machine.after_failure { ran_callback = true }
1113
+
1114
+ begin; @record.save; rescue; end
1115
+ assert ran_callback
1116
+ end
1117
+
1118
+ def test_should_not_run_around_callbacks_with_failures_disabled_if_fails
1119
+ @model.class_eval do
1120
+ validates_numericality_of :state
1121
+ end
1122
+
1123
+ ran_callback = false
1124
+ @machine.around_transition {|block| block.call; ran_callback = true }
1125
+
1126
+ begin; @record.save; rescue; end
1127
+ assert !ran_callback
1128
+ end
1129
+
1130
+ def test_should_run_around_callbacks_after_yield
1131
+ ran_callback = false
1132
+ @machine.around_transition {|block| block.call; ran_callback = true }
1133
+
1134
+ @record.save
1135
+ assert ran_callback
1136
+ end
1137
+ end
1138
+
1139
+ class MachineWithEventAttributesOnSaveBangTest < BaseTestCase
1140
+ def setup
1141
+ @model = new_model
1142
+ @machine = StateMachine::Machine.new(@model)
1143
+ @machine.event :ignite do
1144
+ transition :parked => :idling
1145
+ end
1146
+
1147
+ @record = @model.new
1148
+ @record.state = 'parked'
1149
+ @record.state_event = 'ignite'
1150
+ end
1151
+
1152
+ def test_should_fail_if_event_is_invalid
1153
+ @record.state_event = 'invalid'
1154
+ assert_raise(Mongoid::Errors::Validations) { @record.save! }
1155
+ end
1156
+
1157
+ def test_should_fail_if_event_has_no_transition
1158
+ @record.state = 'idling'
1159
+ assert_raise(Mongoid::Errors::Validations) { @record.save! }
1160
+ end
1161
+
1162
+ def test_should_be_successful_if_event_has_transition
1163
+ assert_equal true, @record.save!
1164
+ end
1165
+
1166
+ def test_should_run_before_callbacks
1167
+ ran_callback = false
1168
+ @machine.before_transition { ran_callback = true }
1169
+
1170
+ @record.save!
1171
+ assert ran_callback
1172
+ end
1173
+
1174
+ def test_should_run_before_callbacks_once
1175
+ before_count = 0
1176
+ @machine.before_transition { before_count += 1 }
1177
+
1178
+ @record.save!
1179
+ assert_equal 1, before_count
1180
+ end
1181
+
1182
+ def test_should_run_around_callbacks_before_yield
1183
+ ran_callback = false
1184
+ @machine.around_transition {|block| ran_callback = true; block.call }
1185
+
1186
+ @record.save!
1187
+ assert ran_callback
1188
+ end
1189
+
1190
+ def test_should_run_around_callbacks_before_yield_once
1191
+ around_before_count = 0
1192
+ @machine.around_transition {|block| around_before_count += 1; block.call }
1193
+
1194
+ @record.save!
1195
+ assert_equal 1, around_before_count
1196
+ end
1197
+
1198
+ def test_should_persist_new_state
1199
+ @record.save!
1200
+ assert_equal 'idling', @record.state
1201
+ end
1202
+
1203
+ def test_should_persist_new_state
1204
+ @record.save!
1205
+ assert_equal 'idling', @record.state
1206
+ end
1207
+
1208
+ def test_should_run_after_callbacks
1209
+ ran_callback = false
1210
+ @machine.after_transition { ran_callback = true }
1211
+
1212
+ @record.save!
1213
+ assert ran_callback
1214
+ end
1215
+
1216
+ def test_should_run_around_callbacks_after_yield
1217
+ ran_callback = false
1218
+ @machine.around_transition {|block| block.call; ran_callback = true }
1219
+
1220
+ @record.save!
1221
+ assert ran_callback
1222
+ end
1223
+ end
1224
+
1225
+ class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
1226
+ def setup
1227
+ @superclass = new_model do
1228
+ def persist
1229
+ upsert
1230
+ end
1231
+ end
1232
+ @model = Class.new(@superclass)
1233
+ @machine = StateMachine::Machine.new(@model, :action => :persist)
1234
+ @machine.event :ignite do
1235
+ transition :parked => :idling
1236
+ end
1237
+
1238
+ @record = @model.new
1239
+ @record.state = 'parked'
1240
+ @record.state_event = 'ignite'
1241
+ end
1242
+
1243
+ def test_should_not_transition_on_valid?
1244
+ @record.valid?
1245
+ assert_equal 'parked', @record.state
1246
+ end
1247
+
1248
+ def test_should_not_transition_on_save
1249
+ @record.save
1250
+ assert_equal 'parked', @record.state
1251
+ end
1252
+
1253
+ def test_should_not_transition_on_save!
1254
+ @record.save!
1255
+ assert_equal 'parked', @record.state
1256
+ end
1257
+
1258
+ def test_should_transition_on_custom_action
1259
+ @record.persist
1260
+ assert_equal 'idling', @record.state
1261
+ end
1262
+ end
1263
+
1264
+ class MachineWithScopesTest < BaseTestCase
1265
+ def setup
1266
+ @model = new_model
1267
+ @machine = StateMachine::Machine.new(@model)
1268
+ @machine.state :parked, :first_gear
1269
+ @machine.state :idling, :value => lambda {'idling'}
1270
+ end
1271
+
1272
+ def test_should_create_singular_with_scope
1273
+ assert @model.respond_to?(:with_state)
1274
+ end
1275
+
1276
+ def test_should_only_include_records_with_state_in_singular_with_scope
1277
+ parked = @model.create :state => 'parked'
1278
+ idling = @model.create :state => 'idling'
1279
+
1280
+ assert_equal [parked], @model.with_state(:parked).to_a
1281
+ end
1282
+
1283
+ def test_should_create_plural_with_scope
1284
+ assert @model.respond_to?(:with_states)
1285
+ end
1286
+
1287
+ def test_should_only_include_records_with_states_in_plural_with_scope
1288
+ parked = @model.create :state => 'parked'
1289
+ idling = @model.create :state => 'idling'
1290
+
1291
+ assert_equal [parked, idling], @model.with_states(:parked, :idling).to_a
1292
+ end
1293
+
1294
+ def test_should_create_singular_without_scope
1295
+ assert @model.respond_to?(:without_state)
1296
+ end
1297
+
1298
+ def test_should_only_include_records_without_state_in_singular_without_scope
1299
+ parked = @model.create :state => 'parked'
1300
+ idling = @model.create :state => 'idling'
1301
+
1302
+ assert_equal [parked], @model.without_state(:idling).to_a
1303
+ end
1304
+
1305
+ def test_should_create_plural_without_scope
1306
+ assert @model.respond_to?(:without_states)
1307
+ end
1308
+
1309
+ def test_should_only_include_records_without_states_in_plural_without_scope
1310
+ parked = @model.create :state => 'parked'
1311
+ idling = @model.create :state => 'idling'
1312
+ first_gear = @model.create :state => 'first_gear'
1313
+
1314
+ assert_equal [parked, idling], @model.without_states(:first_gear).to_a
1315
+ end
1316
+
1317
+ def test_should_allow_chaining_scopes
1318
+ parked = @model.create :state => 'parked'
1319
+ idling = @model.create :state => 'idling'
1320
+
1321
+ assert_equal [idling], @model.without_state(:parked).with_state(:idling).all
1322
+ end
1323
+ end
1324
+
1325
+ class MachineWithScopesAndOwnerSubclassTest < BaseTestCase
1326
+ def setup
1327
+ @model = new_model
1328
+ @machine = StateMachine::Machine.new(@model, :state)
1329
+
1330
+ MongoidTest.const_set('Foo', @model)
1331
+
1332
+ @subclass = Class.new(@model)
1333
+ @subclass_machine = @subclass.state_machine(:state) {}
1334
+ @subclass_machine.state :parked, :idling, :first_gear
1335
+ end
1336
+
1337
+ def test_should_only_include_records_with_subclass_states_in_with_scope
1338
+ parked = @subclass.create :state => 'parked'
1339
+ idling = @subclass.create :state => 'idling'
1340
+
1341
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling).to_a
1342
+ end
1343
+
1344
+ def test_should_only_include_records_without_subclass_states_in_without_scope
1345
+ parked = @subclass.create :state => 'parked'
1346
+ idling = @subclass.create :state => 'idling'
1347
+ first_gear = @subclass.create :state => 'first_gear'
1348
+
1349
+ assert_equal [parked, idling], @subclass.without_states(:first_gear).to_a
1350
+ end
1351
+
1352
+ def teardown
1353
+ MongoidTest.send(:remove_const, 'Foo')
1354
+ end
1355
+ end
1356
+
1357
+ class MachineWithComplexPluralizationScopesTest < BaseTestCase
1358
+ def setup
1359
+ @model = new_model
1360
+ @machine = StateMachine::Machine.new(@model, :status)
1361
+ end
1362
+
1363
+ def test_should_create_singular_with_scope
1364
+ assert @model.respond_to?(:with_status)
1365
+ end
1366
+
1367
+ def test_should_create_plural_with_scope
1368
+ assert @model.respond_to?(:with_statuses)
1369
+ end
1370
+ end
1371
+
1372
+ class MachineWithInternationalizationTest < BaseTestCase
1373
+ def setup
1374
+ I18n.backend = I18n::Backend::Simple.new
1375
+
1376
+ # Initialize the backend
1377
+ StateMachine::Machine.new(new_model)
1378
+ I18n.backend.translate(:en, 'mongoid.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling')
1379
+
1380
+ @model = new_model
1381
+ end
1382
+
1383
+ def test_should_use_defaults
1384
+ I18n.backend.store_translations(:en, {
1385
+ :mongoid => {:errors => {:messages => {:invalid_transition => 'cannot %{event}'}}}
1386
+ })
1387
+
1388
+ machine = StateMachine::Machine.new(@model)
1389
+ machine.state :parked, :idling
1390
+ machine.event :ignite
1391
+
1392
+ record = @model.new(:state => 'idling')
1393
+
1394
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
1395
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1396
+ end
1397
+
1398
+ def test_should_allow_customized_error_key
1399
+ I18n.backend.store_translations(:en, {
1400
+ :mongoid => {:errors => {:messages => {:bad_transition => 'cannot %{event}'}}}
1401
+ })
1402
+
1403
+ machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => :bad_transition})
1404
+ machine.state :parked, :idling
1405
+
1406
+ record = @model.new(:state => 'idling')
1407
+
1408
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
1409
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1410
+ end
1411
+
1412
+ def test_should_allow_customized_error_string
1413
+ machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => 'cannot %{event}'})
1414
+ machine.state :parked, :idling
1415
+
1416
+ record = @model.new(:state => 'idling')
1417
+
1418
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
1419
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1420
+ end
1421
+
1422
+ def test_should_allow_customized_state_key_scoped_to_class_and_machine
1423
+ I18n.backend.store_translations(:en, {
1424
+ :mongoid => {:state_machines => {:'mongoid_test/foo' => {:state => {:states => {:parked => 'shutdown'}}}}}
1425
+ })
1426
+
1427
+ machine = StateMachine::Machine.new(@model)
1428
+ machine.state :parked
1429
+
1430
+ assert_equal 'shutdown', machine.state(:parked).human_name
1431
+ end
1432
+
1433
+ def test_should_allow_customized_state_key_scoped_to_machine
1434
+ I18n.backend.store_translations(:en, {
1435
+ :mongoid => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}}
1436
+ })
1437
+
1438
+ machine = StateMachine::Machine.new(@model)
1439
+ machine.state :parked
1440
+
1441
+ assert_equal 'shutdown', machine.state(:parked).human_name
1442
+ end
1443
+
1444
+ def test_should_allow_customized_state_key_unscoped
1445
+ I18n.backend.store_translations(:en, {
1446
+ :mongoid => {:state_machines => {:states => {:parked => 'shutdown'}}}
1447
+ })
1448
+
1449
+ machine = StateMachine::Machine.new(@model)
1450
+ machine.state :parked
1451
+
1452
+ assert_equal 'shutdown', machine.state(:parked).human_name
1453
+ end
1454
+
1455
+ def test_should_allow_customized_event_key_scoped_to_class_and_machine
1456
+ I18n.backend.store_translations(:en, {
1457
+ :mongoid => {:state_machines => {:'mongoid_test/foo' => {:state => {:events => {:park => 'stop'}}}}}
1458
+ })
1459
+
1460
+ machine = StateMachine::Machine.new(@model)
1461
+ machine.event :park
1462
+
1463
+ assert_equal 'stop', machine.event(:park).human_name
1464
+ end
1465
+
1466
+ def test_should_allow_customized_event_key_scoped_to_machine
1467
+ I18n.backend.store_translations(:en, {
1468
+ :mongoid => {:state_machines => {:state => {:events => {:park => 'stop'}}}}
1469
+ })
1470
+
1471
+ machine = StateMachine::Machine.new(@model)
1472
+ machine.event :park
1473
+
1474
+ assert_equal 'stop', machine.event(:park).human_name
1475
+ end
1476
+
1477
+ def test_should_allow_customized_event_key_unscoped
1478
+ I18n.backend.store_translations(:en, {
1479
+ :mongoid => {:state_machines => {:events => {:park => 'stop'}}}
1480
+ })
1481
+
1482
+ machine = StateMachine::Machine.new(@model)
1483
+ machine.event :park
1484
+
1485
+ assert_equal 'stop', machine.event(:park).human_name
1486
+ end
1487
+
1488
+ def test_should_only_add_locale_once_in_load_path
1489
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{mongoid/locale\.rb$}}.length
1490
+
1491
+ # Create another Mongoid model that will triger the i18n feature
1492
+ new_model
1493
+
1494
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{mongoid/locale\.rb$}}.length
1495
+ end
1496
+
1497
+ def test_should_add_locale_to_beginning_of_load_path
1498
+ @original_load_path = I18n.load_path
1499
+ I18n.backend = I18n::Backend::Simple.new
1500
+
1501
+ app_locale = File.dirname(__FILE__) + '/../../files/en.yml'
1502
+ default_locale = File.dirname(__FILE__) + '/../../../lib/state_machine/integrations/mongoid/locale.rb'
1503
+ I18n.load_path = [app_locale]
1504
+
1505
+ StateMachine::Machine.new(@model)
1506
+
1507
+ assert_equal [default_locale, app_locale].map {|path| File.expand_path(path)}, I18n.load_path.map {|path| File.expand_path(path)}
1508
+ ensure
1509
+ I18n.load_path = @original_load_path
1510
+ end
1511
+
1512
+ def test_should_prefer_other_locales_first
1513
+ @original_load_path = I18n.load_path
1514
+ I18n.backend = I18n::Backend::Simple.new
1515
+ I18n.load_path = [File.dirname(__FILE__) + '/../../files/en.yml']
1516
+
1517
+ machine = StateMachine::Machine.new(@model)
1518
+ machine.state :parked, :idling
1519
+ machine.event :ignite
1520
+
1521
+ record = @model.new(:state => 'idling')
1522
+
1523
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
1524
+ assert_equal ['State cannot transition'], record.errors.full_messages
1525
+ ensure
1526
+ I18n.load_path = @original_load_path
1527
+ end
1528
+ end
1529
+ end