state_machine 0.4.3 → 0.5.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 +54 -84
- data/Rakefile +1 -1
- data/examples/Car_state.png +0 -0
- data/examples/Vehicle_state.png +0 -0
- data/examples/auto_shop.rb +11 -0
- data/examples/car.rb +19 -0
- data/examples/traffic_light.rb +9 -0
- data/examples/vehicle.rb +35 -0
- data/lib/state_machine.rb +65 -52
- data/lib/state_machine/assertions.rb +1 -1
- data/lib/state_machine/callback.rb +13 -9
- data/lib/state_machine/eval_helpers.rb +4 -3
- data/lib/state_machine/event.rb +51 -33
- data/lib/state_machine/extensions.rb +2 -2
- data/lib/state_machine/guard.rb +47 -41
- data/lib/state_machine/integrations.rb +67 -0
- data/lib/state_machine/integrations/active_record.rb +62 -36
- data/lib/state_machine/integrations/active_record/observer.rb +41 -0
- data/lib/state_machine/integrations/data_mapper.rb +23 -37
- data/lib/state_machine/integrations/data_mapper/observer.rb +23 -9
- data/lib/state_machine/integrations/sequel.rb +23 -24
- data/lib/state_machine/machine.rb +380 -277
- data/lib/state_machine/node_collection.rb +142 -0
- data/lib/state_machine/state.rb +114 -69
- data/lib/state_machine/state_collection.rb +38 -0
- data/lib/state_machine/transition.rb +36 -17
- data/test/active_record.log +2940 -85664
- data/test/functional/state_machine_test.rb +49 -53
- data/test/sequel.log +747 -11990
- data/test/unit/assertions_test.rb +2 -1
- data/test/unit/callback_test.rb +14 -12
- data/test/unit/eval_helpers_test.rb +25 -6
- data/test/unit/event_test.rb +144 -124
- data/test/unit/guard_test.rb +118 -140
- data/test/unit/integrations/active_record_test.rb +102 -68
- data/test/unit/integrations/data_mapper_test.rb +48 -37
- data/test/unit/integrations/sequel_test.rb +34 -25
- data/test/unit/integrations_test.rb +42 -0
- data/test/unit/machine_test.rb +460 -531
- data/test/unit/node_collection_test.rb +208 -0
- data/test/unit/state_collection_test.rb +167 -0
- data/test/unit/state_machine_test.rb +1 -1
- data/test/unit/state_test.rb +223 -200
- data/test/unit/transition_test.rb +81 -46
- metadata +17 -3
- data/test/data_mapper.log +0 -30860
@@ -1,66 +1,21 @@
|
|
1
1
|
require 'state_machine/extensions'
|
2
|
+
require 'state_machine/assertions'
|
3
|
+
require 'state_machine/integrations'
|
4
|
+
|
2
5
|
require 'state_machine/state'
|
3
6
|
require 'state_machine/event'
|
4
7
|
require 'state_machine/callback'
|
5
|
-
require 'state_machine/
|
6
|
-
|
7
|
-
# Load each available integration
|
8
|
-
Dir["#{File.dirname(__FILE__)}/integrations/*.rb"].sort.each do |path|
|
9
|
-
require "state_machine/integrations/#{File.basename(path)}"
|
10
|
-
end
|
8
|
+
require 'state_machine/node_collection'
|
9
|
+
require 'state_machine/state_collection'
|
11
10
|
|
12
11
|
module StateMachine
|
13
12
|
# Represents a state machine for a particular attribute. State machines
|
14
13
|
# consist of states, events and a set of transitions that define how the state
|
15
14
|
# changes after a particular event is fired.
|
16
15
|
#
|
17
|
-
# A state machine
|
18
|
-
#
|
19
|
-
#
|
20
|
-
# a state has not been referenced *anywhere* in the state machine definition,
|
21
|
-
# then it will *not* be a known state unless the +other_states+ helper is used.
|
22
|
-
#
|
23
|
-
# == State values
|
24
|
-
#
|
25
|
-
# While strings are the most common object type used for setting values on
|
26
|
-
# the state of the machine, there are no restrictions on what can be used.
|
27
|
-
# This means that symbols, integers, dates/times, etc. can all be used.
|
28
|
-
#
|
29
|
-
# With string states:
|
30
|
-
#
|
31
|
-
# class Vehicle
|
32
|
-
# state_machine :initial => 'parked' do
|
33
|
-
# event :ignite do
|
34
|
-
# transition :to => 'idling', :from => 'parked'
|
35
|
-
# end
|
36
|
-
# end
|
37
|
-
# end
|
38
|
-
#
|
39
|
-
# With symbolic states:
|
40
|
-
#
|
41
|
-
# class Vehicle
|
42
|
-
# state_machine :initial => :parked do
|
43
|
-
# event :ignite do
|
44
|
-
# transition :to => :idling, :from => :parked
|
45
|
-
# end
|
46
|
-
# end
|
47
|
-
# end
|
48
|
-
#
|
49
|
-
# With time states:
|
50
|
-
#
|
51
|
-
# class Switch
|
52
|
-
# state_machine :activated_at
|
53
|
-
# before_transition :to => nil, :do => lambda {...}
|
54
|
-
#
|
55
|
-
# event :activate do
|
56
|
-
# transition :to => lambda {Time.now}
|
57
|
-
# end
|
58
|
-
#
|
59
|
-
# event :deactivate do
|
60
|
-
# transition :to => nil
|
61
|
-
# end
|
62
|
-
# end
|
63
|
-
# end
|
16
|
+
# A state machine will not know all of the possible states for an object unless
|
17
|
+
# they are referenced *somewhere* in the state machine definition. As a result,
|
18
|
+
# any unused states should be defined with the +other_states+ or +state+ helper.
|
64
19
|
#
|
65
20
|
# == Callbacks
|
66
21
|
#
|
@@ -88,8 +43,8 @@ module StateMachine
|
|
88
43
|
# example,
|
89
44
|
#
|
90
45
|
# class Vehicle
|
91
|
-
# state_machine, :initial =>
|
92
|
-
# before_transition :to =>
|
46
|
+
# state_machine, :initial => :parked do
|
47
|
+
# before_transition :to => :idling, :do => lambda {|vehicle| throw :halt}
|
93
48
|
# ...
|
94
49
|
# end
|
95
50
|
# end
|
@@ -109,7 +64,7 @@ module StateMachine
|
|
109
64
|
# class Vehicle
|
110
65
|
# state_machine do
|
111
66
|
# event :park do
|
112
|
-
# transition :to =>
|
67
|
+
# transition :to => :parked, :from => :idling
|
113
68
|
# end
|
114
69
|
# ...
|
115
70
|
# end
|
@@ -188,23 +143,28 @@ module StateMachine
|
|
188
143
|
# Attempts to find or create a state machine for the given class. For
|
189
144
|
# example,
|
190
145
|
#
|
191
|
-
# StateMachine::Machine.find_or_create(
|
192
|
-
# StateMachine::Machine.find_or_create(
|
193
|
-
# StateMachine::Machine.find_or_create(
|
194
|
-
# StateMachine::Machine.find_or_create(
|
146
|
+
# StateMachine::Machine.find_or_create(Vehicle)
|
147
|
+
# StateMachine::Machine.find_or_create(Vehicle, :initial => :parked)
|
148
|
+
# StateMachine::Machine.find_or_create(Vehicle, :status)
|
149
|
+
# StateMachine::Machine.find_or_create(Vehicle, :status, :initial => :parked)
|
195
150
|
#
|
196
151
|
# If a machine of the given name already exists in one of the class's
|
197
152
|
# superclasses, then a copy of that machine will be created and stored
|
198
153
|
# in the new owner class (the original will remain unchanged).
|
199
154
|
def find_or_create(owner_class, *args, &block)
|
200
155
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
201
|
-
attribute =
|
156
|
+
attribute = args.first || :state
|
202
157
|
|
203
158
|
# Attempts to find an existing machine
|
204
159
|
if owner_class.respond_to?(:state_machines) && machine = owner_class.state_machines[attribute]
|
205
|
-
|
160
|
+
# Create a copy of the state machine if it's being created by a subclass
|
161
|
+
unless machine.owner_class == owner_class
|
162
|
+
machine = machine.clone
|
163
|
+
machine.initial_state = options[:initial] if options.include?(:initial)
|
164
|
+
machine.owner_class = owner_class
|
165
|
+
end
|
206
166
|
|
207
|
-
# Evaluate caller block
|
167
|
+
# Evaluate DSL caller block
|
208
168
|
machine.instance_eval(&block) if block_given?
|
209
169
|
else
|
210
170
|
# No existing machine: create a new one
|
@@ -218,10 +178,11 @@ module StateMachine
|
|
218
178
|
# The given classes must be a comma-delimited string of class names.
|
219
179
|
#
|
220
180
|
# Configuration options:
|
221
|
-
# *
|
222
|
-
#
|
223
|
-
# *
|
224
|
-
# *
|
181
|
+
# * <tt>:file</tt> - A comma-delimited string of files to load that
|
182
|
+
# contain the state machine definitions to draw
|
183
|
+
# * <tt>:path</tt> - The path to write the graph file to
|
184
|
+
# * <tt>:format</tt> - The image format to generate the graph in
|
185
|
+
# * <tt>:font</tt> - The name of the font to draw state names in
|
225
186
|
def draw(class_names, options = {})
|
226
187
|
raise ArgumentError, 'At least one class must be specified' unless class_names && class_names.split(',').any?
|
227
188
|
|
@@ -246,35 +207,29 @@ module StateMachine
|
|
246
207
|
end
|
247
208
|
|
248
209
|
# The class that the machine is defined in
|
249
|
-
|
210
|
+
attr_accessor :owner_class
|
250
211
|
|
251
212
|
# The attribute for which the machine is being defined
|
252
213
|
attr_reader :attribute
|
253
214
|
|
254
|
-
# The
|
255
|
-
|
256
|
-
|
257
|
-
# The events that trigger transitions
|
258
|
-
#
|
259
|
-
# Maps "name" => StateMachine::Event
|
215
|
+
# The events that trigger transitions. These are sorted, by default, in the
|
216
|
+
# order in which they were defined.
|
260
217
|
attr_reader :events
|
261
218
|
|
262
|
-
# Tracks the order in which events were defined. This is used to determine
|
263
|
-
# in what order events are drawn on GraphViz visualizations.
|
264
|
-
attr_reader :events_order
|
265
|
-
|
266
219
|
# A list of all of the states known to this state machine. This will pull
|
267
|
-
#
|
220
|
+
# states from the following sources:
|
268
221
|
# * Initial state
|
269
222
|
# * State behaviors
|
270
|
-
# * Event transitions (:to, :from,
|
223
|
+
# * Event transitions (:to, :from, and :except_from options)
|
271
224
|
# * Transition callbacks (:to, :from, :except_to, and :except_from options)
|
272
225
|
# * Unreferenced states (using +other_states+ helper)
|
273
226
|
#
|
274
|
-
#
|
227
|
+
# These are sorted, by default, in the order in which they were referenced.
|
275
228
|
attr_reader :states
|
276
229
|
|
277
230
|
# The callbacks to invoke before/after a transition is performed
|
231
|
+
#
|
232
|
+
# Maps :before => callbacks and :after => callbacks
|
278
233
|
attr_reader :callbacks
|
279
234
|
|
280
235
|
# The action to invoke when an object transitions
|
@@ -291,87 +246,55 @@ module StateMachine
|
|
291
246
|
assert_valid_keys(options, :initial, :action, :plural, :namespace, :integration)
|
292
247
|
|
293
248
|
# Set machine configuration
|
294
|
-
@attribute =
|
295
|
-
@events =
|
296
|
-
@
|
297
|
-
@states = {}
|
249
|
+
@attribute = args.first || :state
|
250
|
+
@events = NodeCollection.new
|
251
|
+
@states = StateCollection.new
|
298
252
|
@callbacks = {:before => [], :after => []}
|
299
|
-
@action = options[:action]
|
300
253
|
@namespace = options[:namespace]
|
254
|
+
self.owner_class = owner_class
|
255
|
+
self.initial_state = options[:initial]
|
301
256
|
|
302
|
-
#
|
303
|
-
|
304
|
-
extend
|
305
|
-
|
306
|
-
end unless owner_class.included_modules.include?(StateMachine::InstanceMethods)
|
307
|
-
|
308
|
-
# Initialize the class context of the machine
|
309
|
-
set_context(owner_class, :initial => options[:initial], :integration => options[:integration], &block)
|
257
|
+
# Find an integration that matches this machine's owner class
|
258
|
+
if integration = options[:integration] ? StateMachine::Integrations.find(options[:integration]) : StateMachine::Integrations.match(owner_class)
|
259
|
+
extend integration
|
260
|
+
end
|
310
261
|
|
311
262
|
# Set integration-specific configurations
|
312
|
-
@action
|
263
|
+
@action = options.include?(:action) ? options[:action] : default_action
|
313
264
|
define_attribute_accessor
|
314
265
|
define_scopes(options[:plural])
|
315
266
|
|
316
267
|
# Call after hook for integration-specific extensions
|
317
268
|
after_initialize
|
318
269
|
|
319
|
-
# Evaluate caller block
|
270
|
+
# Evaluate DSL caller block
|
320
271
|
instance_eval(&block) if block_given?
|
321
272
|
end
|
322
273
|
|
323
274
|
# Creates a copy of this machine in addition to copies of each associated
|
324
|
-
# event, so that the
|
325
|
-
#
|
275
|
+
# event/states/callback, so that the modifications to those collections do
|
276
|
+
# not affect the original machine.
|
326
277
|
def initialize_copy(orig) #:nodoc:
|
327
278
|
super
|
328
279
|
|
329
|
-
@events = @events.
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
events
|
334
|
-
end
|
335
|
-
@events_order = @events_order.dup
|
336
|
-
@states = @states.inject({}) do |states, (value, state)|
|
337
|
-
state = state.dup
|
338
|
-
state.machine = self
|
339
|
-
states[value] = state
|
340
|
-
states
|
341
|
-
end
|
342
|
-
@initial_state = @states[@initial_state.value]
|
280
|
+
@events = @events.dup
|
281
|
+
@events.machine = self
|
282
|
+
@states = @states.dup
|
283
|
+
@states.machine = self
|
343
284
|
@callbacks = {:before => @callbacks[:before].dup, :after => @callbacks[:after].dup}
|
344
285
|
end
|
345
286
|
|
346
|
-
#
|
347
|
-
#
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
machine
|
352
|
-
end
|
353
|
-
|
354
|
-
# Changes the context of this machine to the given class so that new
|
355
|
-
# events and transitions are created in the proper context.
|
356
|
-
#
|
357
|
-
# Configuration options:
|
358
|
-
# * +initial+ - The initial value to set the attribute to
|
359
|
-
# * +integration+ - The name of the integration for extending this machine with library-specific behavior
|
360
|
-
#
|
361
|
-
# All other configuration options for the machine can only be set on
|
362
|
-
# creation.
|
363
|
-
def set_context(owner_class, options = {}) #:nodoc:
|
364
|
-
assert_valid_keys(options, :initial, :integration)
|
365
|
-
|
366
|
-
@owner_class = owner_class
|
367
|
-
@initial_state = add_states([options[:initial]]).first if options.include?(:initial) || !@initial_state
|
368
|
-
states.each {|name, state| state.initial = (state == @initial_state)}
|
287
|
+
# Sets the class which is the owner of this state machine. Any methods
|
288
|
+
# generated by states, events, or other parts of the machine will be defined
|
289
|
+
# on the given owner class.
|
290
|
+
def owner_class=(klass)
|
291
|
+
@owner_class = klass
|
369
292
|
|
370
|
-
#
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
end
|
293
|
+
# Add class-/instance-level methods to the owner class for state initialization
|
294
|
+
owner_class.class_eval do
|
295
|
+
extend StateMachine::ClassMethods
|
296
|
+
include StateMachine::InstanceMethods
|
297
|
+
end unless owner_class.included_modules.include?(StateMachine::InstanceMethods)
|
375
298
|
|
376
299
|
# Record this machine as matched to the attribute in the current owner
|
377
300
|
# class. This will override any machines mapped to the same attribute
|
@@ -379,30 +302,40 @@ module StateMachine
|
|
379
302
|
owner_class.state_machines[attribute] = self
|
380
303
|
end
|
381
304
|
|
305
|
+
# Sets the initial state of the machine. This can be either the static name
|
306
|
+
# of a state or a lambda block which determines the initial state at
|
307
|
+
# creation time.
|
308
|
+
def initial_state=(new_initial_state)
|
309
|
+
@initial_state = new_initial_state
|
310
|
+
add_states([@initial_state]) unless @initial_state.is_a?(Proc)
|
311
|
+
|
312
|
+
# Update all states to reflect the new initial state
|
313
|
+
states.each {|state| state.initial = (state.name == @initial_state)}
|
314
|
+
end
|
315
|
+
|
382
316
|
# Gets the initial state of the machine for the given object. If a dynamic
|
383
317
|
# initial state was configured for this machine, then the object will be
|
384
|
-
# passed into the lambda block to help determine the actual
|
385
|
-
# initial state.
|
318
|
+
# passed into the lambda block to help determine the actual state.
|
386
319
|
#
|
387
320
|
# == Examples
|
388
321
|
#
|
389
322
|
# With a static initial state:
|
390
323
|
#
|
391
324
|
# class Vehicle
|
392
|
-
# state_machine :initial =>
|
325
|
+
# state_machine :initial => :parked do
|
393
326
|
# ...
|
394
327
|
# end
|
395
328
|
# end
|
396
329
|
#
|
397
330
|
# vehicle = Vehicle.new
|
398
|
-
# Vehicle.state_machines[
|
331
|
+
# Vehicle.state_machines[:state].initial_state(vehicle) # => #<StateMachine::State name=:parked value="parked" initial=true>
|
399
332
|
#
|
400
333
|
# With a dynamic initial state:
|
401
334
|
#
|
402
335
|
# class Vehicle
|
403
336
|
# attr_accessor :force_idle
|
404
337
|
#
|
405
|
-
# state_machine :initial => lambda {|vehicle| vehicle.force_idle ?
|
338
|
+
# state_machine :initial => lambda {|vehicle| vehicle.force_idle ? :idling : :parked} do
|
406
339
|
# ...
|
407
340
|
# end
|
408
341
|
# end
|
@@ -410,15 +343,116 @@ module StateMachine
|
|
410
343
|
# vehicle = Vehicle.new
|
411
344
|
#
|
412
345
|
# vehicle.force_idle = true
|
413
|
-
# Vehicle.state_machines[
|
346
|
+
# Vehicle.state_machines[:state].initial_state(vehicle) # => #<StateMachine::State name=:idling value="idling" initial=false>
|
414
347
|
#
|
415
348
|
# vehicle.force_idle = false
|
416
|
-
# Vehicle.state_machines[
|
349
|
+
# Vehicle.state_machines[:state].initial_state(vehicle) # => #<StateMachine::State name=:parked value="parked" initial=false>
|
417
350
|
def initial_state(object)
|
418
|
-
@initial_state
|
351
|
+
states.fetch(@initial_state.is_a?(Proc) ? @initial_state.call(object) : @initial_state)
|
419
352
|
end
|
420
353
|
|
421
|
-
#
|
354
|
+
# Customizes the definition of one or more states in the machine.
|
355
|
+
#
|
356
|
+
# Configuration options:
|
357
|
+
# * <tt>:value</tt> - The actual value to store when an object transitions
|
358
|
+
# to the state. Default is the name (stringified).
|
359
|
+
# * <tt>:if</tt> - Determines whether an object's value matches the state
|
360
|
+
# (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}).
|
361
|
+
# By default, the configured value is matched.
|
362
|
+
#
|
363
|
+
# == Customizing the stored value
|
364
|
+
#
|
365
|
+
# Whenever a state is automatically discovered in the state machine, its
|
366
|
+
# default value is assumed to be the stringified version of the name. For
|
367
|
+
# example,
|
368
|
+
#
|
369
|
+
# class Vehicle
|
370
|
+
# state_machine :initial => :parked do
|
371
|
+
# event :ignite do
|
372
|
+
# transition :to => :idling, :from => :parked
|
373
|
+
# end
|
374
|
+
# end
|
375
|
+
# end
|
376
|
+
#
|
377
|
+
# In the above state machine, there are two states automatically discovered:
|
378
|
+
# :parked and :idling. These states, by default, will store their stringified
|
379
|
+
# equivalents when an object moves into that states (e.g. "parked" / "idling").
|
380
|
+
#
|
381
|
+
# For legacy systems or when tying state machines into existing frameworks,
|
382
|
+
# it's oftentimes necessary to need to store a different value for a state
|
383
|
+
# than the default. In order to continue taking advantage of an expressive
|
384
|
+
# state machine and helper methods, every defined state can be re-configured
|
385
|
+
# with a custom stored value. For example,
|
386
|
+
#
|
387
|
+
# class Vehicle
|
388
|
+
# state_machine :initial => :parked do
|
389
|
+
# event :ignite do
|
390
|
+
# transition :to => :idling, :from => :parked
|
391
|
+
# end
|
392
|
+
#
|
393
|
+
# state :idling, :value => 'IDLING'
|
394
|
+
# state :parked, :value => 'PARKED
|
395
|
+
# end
|
396
|
+
# end
|
397
|
+
#
|
398
|
+
# This is also useful if being used in association with a database and,
|
399
|
+
# instead of storing the state name in a column, you want to store the
|
400
|
+
# state's foreign key:
|
401
|
+
#
|
402
|
+
# class VehicleState < ActiveRecord::Base
|
403
|
+
# end
|
404
|
+
#
|
405
|
+
# class Vehicle < ActiveRecord::Base
|
406
|
+
# state_machine :state_id, :initial => :parked do
|
407
|
+
# event :ignite do
|
408
|
+
# transition :to => :idling, :from => :parked
|
409
|
+
# end
|
410
|
+
#
|
411
|
+
# states.each {|state| self.state(state.name, :value => VehicleState.find_by_name(state.name.to_s).id)}
|
412
|
+
# end
|
413
|
+
# end
|
414
|
+
#
|
415
|
+
# In the above example, each known state is configured to store it's
|
416
|
+
# associated database id in the +state_id+ attribute.
|
417
|
+
#
|
418
|
+
# === Dynamic values
|
419
|
+
#
|
420
|
+
# In addition to customizing states with other value types, lambda blocks
|
421
|
+
# can also be specified to allow for a state's value to be determined
|
422
|
+
# dynamically at runtime. For example,
|
423
|
+
#
|
424
|
+
# class Vehicle
|
425
|
+
# state_machine :purchased_at, :initial => :available do
|
426
|
+
# event :purchase do
|
427
|
+
# transition :to => :purchased
|
428
|
+
# end
|
429
|
+
#
|
430
|
+
# event :restock do
|
431
|
+
# transition :to => :available
|
432
|
+
# end
|
433
|
+
#
|
434
|
+
# state :available, :value => nil
|
435
|
+
# state :purchased, :if => lambda {|value| !value.nil?}, :value => lambda {Time.now}
|
436
|
+
# end
|
437
|
+
# end
|
438
|
+
#
|
439
|
+
# In the above definition, the <tt>:purchased</tt> state is customized with
|
440
|
+
# both a dynamic value *and* a value matcher.
|
441
|
+
#
|
442
|
+
# When an object transitions to the purchased state, the value's lambda
|
443
|
+
# block will be called. This will get the current time and store it in the
|
444
|
+
# object's +purchased_at+ attribute.
|
445
|
+
#
|
446
|
+
# *Note* that the custom matcher is very important here. Since there's no
|
447
|
+
# way for the state machine to figure out an object's state when it's set to
|
448
|
+
# a runtime value, it must be explicitly defined. If the <tt>:if</tt> option
|
449
|
+
# were not configured for the state, then an ArgumentError exception would
|
450
|
+
# be raised at runtime, indicating that the state machine could not figure
|
451
|
+
# out what the current state of the object was.
|
452
|
+
#
|
453
|
+
# == Behaviors
|
454
|
+
#
|
455
|
+
# Behaviors defined a series of methods to mixin with objects when the current
|
422
456
|
# state matches the given one(s). This allows instance methods to behave
|
423
457
|
# a specific way depending on what the value of the object's state is.
|
424
458
|
#
|
@@ -428,12 +462,12 @@ module StateMachine
|
|
428
462
|
# attr_accessor :driver
|
429
463
|
# attr_accessor :passenger
|
430
464
|
#
|
431
|
-
# state_machine :initial =>
|
465
|
+
# state_machine :initial => :parked do
|
432
466
|
# event :ignite do
|
433
|
-
# transition :to =>
|
467
|
+
# transition :to => :idling, :from => :parked
|
434
468
|
# end
|
435
469
|
#
|
436
|
-
# state
|
470
|
+
# state :parked do
|
437
471
|
# def speed
|
438
472
|
# 0
|
439
473
|
# end
|
@@ -446,16 +480,18 @@ module StateMachine
|
|
446
480
|
# end
|
447
481
|
# end
|
448
482
|
#
|
449
|
-
# state
|
483
|
+
# state :idling, :first_gear do
|
450
484
|
# def speed
|
451
485
|
# 20
|
452
486
|
# end
|
453
487
|
#
|
454
488
|
# def rotate_driver
|
455
|
-
# self.state =
|
489
|
+
# self.state = 'parked'
|
456
490
|
# rotate_driver
|
457
491
|
# end
|
458
492
|
# end
|
493
|
+
#
|
494
|
+
# other_states :backing_up
|
459
495
|
# end
|
460
496
|
# end
|
461
497
|
#
|
@@ -493,7 +529,7 @@ module StateMachine
|
|
493
529
|
# implementations changed how they behave based on what the current state
|
494
530
|
# of the vehicle was.
|
495
531
|
#
|
496
|
-
#
|
532
|
+
# === Invalid behaviors
|
497
533
|
#
|
498
534
|
# If a specific behavior has not been defined for a state, then a
|
499
535
|
# NoMethodError exception will be raised, indicating that that method would
|
@@ -502,66 +538,79 @@ module StateMachine
|
|
502
538
|
# Using the example from before:
|
503
539
|
#
|
504
540
|
# vehicle = Vehicle.new
|
505
|
-
# vehicle.state =
|
541
|
+
# vehicle.state = 'backing_up'
|
506
542
|
# vehicle.speed # => NoMethodError: undefined method 'speed' for #<Vehicle:0xb7d296ac> in state "backing_up"
|
507
|
-
def state(*
|
508
|
-
|
509
|
-
|
543
|
+
def state(*names, &block)
|
544
|
+
options = names.last.is_a?(Hash) ? names.pop : {}
|
545
|
+
assert_valid_keys(options, :value, :if)
|
546
|
+
|
547
|
+
states = add_states(names)
|
548
|
+
states.each do |state|
|
549
|
+
if options.include?(:value)
|
550
|
+
state.value = options[:value]
|
551
|
+
self.states.update(state)
|
552
|
+
end
|
553
|
+
|
554
|
+
state.matcher = options[:if] if options.include?(:if)
|
555
|
+
state.context(&block) if block_given?
|
556
|
+
end
|
557
|
+
|
510
558
|
states.length == 1 ? states.first : states
|
511
559
|
end
|
560
|
+
alias_method :other_states, :state
|
512
561
|
|
513
|
-
#
|
514
|
-
#
|
515
|
-
#
|
516
|
-
#
|
517
|
-
# * Included in GraphViz visualizations
|
518
|
-
# * Used in :except_from and :except_to transition/callback conditionals
|
562
|
+
# Determines whether the given object is in a specific state. If the
|
563
|
+
# object's current value doesn't match the state, then this will return
|
564
|
+
# false, otherwise true. If the given state is unknown, then an ArgumentError
|
565
|
+
# exception will be raised.
|
519
566
|
#
|
520
|
-
# ==
|
567
|
+
# == Examples
|
521
568
|
#
|
522
569
|
# class Vehicle
|
523
|
-
# state_machine :initial =>
|
524
|
-
#
|
525
|
-
# transition :to => 'idling', :from => 'parked'
|
526
|
-
# end
|
527
|
-
#
|
528
|
-
# other_states %w(stalled stopped)
|
529
|
-
# end
|
530
|
-
#
|
531
|
-
# def stop
|
532
|
-
# self.state = 'stopped'
|
570
|
+
# state_machine :initial => :parked do
|
571
|
+
# other_states :idling
|
533
572
|
# end
|
534
573
|
# end
|
535
|
-
#
|
536
|
-
#
|
537
|
-
#
|
538
|
-
#
|
539
|
-
#
|
540
|
-
#
|
541
|
-
#
|
542
|
-
|
543
|
-
|
544
|
-
def other_states(*args)
|
545
|
-
add_states(args.flatten)
|
574
|
+
#
|
575
|
+
# machine = Vehicle.state_machines[:state]
|
576
|
+
# vehicle = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
|
577
|
+
#
|
578
|
+
# machine.state?(vehicle, :parked) # => true
|
579
|
+
# machine.state?(vehicle, :idling) # => false
|
580
|
+
# machine.state?(vehicle, :invalid) # => ArgumentError: :invalid is an invalid key for :name index
|
581
|
+
def state?(object, name)
|
582
|
+
states.fetch(name).matches?(object.send(attribute))
|
546
583
|
end
|
547
584
|
|
548
|
-
#
|
549
|
-
#
|
550
|
-
#
|
551
|
-
#
|
552
|
-
#
|
553
|
-
#
|
554
|
-
#
|
555
|
-
#
|
556
|
-
#
|
557
|
-
#
|
558
|
-
|
559
|
-
|
585
|
+
# Determines the current state of the given object as configured by this
|
586
|
+
# state machine. This will attempt to find a known state that matches
|
587
|
+
# the value of the attribute on the object. If no state is found, then
|
588
|
+
# an ArgumentError will be raised.
|
589
|
+
#
|
590
|
+
# == Examples
|
591
|
+
#
|
592
|
+
# class Vehicle
|
593
|
+
# state_machine :initial => :parked do
|
594
|
+
# other_states :idling
|
595
|
+
# end
|
596
|
+
# end
|
597
|
+
#
|
598
|
+
# machine = Vehicle.state_machines[:state]
|
599
|
+
#
|
600
|
+
# vehicle = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
|
601
|
+
# machine.state_for(vehicle) # => #<StateMachine::State name=:parked value="parked" initial=true>
|
602
|
+
#
|
603
|
+
# vehicle.state = 'idling'
|
604
|
+
# machine.state_for(vehicle) # => #<StateMachine::State name=:idling value="idling" initial=true>
|
605
|
+
#
|
606
|
+
# vehicle.state = 'invalid'
|
607
|
+
# machine.state_for(vehicle) # => ArgumentError: "invalid" is not a known state value
|
608
|
+
def state_for(object)
|
609
|
+
value = object.send(attribute)
|
610
|
+
state = states[value, :value] || states.detect {|state| state.matches?(value)}
|
611
|
+
raise ArgumentError, "#{value.inspect} is not a known #{attribute} value" unless state
|
560
612
|
|
561
|
-
|
562
|
-
order |= states.select {|value, state| state.methods.any?}.map {|state| state.first}
|
563
|
-
order |= states.keys - callbacks.values.flatten.map {|callback| callback.known_states}.flatten
|
564
|
-
order |= states.keys
|
613
|
+
state
|
565
614
|
end
|
566
615
|
|
567
616
|
# Defines one or more events for the machine and the transitions that can
|
@@ -571,10 +620,16 @@ module StateMachine
|
|
571
620
|
#
|
572
621
|
# The following instance methods are generated when a new event is defined
|
573
622
|
# (the "park" event is used as an example):
|
574
|
-
# * <tt>can_park?</tt> - Checks whether the "park" event can be fired given
|
575
|
-
#
|
576
|
-
# * <tt>
|
577
|
-
#
|
623
|
+
# * <tt>can_park?</tt> - Checks whether the "park" event can be fired given
|
624
|
+
# the current state of the object.
|
625
|
+
# * <tt>next_park_transition</tt> - Gets the next transition that would be
|
626
|
+
# performed if the "park" event were to be fired now on the object or nil
|
627
|
+
# if no transitions can be performed.
|
628
|
+
# * <tt>park(run_action = true)</tt> - Fires the "park" event, transitioning
|
629
|
+
# from the current state to the next valid state.
|
630
|
+
# * <tt>park!(run_action = true)</tt> - Fires the "park" event, transitioning
|
631
|
+
# from the current state to the next valid state. If the transition fails,
|
632
|
+
# then a StateMachine::InvalidTransition error will be raised.
|
578
633
|
#
|
579
634
|
# With a namespace of "car", the above names map to the following methods:
|
580
635
|
# * <tt>can_park_car?</tt>
|
@@ -588,11 +643,11 @@ module StateMachine
|
|
588
643
|
# transitions that can happen as a result of that event. For example,
|
589
644
|
#
|
590
645
|
# event :park, :stop do
|
591
|
-
# transition :to =>
|
646
|
+
# transition :to => :parked, :from => :idling
|
592
647
|
# end
|
593
648
|
#
|
594
649
|
# event :first_gear do
|
595
|
-
# transition :to =>
|
650
|
+
# transition :to => :first_gear, :from => :parked, :if => :seatbelt_on?
|
596
651
|
# end
|
597
652
|
#
|
598
653
|
# See StateMachine::Event#transition for more information on
|
@@ -604,12 +659,12 @@ module StateMachine
|
|
604
659
|
#
|
605
660
|
# class Vehicle
|
606
661
|
# def self.safe_states
|
607
|
-
#
|
662
|
+
# [:parked, :idling, :stalled]
|
608
663
|
# end
|
609
664
|
#
|
610
665
|
# state_machine do
|
611
666
|
# event :park do
|
612
|
-
# transition :to =>
|
667
|
+
# transition :to => :parked, :from => Vehicle.safe_states
|
613
668
|
# end
|
614
669
|
# end
|
615
670
|
# end
|
@@ -620,23 +675,23 @@ module StateMachine
|
|
620
675
|
# state_machine do
|
621
676
|
# # The park, stop, and halt events will all share the given transitions
|
622
677
|
# event :park, :stop, :halt do
|
623
|
-
# transition :to =>
|
678
|
+
# transition :to => :parked, :from => [:idling, :backing_up]
|
624
679
|
# end
|
625
680
|
#
|
626
681
|
# event :stop do
|
627
|
-
# transition :to =>
|
682
|
+
# transition :to => :idling, :from => :first_gear
|
628
683
|
# end
|
629
684
|
#
|
630
685
|
# event :ignite do
|
631
|
-
# transition :to =>
|
686
|
+
# transition :to => :idling, :from => :parked
|
632
687
|
# end
|
633
688
|
# end
|
634
689
|
# end
|
635
690
|
def event(*names, &block)
|
636
691
|
events = names.collect do |name|
|
637
|
-
|
638
|
-
|
639
|
-
|
692
|
+
unless event = self.events[name]
|
693
|
+
self.events << event = Event.new(self, name)
|
694
|
+
end
|
640
695
|
|
641
696
|
if block_given?
|
642
697
|
event.instance_eval(&block)
|
@@ -655,15 +710,25 @@ module StateMachine
|
|
655
710
|
# order for the callback to get invoked.
|
656
711
|
#
|
657
712
|
# Configuration options:
|
658
|
-
# *
|
659
|
-
#
|
660
|
-
# *
|
661
|
-
#
|
662
|
-
# *
|
663
|
-
#
|
664
|
-
# *
|
665
|
-
# *
|
666
|
-
# *
|
713
|
+
# * <tt>:from</tt> - One or more states being transitioned from. If none
|
714
|
+
# are specified, then all states will match.
|
715
|
+
# * <tt>:to</tt> - One or more states being transitioned to. If none are
|
716
|
+
# specified, then all states will match.
|
717
|
+
# * <tt>:on</tt> - One or more events that fired the transition. If none
|
718
|
+
# are specified, then all events will match.
|
719
|
+
# * <tt>:except_from</tt> - One or more states *not* being transitioned from
|
720
|
+
# * <tt>:except_to</tt> - One more states *not* being transitioned to
|
721
|
+
# * <tt>:except_on</tt> - One or more events that *did not* fire the transition
|
722
|
+
# * <tt>:do</tt> - The callback to invoke when a transition matches. This
|
723
|
+
# can be a method, proc or string.
|
724
|
+
# * <tt>:if</tt> - A method, proc or string to call to determine if the
|
725
|
+
# callback should occur (e.g. :if => :allow_callbacks, or
|
726
|
+
# :if => lambda {|user| user.signup_step > 2}). The method, proc or string
|
727
|
+
# should return or evaluate to a true or false value.
|
728
|
+
# * <tt>:unless</tt> - A method, proc or string to call to determine if the
|
729
|
+
# callback should not occur (e.g. :unless => :skip_callbacks, or
|
730
|
+
# :unless => lambda {|user| user.signup_step <= 2}). The method, proc or
|
731
|
+
# string should return or evaluate to a true or false value.
|
667
732
|
#
|
668
733
|
# The +except+ group of options (+except_to+, +exception_from+, and
|
669
734
|
# +except_on+) acts as the +unless+ equivalent of their counterparts (+to+,
|
@@ -676,8 +741,8 @@ module StateMachine
|
|
676
741
|
#
|
677
742
|
# class Vehicle
|
678
743
|
# state_machine do
|
679
|
-
# before_transition :to =>
|
680
|
-
# before_transition :to =>
|
744
|
+
# before_transition :to => :parked, :do => :set_alarm
|
745
|
+
# before_transition :to => :parked do |vehicle, transition|
|
681
746
|
# vehicle.set_alarm
|
682
747
|
# end
|
683
748
|
# ...
|
@@ -687,18 +752,18 @@ module StateMachine
|
|
687
752
|
# === Accessing the transition
|
688
753
|
#
|
689
754
|
# In addition to passing the object being transitioned, the actual
|
690
|
-
# transition describing the context (e.g. event, from
|
691
|
-
#
|
692
|
-
#
|
755
|
+
# transition describing the context (e.g. event, from, to) can be accessed
|
756
|
+
# as well. This additional argument is only passed if the callback allows
|
757
|
+
# for it.
|
693
758
|
#
|
694
759
|
# For example,
|
695
760
|
#
|
696
761
|
# class Vehicle
|
697
762
|
# # Only specifies one parameter (the object being transitioned)
|
698
|
-
# before_transition :to =>
|
763
|
+
# before_transition :to => :parked, :do => lambda {|vehicle| vehicle.set_alarm}
|
699
764
|
#
|
700
765
|
# # Specifies 2 parameters (object being transitioned and actual transition)
|
701
|
-
# before_transition :to =>
|
766
|
+
# before_transition :to => :parked, :do => lambda {|vehicle, transition| vehicle.set_alarm(transition)}
|
702
767
|
# end
|
703
768
|
#
|
704
769
|
# *Note* that the object in the callback will only be passed in as an
|
@@ -719,13 +784,13 @@ module StateMachine
|
|
719
784
|
# before_transition :update_dashboard
|
720
785
|
#
|
721
786
|
# # Before specific transition:
|
722
|
-
# before_transition :to =>
|
787
|
+
# before_transition :to => :parked, :from => [:first_gear, :idling], :on => :park, :do => :take_off_seatbelt
|
723
788
|
#
|
724
789
|
# # With conditional callback:
|
725
|
-
# before_transition :to =>
|
790
|
+
# before_transition :to => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on?
|
726
791
|
#
|
727
792
|
# # Using :except counterparts:
|
728
|
-
# before_transition :except_to =>
|
793
|
+
# before_transition :except_to => :stalled, :except_from => :stalled, :except_on => :crash, :do => :update_dashboard
|
729
794
|
# ...
|
730
795
|
# end
|
731
796
|
# end
|
@@ -742,15 +807,25 @@ module StateMachine
|
|
742
807
|
# in order for the callback to get invoked.
|
743
808
|
#
|
744
809
|
# Configuration options:
|
745
|
-
# *
|
746
|
-
#
|
747
|
-
# *
|
748
|
-
#
|
749
|
-
# *
|
750
|
-
#
|
751
|
-
# *
|
752
|
-
# *
|
753
|
-
# *
|
810
|
+
# * <tt>:from</tt> - One or more states being transitioned from. If none
|
811
|
+
# are specified, then all states will match.
|
812
|
+
# * <tt>:to</tt> - One or more states being transitioned to. If none are
|
813
|
+
# specified, then all states will match.
|
814
|
+
# * <tt>:on</tt> - One or more events that fired the transition. If none
|
815
|
+
# are specified, then all events will match.
|
816
|
+
# * <tt>:except_from</tt> - One or more states *not* being transitioned from
|
817
|
+
# * <tt>:except_to</tt> - One more states *not* being transitioned to
|
818
|
+
# * <tt>:except_on</tt> - One or more events that *did not* fire the transition
|
819
|
+
# * <tt>:do</tt> - The callback to invoke when a transition matches. This
|
820
|
+
# can be a method, proc or string.
|
821
|
+
# * <tt>:if</tt> - A method, proc or string to call to determine if the
|
822
|
+
# callback should occur (e.g. :if => :allow_callbacks, or
|
823
|
+
# :if => lambda {|user| user.signup_step > 2}). The method, proc or string
|
824
|
+
# should return or evaluate to a true or false value.
|
825
|
+
# * <tt>:unless</tt> - A method, proc or string to call to determine if the
|
826
|
+
# callback should not occur (e.g. :unless => :skip_callbacks, or
|
827
|
+
# :unless => lambda {|user| user.signup_step <= 2}). The method, proc or
|
828
|
+
# string should return or evaluate to a true or false value.
|
754
829
|
#
|
755
830
|
# The +except+ group of options (+except_to+, +exception_from+, and
|
756
831
|
# +except_on+) acts as the +unless+ equivalent of their counterparts (+to+,
|
@@ -763,8 +838,8 @@ module StateMachine
|
|
763
838
|
#
|
764
839
|
# class Vehicle
|
765
840
|
# state_machine do
|
766
|
-
# after_transition :to =>
|
767
|
-
# after_transition :to =>
|
841
|
+
# after_transition :to => :parked, :do => :set_alarm
|
842
|
+
# after_transition :to => :parked do |vehicle, transition, result|
|
768
843
|
# vehicle.set_alarm
|
769
844
|
# end
|
770
845
|
# ...
|
@@ -774,19 +849,18 @@ module StateMachine
|
|
774
849
|
# === Accessing the transition / result
|
775
850
|
#
|
776
851
|
# In addition to passing the object being transitioned, the actual
|
777
|
-
# transition describing the context (e.g. event, from
|
778
|
-
#
|
779
|
-
#
|
780
|
-
# for it.
|
852
|
+
# transition describing the context (e.g. event, from, to) and the result
|
853
|
+
# from calling the object's action can be optionally passed as well. These
|
854
|
+
# additional arguments are only passed if the callback allows for it.
|
781
855
|
#
|
782
856
|
# For example,
|
783
857
|
#
|
784
858
|
# class Vehicle
|
785
859
|
# # Only specifies one parameter (the object being transitioned)
|
786
|
-
# after_transition :to =>
|
860
|
+
# after_transition :to => :parked, :do => lambda {|vehicle| vehicle.set_alarm}
|
787
861
|
#
|
788
862
|
# # Specifies 3 parameters (object being transitioned, transition, and action result)
|
789
|
-
# after_transition :to =>
|
863
|
+
# after_transition :to => :parked, :do => lambda {|vehicle, transition, result| vehicle.set_alarm(transition) if result}
|
790
864
|
# end
|
791
865
|
#
|
792
866
|
# *Note* that the object in the callback will only be passed in as an
|
@@ -807,13 +881,13 @@ module StateMachine
|
|
807
881
|
# after_transition :update_dashboard
|
808
882
|
#
|
809
883
|
# # After specific transition:
|
810
|
-
# after_transition :to =>
|
884
|
+
# after_transition :to => :parked, :from => [:first_gear, :idling], :on => :park, :do => :take_off_seatbelt
|
811
885
|
#
|
812
886
|
# # With conditional callback:
|
813
|
-
# after_transition :to =>
|
887
|
+
# after_transition :to => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on?
|
814
888
|
#
|
815
889
|
# # Using :except counterparts:
|
816
|
-
# after_transition :except_to =>
|
890
|
+
# after_transition :except_to => :stalled, :except_from => :stalled, :except_on => :crash, :do => :update_dashboard
|
817
891
|
# ...
|
818
892
|
# end
|
819
893
|
# end
|
@@ -840,10 +914,14 @@ module StateMachine
|
|
840
914
|
# installed on the system.
|
841
915
|
#
|
842
916
|
# Configuration options:
|
843
|
-
# *
|
844
|
-
#
|
845
|
-
# *
|
846
|
-
#
|
917
|
+
# * <tt>:name</tt> - The name of the file to write to (without the file extension).
|
918
|
+
# Default is "#{owner_class.name}_#{attribute}"
|
919
|
+
# * <tt>:path</tt> - The path to write the graph file to. Default is the
|
920
|
+
# current directory (".").
|
921
|
+
# * <tt>:format</tt> - The image format to generate the graph in.
|
922
|
+
# Default is "png'.
|
923
|
+
# * <tt>:font</tt> - The name of the font to draw state names in.
|
924
|
+
# Default is "Arial".
|
847
925
|
def draw(options = {})
|
848
926
|
options = {
|
849
927
|
:name => "#{owner_class.name}_#{attribute}",
|
@@ -861,13 +939,13 @@ module StateMachine
|
|
861
939
|
graph = GraphViz.new('G', :output => options[:format], :file => File.join(options[:path], "#{options[:name]}.#{options[:format]}"))
|
862
940
|
|
863
941
|
# Add nodes
|
864
|
-
|
942
|
+
states.by_priority.each do |state|
|
865
943
|
node = state.draw(graph)
|
866
944
|
node.fontname = options[:font]
|
867
945
|
end
|
868
946
|
|
869
947
|
# Add edges
|
870
|
-
|
948
|
+
events.each do |event|
|
871
949
|
edges = event.draw(graph)
|
872
950
|
edges.each {|edge| edge.fontname = options[:font]}
|
873
951
|
end
|
@@ -892,7 +970,7 @@ module StateMachine
|
|
892
970
|
def default_action
|
893
971
|
end
|
894
972
|
|
895
|
-
# Adds reader/writer/
|
973
|
+
# Adds reader/writer/predicate methods for accessing the attribute that
|
896
974
|
# this state machine is defined for.
|
897
975
|
def define_attribute_accessor
|
898
976
|
attribute = self.attribute
|
@@ -901,12 +979,15 @@ module StateMachine
|
|
901
979
|
attr_reader attribute unless method_defined?(attribute) || private_method_defined?(attribute)
|
902
980
|
attr_writer attribute unless method_defined?("#{attribute}=") || private_method_defined?("#{attribute}=")
|
903
981
|
|
904
|
-
# Checks whether the current state is a given value
|
905
|
-
# is not a known state, then an ArgumentError is raised.
|
982
|
+
# Checks whether the current state is a given value
|
906
983
|
define_method("#{attribute}?") do |state|
|
907
|
-
|
908
|
-
send(attribute) == state
|
984
|
+
self.class.state_machines[attribute].state?(self, state)
|
909
985
|
end unless method_defined?("#{attribute}?") || private_method_defined?("#{attribute}?")
|
986
|
+
|
987
|
+
# Gets the state name for the current value
|
988
|
+
define_method("#{attribute}_name") do
|
989
|
+
self.class.state_machines[attribute].state_for(self).name
|
990
|
+
end
|
910
991
|
end
|
911
992
|
end
|
912
993
|
|
@@ -916,39 +997,61 @@ module StateMachine
|
|
916
997
|
# automatically determined by either calling +pluralize+ on the attribute
|
917
998
|
# name or adding an "s" to the end of the name.
|
918
999
|
def define_scopes(custom_plural = nil)
|
1000
|
+
attribute = self.attribute
|
919
1001
|
plural = custom_plural || (attribute.respond_to?(:pluralize) ? attribute.pluralize : "#{attribute}s")
|
920
1002
|
|
921
1003
|
[attribute, plural].uniq.each do |name|
|
922
|
-
|
923
|
-
|
1004
|
+
[:with, :without].each do |kind|
|
1005
|
+
method = "#{kind}_#{name}"
|
1006
|
+
|
1007
|
+
if !owner_class.respond_to?(method) && scope = send("create_#{kind}_scope", method)
|
1008
|
+
(class << owner_class; self; end).class_eval do
|
1009
|
+
# Converts state names to their corresponding values so that
|
1010
|
+
# they can be looked up properly
|
1011
|
+
define_method(method) do |*states|
|
1012
|
+
machine_states = state_machines[attribute].states
|
1013
|
+
values = states.flatten.map {|state| machine_states.fetch(state).value}
|
1014
|
+
|
1015
|
+
# Invoke the original scope implementation
|
1016
|
+
scope.call(self, values)
|
1017
|
+
end
|
1018
|
+
end
|
1019
|
+
end
|
1020
|
+
end
|
924
1021
|
end
|
925
1022
|
end
|
926
1023
|
|
927
|
-
#
|
928
|
-
#
|
1024
|
+
# Creates a scope for finding objects *with* a particular value or values
|
1025
|
+
# for the attribute.
|
929
1026
|
#
|
930
1027
|
# This is only applicable to specific integrations.
|
931
|
-
def
|
1028
|
+
def create_with_scope(name)
|
932
1029
|
end
|
933
1030
|
|
934
|
-
#
|
1031
|
+
# Creates a scope for finding objects *without* a particular value or
|
935
1032
|
# values for the attribute.
|
936
1033
|
#
|
937
1034
|
# This is only applicable to specific integrations.
|
938
|
-
def
|
1035
|
+
def create_without_scope(name)
|
939
1036
|
end
|
940
1037
|
|
941
1038
|
# Adds a new transition callback of the given type.
|
942
1039
|
def add_callback(type, options, &block)
|
943
|
-
|
1040
|
+
callbacks[type] << callback = Callback.new(options, &block)
|
944
1041
|
add_states(callback.known_states)
|
945
1042
|
callback
|
946
1043
|
end
|
947
1044
|
|
948
1045
|
# Tracks the given set of states in the list of all known states for
|
949
1046
|
# this machine
|
950
|
-
def add_states(
|
951
|
-
|
1047
|
+
def add_states(new_states)
|
1048
|
+
new_states.collect do |new_state|
|
1049
|
+
unless state = states[new_state]
|
1050
|
+
states << state = State.new(self, new_state)
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
state
|
1054
|
+
end
|
952
1055
|
end
|
953
1056
|
end
|
954
1057
|
end
|