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