hsume2-state_machine 1.0.1

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 (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