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