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