verborghs-state_machine 0.9.4

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