hsume2-state_machine 1.0.1

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