state_machine 1.0.2 → 1.0.3
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/.gitignore +1 -0
- data/.travis.yml +0 -2
- data/.yardopts +3 -2
- data/Appraisals +48 -0
- data/{CHANGELOG.rdoc → CHANGELOG.md} +63 -46
- data/README.md +1029 -0
- data/gemfiles/active_model-3.0.0.gemfile.lock +1 -3
- data/gemfiles/active_model-3.0.5.gemfile.lock +1 -3
- data/gemfiles/active_model-3.1.1.gemfile +7 -0
- data/gemfiles/active_model-3.1.1.gemfile.lock +32 -0
- data/gemfiles/active_record-2.0.0.gemfile.lock +1 -3
- data/gemfiles/active_record-2.0.5.gemfile.lock +1 -3
- data/gemfiles/active_record-2.1.0.gemfile.lock +1 -3
- data/gemfiles/active_record-2.1.2.gemfile.lock +1 -3
- data/gemfiles/active_record-2.2.3.gemfile.lock +1 -3
- data/gemfiles/active_record-2.3.12.gemfile.lock +1 -3
- data/gemfiles/active_record-3.0.0.gemfile.lock +1 -3
- data/gemfiles/active_record-3.0.5.gemfile.lock +1 -3
- data/gemfiles/active_record-3.1.1.gemfile +8 -0
- data/gemfiles/active_record-3.1.1.gemfile.lock +43 -0
- data/gemfiles/data_mapper-0.10.2.gemfile.lock +1 -3
- data/gemfiles/data_mapper-0.9.11.gemfile.lock +1 -3
- data/gemfiles/data_mapper-0.9.4.gemfile.lock +1 -3
- data/gemfiles/data_mapper-0.9.7.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.0.0.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.0.1.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.0.2.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.1.0.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.2.0.gemfile +12 -0
- data/gemfiles/data_mapper-1.2.0.gemfile.lock +49 -0
- data/gemfiles/default.gemfile.lock +1 -3
- data/gemfiles/graphviz-0.9.0.gemfile +7 -0
- data/gemfiles/graphviz-0.9.0.gemfile.lock +24 -0
- data/gemfiles/graphviz-0.9.21.gemfile +7 -0
- data/gemfiles/graphviz-0.9.21.gemfile.lock +24 -0
- data/gemfiles/graphviz-1.0.0.gemfile +7 -0
- data/gemfiles/graphviz-1.0.0.gemfile.lock +24 -0
- data/gemfiles/mongo_mapper-0.10.0.gemfile +7 -0
- data/gemfiles/mongo_mapper-0.10.0.gemfile.lock +41 -0
- data/gemfiles/mongo_mapper-0.5.5.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.5.8.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.6.0.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.6.10.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.7.0.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.7.5.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.8.0.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.8.3.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.8.4.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.8.6.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.9.0.gemfile.lock +1 -3
- data/gemfiles/mongoid-2.0.0.gemfile.lock +1 -3
- data/gemfiles/mongoid-2.1.4.gemfile.lock +1 -3
- data/gemfiles/mongoid-2.2.4.gemfile +7 -0
- data/gemfiles/mongoid-2.2.4.gemfile.lock +40 -0
- data/gemfiles/mongoid-2.3.3.gemfile +7 -0
- data/gemfiles/mongoid-2.3.3.gemfile.lock +40 -0
- data/gemfiles/sequel-2.11.0.gemfile.lock +1 -3
- data/gemfiles/sequel-2.12.0.gemfile.lock +1 -3
- data/gemfiles/sequel-2.8.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.0.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.13.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.14.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.23.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.24.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.29.0.gemfile +8 -0
- data/gemfiles/sequel-3.29.0.gemfile.lock +26 -0
- data/lib/state_machine.rb +45 -0
- data/lib/state_machine/event.rb +18 -3
- data/lib/state_machine/event_collection.rb +1 -1
- data/lib/state_machine/integrations/active_model.rb +59 -16
- data/lib/state_machine/integrations/active_model/observer.rb +3 -15
- data/lib/state_machine/integrations/active_record.rb +46 -9
- data/lib/state_machine/integrations/data_mapper.rb +42 -2
- data/lib/state_machine/integrations/data_mapper/versions.rb +22 -10
- data/lib/state_machine/integrations/mongo_mapper.rb +55 -0
- data/lib/state_machine/integrations/mongo_mapper/versions.rb +3 -3
- data/lib/state_machine/integrations/mongoid.rb +57 -12
- data/lib/state_machine/integrations/mongoid/versions.rb +22 -4
- data/lib/state_machine/integrations/sequel.rb +45 -0
- data/lib/state_machine/integrations/sequel/versions.rb +3 -0
- data/lib/state_machine/machine.rb +148 -34
- data/lib/state_machine/node_collection.rb +36 -3
- data/lib/state_machine/state.rb +6 -3
- data/lib/state_machine/state_collection.rb +1 -1
- data/lib/state_machine/version.rb +1 -1
- data/lib/tasks/state_machine.rb +11 -9
- data/state_machine.gemspec +2 -3
- data/test/functional/state_machine_test.rb +54 -1
- data/test/unit/event_collection_test.rb +4 -0
- data/test/unit/event_test.rb +34 -1
- data/test/unit/integrations/active_model_test.rb +80 -0
- data/test/unit/integrations/active_record_test.rb +105 -2
- data/test/unit/integrations/data_mapper_test.rb +27 -25
- data/test/unit/integrations/mongo_mapper_test.rb +80 -25
- data/test/unit/integrations/mongoid_test.rb +61 -6
- data/test/unit/integrations/sequel_test.rb +8 -2
- data/test/unit/machine_test.rb +87 -9
- data/test/unit/node_collection_test.rb +129 -12
- data/test/unit/state_collection_test.rb +4 -0
- data/test/unit/state_test.rb +2 -2
- metadata +30 -24
- data/README.rdoc +0 -844
|
@@ -187,6 +187,11 @@ module StateMachine
|
|
|
187
187
|
# Because of the way named scopes work in MongoMapper, they *cannot* be
|
|
188
188
|
# chained.
|
|
189
189
|
#
|
|
190
|
+
# Note that states can also be referenced by the string version of their
|
|
191
|
+
# name:
|
|
192
|
+
#
|
|
193
|
+
# Vehicle.with_state('parked')
|
|
194
|
+
#
|
|
190
195
|
# == Callbacks
|
|
191
196
|
#
|
|
192
197
|
# All before/after transition callbacks defined for MongoMapper models
|
|
@@ -219,6 +224,56 @@ module StateMachine
|
|
|
219
224
|
#
|
|
220
225
|
# Note, also, that the transition can be accessed by simply defining
|
|
221
226
|
# additional arguments in the callback block.
|
|
227
|
+
#
|
|
228
|
+
# == Internationalization
|
|
229
|
+
#
|
|
230
|
+
# Any error message that is generated from performing invalid transitions
|
|
231
|
+
# can be localized. The following default translations are used:
|
|
232
|
+
#
|
|
233
|
+
# en:
|
|
234
|
+
# mongo_mapper:
|
|
235
|
+
# errors:
|
|
236
|
+
# messages:
|
|
237
|
+
# invalid: "is invalid"
|
|
238
|
+
# # %{value} = attribute value, %{state} = Human state name
|
|
239
|
+
# invalid_event: "cannot transition when %{state}"
|
|
240
|
+
# # %{value} = attribute value, %{event} = Human event name, %{state} = Human current state name
|
|
241
|
+
# invalid_transition: "cannot transition via %{event}"
|
|
242
|
+
#
|
|
243
|
+
# You can override these for a specific model like so:
|
|
244
|
+
#
|
|
245
|
+
# en:
|
|
246
|
+
# mongo_mapper:
|
|
247
|
+
# errors:
|
|
248
|
+
# models:
|
|
249
|
+
# user:
|
|
250
|
+
# invalid: "is not valid"
|
|
251
|
+
#
|
|
252
|
+
# In addition to the above, you can also provide translations for the
|
|
253
|
+
# various states / events in each state machine. Using the Vehicle example,
|
|
254
|
+
# state translations will be looked for using the following keys, where
|
|
255
|
+
# +model_name+ = "vehicle", +machine_name+ = "state" and +state_name+ = "parked":
|
|
256
|
+
# * <tt>mongo_mapper.state_machines.#{model_name}.#{machine_name}.states.#{state_name}</tt>
|
|
257
|
+
# * <tt>mongo_mapper.state_machines.#{model_name}.states.#{state_name}</tt>
|
|
258
|
+
# * <tt>mongo_mapper.state_machines.#{machine_name}.states.#{state_name}</tt>
|
|
259
|
+
# * <tt>mongo_mapper.state_machines.states.#{state_name}</tt>
|
|
260
|
+
#
|
|
261
|
+
# Event translations will be looked for using the following keys, where
|
|
262
|
+
# +model_name+ = "vehicle", +machine_name+ = "state" and +event_name+ = "ignite":
|
|
263
|
+
# * <tt>mongo_mapper.state_machines.#{model_name}.#{machine_name}.events.#{event_name}</tt>
|
|
264
|
+
# * <tt>mongo_mapper.state_machines.#{model_name}.events.#{event_name}</tt>
|
|
265
|
+
# * <tt>mongo_mapper.state_machines.#{machine_name}.events.#{event_name}</tt>
|
|
266
|
+
# * <tt>mongo_mapper.state_machines.events.#{event_name}</tt>
|
|
267
|
+
#
|
|
268
|
+
# An example translation configuration might look like so:
|
|
269
|
+
#
|
|
270
|
+
# es:
|
|
271
|
+
# mongo_mapper:
|
|
272
|
+
# state_machines:
|
|
273
|
+
# states:
|
|
274
|
+
# parked: 'estacionado'
|
|
275
|
+
# events:
|
|
276
|
+
# park: 'estacionarse'
|
|
222
277
|
module MongoMapper
|
|
223
278
|
include Base
|
|
224
279
|
include ActiveModel
|
|
@@ -22,7 +22,7 @@ module StateMachine
|
|
|
22
22
|
|
|
23
23
|
version '0.5.x - 0.7.x' do
|
|
24
24
|
def self.active?
|
|
25
|
-
!defined?(::MongoMapper::Version) || ::MongoMapper::Version
|
|
25
|
+
!defined?(::MongoMapper::Version) || ::MongoMapper::Version =~ /^0\.[5-7]\./
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def define_scope(name, scope)
|
|
@@ -32,7 +32,7 @@ module StateMachine
|
|
|
32
32
|
|
|
33
33
|
version '0.5.x - 0.8.x' do
|
|
34
34
|
def self.active?
|
|
35
|
-
!defined?(::MongoMapper::Version) || ::MongoMapper::Version
|
|
35
|
+
!defined?(::MongoMapper::Version) || ::MongoMapper::Version =~ /^0\.[5-8]\./
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def invalidate(object, attribute, message, values = [])
|
|
@@ -80,7 +80,7 @@ module StateMachine
|
|
|
80
80
|
def self.active?
|
|
81
81
|
# Only 0.8.x and up has a Version string available, so Plugins is used
|
|
82
82
|
# to detect when 0.7.x is active
|
|
83
|
-
defined?(::MongoMapper::Plugins) && (!defined?(::MongoMapper::Version) || ::MongoMapper::Version
|
|
83
|
+
defined?(::MongoMapper::Plugins) && (!defined?(::MongoMapper::Version) || ::MongoMapper::Version =~ /^0\.(7|8\.[0-3])\./)
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def define_state_initializer
|
|
@@ -181,6 +181,11 @@ module StateMachine
|
|
|
181
181
|
# Because of the way named scopes work in Mongoid, they *cannot* be
|
|
182
182
|
# chained.
|
|
183
183
|
#
|
|
184
|
+
# Note that states can also be referenced by the string version of their
|
|
185
|
+
# name:
|
|
186
|
+
#
|
|
187
|
+
# Vehicle.with_state('parked')
|
|
188
|
+
#
|
|
184
189
|
# == Callbacks
|
|
185
190
|
#
|
|
186
191
|
# All before/after transition callbacks defined for Mongoid models
|
|
@@ -269,6 +274,56 @@ module StateMachine
|
|
|
269
274
|
# Audit.log(record, transition)
|
|
270
275
|
# end
|
|
271
276
|
# end
|
|
277
|
+
#
|
|
278
|
+
# == Internationalization
|
|
279
|
+
#
|
|
280
|
+
# Any error message that is generated from performing invalid transitions
|
|
281
|
+
# can be localized. The following default translations are used:
|
|
282
|
+
#
|
|
283
|
+
# en:
|
|
284
|
+
# mongoid:
|
|
285
|
+
# errors:
|
|
286
|
+
# messages:
|
|
287
|
+
# invalid: "is invalid"
|
|
288
|
+
# # %{value} = attribute value, %{state} = Human state name
|
|
289
|
+
# invalid_event: "cannot transition when %{state}"
|
|
290
|
+
# # %{value} = attribute value, %{event} = Human event name, %{state} = Human current state name
|
|
291
|
+
# invalid_transition: "cannot transition via %{event}"
|
|
292
|
+
#
|
|
293
|
+
# You can override these for a specific model like so:
|
|
294
|
+
#
|
|
295
|
+
# en:
|
|
296
|
+
# mongoid:
|
|
297
|
+
# errors:
|
|
298
|
+
# models:
|
|
299
|
+
# user:
|
|
300
|
+
# invalid: "is not valid"
|
|
301
|
+
#
|
|
302
|
+
# In addition to the above, you can also provide translations for the
|
|
303
|
+
# various states / events in each state machine. Using the Vehicle example,
|
|
304
|
+
# state translations will be looked for using the following keys, where
|
|
305
|
+
# +model_name+ = "vehicle", +machine_name+ = "state" and +state_name+ = "parked":
|
|
306
|
+
# * <tt>mongoid.state_machines.#{model_name}.#{machine_name}.states.#{state_name}</tt>
|
|
307
|
+
# * <tt>mongoid.state_machines.#{model_name}.states.#{state_name}</tt>
|
|
308
|
+
# * <tt>mongoid.state_machines.#{machine_name}.states.#{state_name}</tt>
|
|
309
|
+
# * <tt>mongoid.state_machines.states.#{state_name}</tt>
|
|
310
|
+
#
|
|
311
|
+
# Event translations will be looked for using the following keys, where
|
|
312
|
+
# +model_name+ = "vehicle", +machine_name+ = "state" and +event_name+ = "ignite":
|
|
313
|
+
# * <tt>mongoid.state_machines.#{model_name}.#{machine_name}.events.#{event_name}</tt>
|
|
314
|
+
# * <tt>mongoid.state_machines.#{model_name}.events.#{event_name}</tt>
|
|
315
|
+
# * <tt>mongoid.state_machines.#{machine_name}.events.#{event_name}</tt>
|
|
316
|
+
# * <tt>mongoid.state_machines.events.#{event_name}</tt>
|
|
317
|
+
#
|
|
318
|
+
# An example translation configuration might look like so:
|
|
319
|
+
#
|
|
320
|
+
# es:
|
|
321
|
+
# mongoid:
|
|
322
|
+
# state_machines:
|
|
323
|
+
# states:
|
|
324
|
+
# parked: 'estacionado'
|
|
325
|
+
# events:
|
|
326
|
+
# park: 'estacionarse'
|
|
272
327
|
module Mongoid
|
|
273
328
|
include Base
|
|
274
329
|
include ActiveModel
|
|
@@ -307,19 +362,9 @@ module StateMachine
|
|
|
307
362
|
# object
|
|
308
363
|
def define_state_initializer
|
|
309
364
|
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
|
|
310
|
-
# Initializes dynamic states
|
|
311
365
|
def initialize(*)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
yield(*args) if block_given?
|
|
315
|
-
end
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
# Initializes static states
|
|
319
|
-
def apply_default_attributes(*)
|
|
320
|
-
result = super
|
|
321
|
-
self.class.state_machines.initialize_states(self, :dynamic => false, :to => result) if new_record?
|
|
322
|
-
result
|
|
366
|
+
@attributes = {}
|
|
367
|
+
self.class.state_machines.initialize_states(self) { super }
|
|
323
368
|
end
|
|
324
369
|
end_eval
|
|
325
370
|
end
|
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
module StateMachine
|
|
2
2
|
module Integrations #:nodoc:
|
|
3
3
|
module Mongoid
|
|
4
|
-
|
|
5
|
-
version '2.0.x - 2.1.x' do
|
|
4
|
+
version '2.0.x - 2.2.x' do
|
|
6
5
|
def self.active?
|
|
7
|
-
::Mongoid::VERSION
|
|
6
|
+
::Mongoid::VERSION =~ /^2\.[0-2]\./
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def define_state_initializer
|
|
10
|
+
define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
|
|
11
|
+
# Initializes dynamic states
|
|
12
|
+
def initialize(*)
|
|
13
|
+
super do |*args|
|
|
14
|
+
self.class.state_machines.initialize_states(self, :static => false)
|
|
15
|
+
yield(*args) if block_given?
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Initializes static states
|
|
20
|
+
def apply_default_attributes(*)
|
|
21
|
+
result = super
|
|
22
|
+
self.class.state_machines.initialize_states(self, :dynamic => false, :to => result) if new_record?
|
|
23
|
+
result
|
|
24
|
+
end
|
|
25
|
+
end_eval
|
|
8
26
|
end
|
|
9
27
|
|
|
10
28
|
def define_action_hook
|
|
@@ -16,7 +34,7 @@ module StateMachine
|
|
|
16
34
|
|
|
17
35
|
version '2.0.x' do
|
|
18
36
|
def self.active?
|
|
19
|
-
::Mongoid::VERSION
|
|
37
|
+
::Mongoid::VERSION =~ /^2\.0\./
|
|
20
38
|
end
|
|
21
39
|
|
|
22
40
|
# Forces the change in state to be recognized regardless of whether the
|
|
@@ -193,6 +193,11 @@ module StateMachine
|
|
|
193
193
|
#
|
|
194
194
|
# Vehicle.with_state(:parked).order(:id.desc)
|
|
195
195
|
#
|
|
196
|
+
# Note that states can also be referenced by the string version of their
|
|
197
|
+
# name:
|
|
198
|
+
#
|
|
199
|
+
# Vehicle.with_state('parked')
|
|
200
|
+
#
|
|
196
201
|
# == Callbacks
|
|
197
202
|
#
|
|
198
203
|
# All before/after transition callbacks defined for Sequel resources
|
|
@@ -224,6 +229,34 @@ module StateMachine
|
|
|
224
229
|
#
|
|
225
230
|
# Note, also, that the transition can be accessed by simply defining
|
|
226
231
|
# additional arguments in the callback block.
|
|
232
|
+
#
|
|
233
|
+
# === Failure callbacks
|
|
234
|
+
#
|
|
235
|
+
# +after_failure+ callbacks allow you to execute behaviors when a transition
|
|
236
|
+
# is allowed, but fails to save. This could be useful for something like
|
|
237
|
+
# auditing transition attempts. Since callbacks run within transactions in
|
|
238
|
+
# Sequel, a save failure will cause any records that get created in
|
|
239
|
+
# your callback to roll back. You can work around this issue like so:
|
|
240
|
+
#
|
|
241
|
+
# DB = Sequel.connect('mysql://localhost/app')
|
|
242
|
+
# DB_LOGS = Sequel.connect('mysql://localhost/app')
|
|
243
|
+
#
|
|
244
|
+
# class TransitionLog < Sequel::Model(DB_LOGS[:transition_logs])
|
|
245
|
+
# end
|
|
246
|
+
#
|
|
247
|
+
# class Vehicle < Sequel::Model(DB[:vehicles])
|
|
248
|
+
# state_machine do
|
|
249
|
+
# after_failure do |transition|
|
|
250
|
+
# TransitionLog.create(:vehicle => vehicle, :transition => transition)
|
|
251
|
+
# end
|
|
252
|
+
#
|
|
253
|
+
# ...
|
|
254
|
+
# end
|
|
255
|
+
# end
|
|
256
|
+
#
|
|
257
|
+
# The +TransitionLog+ model uses a second connection to the database that
|
|
258
|
+
# allows new records to be saved without being affected by rollbacks in the
|
|
259
|
+
# +Vehicle+ model's transaction.
|
|
227
260
|
module Sequel
|
|
228
261
|
include Base
|
|
229
262
|
|
|
@@ -276,6 +309,18 @@ module StateMachine
|
|
|
276
309
|
end
|
|
277
310
|
|
|
278
311
|
protected
|
|
312
|
+
# Initializes class-level extensions for this machine
|
|
313
|
+
def define_helpers
|
|
314
|
+
load_plugins
|
|
315
|
+
super
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Loads all of the Sequel plugins necessary to run
|
|
319
|
+
def load_plugins
|
|
320
|
+
owner_class.plugin(:validation_class_methods)
|
|
321
|
+
owner_class.plugin(:hook_class_methods)
|
|
322
|
+
end
|
|
323
|
+
|
|
279
324
|
# Loads the built-in inflector
|
|
280
325
|
def load_inflector
|
|
281
326
|
require 'sequel/extensions/inflector'
|
|
@@ -139,11 +139,60 @@ module StateMachine
|
|
|
139
139
|
# vehicle.save # => true (no exception raised)
|
|
140
140
|
#
|
|
141
141
|
# If you need callbacks to get triggered when an object is created, this
|
|
142
|
-
# should be done by
|
|
143
|
-
# * Use a <tt>before :
|
|
144
|
-
#
|
|
142
|
+
# should be done by one of the following techniques:
|
|
143
|
+
# * Use a <tt>before :create</tt> or equivalent hook:
|
|
144
|
+
#
|
|
145
|
+
# class Vehicle
|
|
146
|
+
# before :create, :track_initial_transition
|
|
147
|
+
#
|
|
148
|
+
# state_machine do
|
|
149
|
+
# ...
|
|
150
|
+
# end
|
|
151
|
+
# end
|
|
152
|
+
#
|
|
153
|
+
# * Set an initial state and use the correct event to create the
|
|
145
154
|
# object with the proper state, resulting in callbacks being triggered and
|
|
146
|
-
# the object getting persisted
|
|
155
|
+
# the object getting persisted (note that the <tt>:pending</tt> state is
|
|
156
|
+
# actually stored as nil):
|
|
157
|
+
#
|
|
158
|
+
# class Vehicle
|
|
159
|
+
# state_machine :initial => :pending
|
|
160
|
+
# after_transition :pending => :parked, :do => :track_initial_transition
|
|
161
|
+
#
|
|
162
|
+
# event :park do
|
|
163
|
+
# transition :pending => :parked
|
|
164
|
+
# end
|
|
165
|
+
#
|
|
166
|
+
# state :pending, :value => nil
|
|
167
|
+
# end
|
|
168
|
+
# end
|
|
169
|
+
#
|
|
170
|
+
# vehicle = Vehicle.new
|
|
171
|
+
# vehicle.park
|
|
172
|
+
#
|
|
173
|
+
# * Use a default event attribute that will automatically trigger when the
|
|
174
|
+
# configured action gets run (note that the <tt>:pending</tt> state is
|
|
175
|
+
# actually stored as nil):
|
|
176
|
+
#
|
|
177
|
+
# class Vehicle < ActiveRecord::Base
|
|
178
|
+
# state_machine :initial => :pending
|
|
179
|
+
# after_transition :pending => :parked, :do => :track_initial_transition
|
|
180
|
+
#
|
|
181
|
+
# event :park do
|
|
182
|
+
# transition :pending => :parked
|
|
183
|
+
# end
|
|
184
|
+
#
|
|
185
|
+
# state :pending, :value => nil
|
|
186
|
+
# end
|
|
187
|
+
#
|
|
188
|
+
# def initialize(*)
|
|
189
|
+
# super
|
|
190
|
+
# self.state_event = 'park'
|
|
191
|
+
# end
|
|
192
|
+
# end
|
|
193
|
+
#
|
|
194
|
+
# vehicle = Vehicle.new
|
|
195
|
+
# vehicle.save
|
|
147
196
|
#
|
|
148
197
|
# === Canceling callbacks
|
|
149
198
|
#
|
|
@@ -301,7 +350,7 @@ module StateMachine
|
|
|
301
350
|
# module that gets included in every Object. As a result, state_machine will
|
|
302
351
|
# generate the following warning:
|
|
303
352
|
#
|
|
304
|
-
# Instance method "open" is already defined in Object, use generic helper instead.
|
|
353
|
+
# Instance method "open" is already defined in Object, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.
|
|
305
354
|
#
|
|
306
355
|
# Even though you may not be using Kernel's implementation of the "open"
|
|
307
356
|
# instance method, state_machine isn't aware of this and, as a result, stays
|
|
@@ -696,7 +745,7 @@ module StateMachine
|
|
|
696
745
|
if block_given?
|
|
697
746
|
if !self.class.ignore_method_conflicts && conflicting_ancestor = owner_class_ancestor_has_method?(scope, method)
|
|
698
747
|
ancestor_name = conflicting_ancestor.name && !conflicting_ancestor.name.empty? ? conflicting_ancestor.name : conflicting_ancestor.to_s
|
|
699
|
-
warn "#{scope == :class ? 'Class' : 'Instance'} method \"#{method}\" is already defined in #{ancestor_name}, use generic helper instead."
|
|
748
|
+
warn "#{scope == :class ? 'Class' : 'Instance'} method \"#{method}\" is already defined in #{ancestor_name}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true."
|
|
700
749
|
else
|
|
701
750
|
name = self.name
|
|
702
751
|
helper_module.class_eval do
|
|
@@ -923,6 +972,28 @@ module StateMachine
|
|
|
923
972
|
# vehicle.state = 'backing_up'
|
|
924
973
|
# vehicle.speed # => NoMethodError: undefined method 'speed' for #<Vehicle:0xb7d296ac> in state "backing_up"
|
|
925
974
|
#
|
|
975
|
+
# === Using matchers
|
|
976
|
+
#
|
|
977
|
+
# The +all+ / +any+ matchers can be used to easily define behaviors for a
|
|
978
|
+
# group of states. Note, however, that you cannot use these matchers to
|
|
979
|
+
# set configurations for states. Behaviors using these matchers can be
|
|
980
|
+
# defined at any point in the state machine and will always get applied to
|
|
981
|
+
# the proper states.
|
|
982
|
+
#
|
|
983
|
+
# For example:
|
|
984
|
+
#
|
|
985
|
+
# state_machine :initial => :parked do
|
|
986
|
+
# ...
|
|
987
|
+
#
|
|
988
|
+
# state all - [:parked, :idling, :stalled] do
|
|
989
|
+
# validates_presence_of :speed
|
|
990
|
+
#
|
|
991
|
+
# def speed
|
|
992
|
+
# gear * 10
|
|
993
|
+
# end
|
|
994
|
+
# end
|
|
995
|
+
# end
|
|
996
|
+
#
|
|
926
997
|
# == State-aware class methods
|
|
927
998
|
#
|
|
928
999
|
# In addition to defining scopes for instance methods that are state-aware,
|
|
@@ -959,17 +1030,29 @@ module StateMachine
|
|
|
959
1030
|
options = names.last.is_a?(Hash) ? names.pop : {}
|
|
960
1031
|
assert_valid_keys(options, :value, :cache, :if, :human_name)
|
|
961
1032
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1033
|
+
# Store the context so that it can be used for / matched against any state
|
|
1034
|
+
# that gets added
|
|
1035
|
+
@states.context(names, &block) if block_given?
|
|
1036
|
+
|
|
1037
|
+
if names.first.is_a?(Matcher)
|
|
1038
|
+
# Add any states referenced in the matcher. When matchers are used,
|
|
1039
|
+
# states are not allowed to be configured.
|
|
1040
|
+
raise ArgumentError, "Cannot configure states when using matchers (using #{options.inspect})" if options.any?
|
|
1041
|
+
states = add_states(names.first.values)
|
|
1042
|
+
else
|
|
1043
|
+
states = add_states(names)
|
|
968
1044
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1045
|
+
# Update the configuration for the state(s)
|
|
1046
|
+
states.each do |state|
|
|
1047
|
+
if options.include?(:value)
|
|
1048
|
+
state.value = options[:value]
|
|
1049
|
+
self.states.update(state)
|
|
1050
|
+
end
|
|
1051
|
+
|
|
1052
|
+
state.human_name = options[:human_name] if options.include?(:human_name)
|
|
1053
|
+
state.cache = options[:cache] if options.include?(:cache)
|
|
1054
|
+
state.matcher = options[:if] if options.include?(:if)
|
|
1055
|
+
end
|
|
973
1056
|
end
|
|
974
1057
|
|
|
975
1058
|
states.length == 1 ? states.first : states
|
|
@@ -1038,14 +1121,18 @@ module StateMachine
|
|
|
1038
1121
|
# transition fails, then a StateMachine::InvalidTransition error will be
|
|
1039
1122
|
# raised. If the last argument is a boolean, it will control whether the
|
|
1040
1123
|
# machine's action gets run.
|
|
1041
|
-
# * <tt>can_park?(requirements = {})</tt> - Checks whether the "park" event
|
|
1042
|
-
# the current state of the object. This will *not* run
|
|
1043
|
-
# ORM integrations.
|
|
1044
|
-
#
|
|
1045
|
-
#
|
|
1046
|
-
#
|
|
1047
|
-
#
|
|
1048
|
-
#
|
|
1124
|
+
# * <tt>can_park?(requirements = {})</tt> - Checks whether the "park" event
|
|
1125
|
+
# can be fired given the current state of the object. This will *not* run
|
|
1126
|
+
# validations or callbacks in ORM integrations. It will only determine if
|
|
1127
|
+
# the state machine defines a valid transition for the event. To check
|
|
1128
|
+
# whether an event can fire *and* passes validations, use event attributes
|
|
1129
|
+
# (e.g. state_event) as described in the "Events" documentation of each
|
|
1130
|
+
# ORM integration.
|
|
1131
|
+
# * <tt>park_transition(requirements = {})</tt> - Gets the next transition
|
|
1132
|
+
# that would be performed if the "park" event were to be fired now on the
|
|
1133
|
+
# object or nil if no transitions can be performed. Like <tt>can_park?</tt>
|
|
1134
|
+
# this will also *not* run validations or callbacks. It will only
|
|
1135
|
+
# determine if the state machine defines a valid transition for the event.
|
|
1049
1136
|
#
|
|
1050
1137
|
# With a namespace of "car", the above names map to the following methods:
|
|
1051
1138
|
# * <tt>can_park_car?</tt>
|
|
@@ -1199,6 +1286,24 @@ module StateMachine
|
|
|
1199
1286
|
# the entire arguments list to be accessed by transition callbacks through
|
|
1200
1287
|
# StateMachine::Transition#args.
|
|
1201
1288
|
#
|
|
1289
|
+
# === Using matchers
|
|
1290
|
+
#
|
|
1291
|
+
# The +all+ / +any+ matchers can be used to easily execute blocks for a
|
|
1292
|
+
# group of events. Note, however, that you cannot use these matchers to
|
|
1293
|
+
# set configurations for events. Blocks using these matchers can be
|
|
1294
|
+
# defined at any point in the state machine and will always get applied to
|
|
1295
|
+
# the proper events.
|
|
1296
|
+
#
|
|
1297
|
+
# For example:
|
|
1298
|
+
#
|
|
1299
|
+
# state_machine :initial => :parked do
|
|
1300
|
+
# ...
|
|
1301
|
+
#
|
|
1302
|
+
# event all - [:crash] do
|
|
1303
|
+
# transition :stalled => :parked
|
|
1304
|
+
# end
|
|
1305
|
+
# end
|
|
1306
|
+
#
|
|
1202
1307
|
# == Example
|
|
1203
1308
|
#
|
|
1204
1309
|
# class Vehicle
|
|
@@ -1222,16 +1327,25 @@ module StateMachine
|
|
|
1222
1327
|
options = names.last.is_a?(Hash) ? names.pop : {}
|
|
1223
1328
|
assert_valid_keys(options, :human_name)
|
|
1224
1329
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1330
|
+
# Store the context so that it can be used for / matched against any event
|
|
1331
|
+
# that gets added
|
|
1332
|
+
@events.context(names, &block) if block_given?
|
|
1333
|
+
|
|
1334
|
+
if names.first.is_a?(Matcher)
|
|
1335
|
+
# Add any events referenced in the matcher. When matchers are used,
|
|
1336
|
+
# events are not allowed to be configured.
|
|
1337
|
+
raise ArgumentError, "Cannot configure events when using matchers (using #{options.inspect})" if options.any?
|
|
1338
|
+
events = add_events(names.first.values)
|
|
1339
|
+
else
|
|
1340
|
+
events = add_events(names)
|
|
1228
1341
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1342
|
+
# Update the configuration for the event(s)
|
|
1343
|
+
events.each do |event|
|
|
1344
|
+
event.human_name = options[:human_name] if options.include?(:human_name)
|
|
1345
|
+
|
|
1346
|
+
# Add any states that may have been referenced within the event
|
|
1231
1347
|
add_states(event.known_states)
|
|
1232
1348
|
end
|
|
1233
|
-
|
|
1234
|
-
event
|
|
1235
1349
|
end
|
|
1236
1350
|
|
|
1237
1351
|
events.length == 1 ? events.first : events
|
|
@@ -1777,7 +1891,7 @@ module StateMachine
|
|
|
1777
1891
|
graphvizVersion = Constants::RGV_VERSION.split('.')
|
|
1778
1892
|
file = File.join(options[:path], "#{options[:name]}.#{options[:format]}")
|
|
1779
1893
|
|
|
1780
|
-
if graphvizVersion[1] == '9' && graphvizVersion[2] == '0'
|
|
1894
|
+
if graphvizVersion[0] == '0' && graphvizVersion[1] == '9' && graphvizVersion[2] == '0'
|
|
1781
1895
|
outputOptions = {:output => options[:format], :file => file}
|
|
1782
1896
|
else
|
|
1783
1897
|
outputOptions = {options[:format] => file}
|
|
@@ -1785,8 +1899,8 @@ module StateMachine
|
|
|
1785
1899
|
|
|
1786
1900
|
graph.output(outputOptions)
|
|
1787
1901
|
graph
|
|
1788
|
-
rescue LoadError
|
|
1789
|
-
$stderr.puts
|
|
1902
|
+
rescue LoadError => ex
|
|
1903
|
+
$stderr.puts "Cannot draw the machine (#{ex.message}). `gem install ruby-graphviz` >= v0.9.0 and try again."
|
|
1790
1904
|
false
|
|
1791
1905
|
end
|
|
1792
1906
|
end
|