state_machine 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|