state_machine 0.5.2 → 0.6.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 +9 -0
- data/README.rdoc +34 -26
- data/Rakefile +1 -1
- data/examples/auto_shop.rb +2 -2
- data/examples/car.rb +4 -4
- data/examples/traffic_light.rb +1 -3
- data/examples/vehicle.rb +7 -11
- data/lib/state_machine.rb +5 -5
- data/lib/state_machine/callback.rb +1 -1
- data/lib/state_machine/condition_proxy.rb +94 -0
- data/lib/state_machine/event.rb +92 -23
- data/lib/state_machine/guard.rb +90 -57
- data/lib/state_machine/integrations/active_record.rb +3 -3
- data/lib/state_machine/integrations/data_mapper.rb +3 -3
- data/lib/state_machine/integrations/data_mapper/observer.rb +8 -6
- data/lib/state_machine/integrations/sequel.rb +3 -3
- data/lib/state_machine/machine.rb +136 -130
- data/lib/state_machine/matcher_helpers.rb +53 -0
- data/lib/state_machine/state.rb +3 -3
- data/test/active_record.log +23221 -0
- data/test/classes/switch.rb +2 -2
- data/test/functional/state_machine_test.rb +25 -29
- data/test/sequel.log +10610 -0
- data/test/unit/callback_test.rb +36 -6
- data/test/unit/condition_proxy_test.rb +301 -0
- data/test/unit/event_test.rb +31 -17
- data/test/unit/guard_test.rb +246 -13
- data/test/unit/integrations/active_record_test.rb +29 -0
- data/test/unit/integrations/data_mapper_test.rb +45 -0
- data/test/unit/integrations/sequel_test.rb +29 -0
- data/test/unit/machine_test.rb +25 -3
- data/test/unit/matcher_helpers_test.rb +37 -0
- data/test/unit/node_collection_test.rb +0 -4
- data/test/unit/state_collection_test.rb +0 -4
- metadata +8 -2
data/CHANGELOG.rdoc
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
== master
|
2
2
|
|
3
|
+
== 0.6.0 / 2009-03-03
|
4
|
+
|
5
|
+
* Allow multiple conditions for callbacks / class behaviors
|
6
|
+
* Add support for state-driven class behavior with :if/:unless options
|
7
|
+
* Alias Machine#event as Machine#on
|
8
|
+
* Fix nil from/to states not being handled properly
|
9
|
+
* Simplify hooking callbacks into loopbacks
|
10
|
+
* Add simplified transition/callback requirement syntax
|
11
|
+
|
3
12
|
== 0.5.2 / 2009-02-17
|
4
13
|
|
5
14
|
* Improve pretty-print of events
|
data/README.rdoc
CHANGED
@@ -42,7 +42,7 @@ Some brief, high-level features include:
|
|
42
42
|
* DataMapper integration
|
43
43
|
* Sequel integration
|
44
44
|
* State predicates
|
45
|
-
* State-driven behavior
|
45
|
+
* State-driven instance / class behavior
|
46
46
|
* State values of any data type
|
47
47
|
* Dynamically-generated state values
|
48
48
|
* Inheritance
|
@@ -60,7 +60,7 @@ Below is an example of many of the features offered by this plugin, including:
|
|
60
60
|
* Namespaced states
|
61
61
|
* Transition callbacks
|
62
62
|
* Conditional transitions
|
63
|
-
* State-driven behavior
|
63
|
+
* State-driven instance behavior
|
64
64
|
* Customized state values
|
65
65
|
|
66
66
|
Class definition:
|
@@ -69,43 +69,39 @@ Class definition:
|
|
69
69
|
attr_accessor :seatbelt_on
|
70
70
|
|
71
71
|
state_machine :state, :initial => :parked do
|
72
|
-
before_transition
|
72
|
+
before_transition any => [:parked, :idling], :do => :put_on_seatbelt
|
73
73
|
after_transition :on => :crash, :do => :tow
|
74
74
|
after_transition :on => :repair, :do => :fix
|
75
|
-
after_transition
|
75
|
+
after_transition any => :parked do |vehicle, transition|
|
76
76
|
vehicle.seatbelt_on = false
|
77
77
|
end
|
78
78
|
|
79
79
|
event :park do
|
80
|
-
transition :
|
80
|
+
transition [:idling, :first_gear] => :parked
|
81
81
|
end
|
82
82
|
|
83
83
|
event :ignite do
|
84
|
-
transition :
|
85
|
-
transition :to => :idling, :from => :parked
|
84
|
+
transition :stalled => :stalled, :parked => :idling
|
86
85
|
end
|
87
86
|
|
88
87
|
event :idle do
|
89
|
-
transition :
|
88
|
+
transition :first_gear => :idling
|
90
89
|
end
|
91
90
|
|
92
91
|
event :shift_up do
|
93
|
-
transition :
|
94
|
-
transition :to => :second_gear, :from => :first_gear
|
95
|
-
transition :to => :third_gear, :from => :second_gear
|
92
|
+
transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
|
96
93
|
end
|
97
94
|
|
98
95
|
event :shift_down do
|
99
|
-
transition :
|
100
|
-
transition :to => :first_gear, :from => :second_gear
|
96
|
+
transition :third_gear => :second_gear, :second_gear => :first_gear
|
101
97
|
end
|
102
98
|
|
103
99
|
event :crash do
|
104
|
-
transition
|
100
|
+
transition [:first_gear, :second_gear, :third_gear] => :stalled, :unless => :auto_shop_busy?
|
105
101
|
end
|
106
102
|
|
107
103
|
event :repair do
|
108
|
-
transition :
|
104
|
+
transition :stalled => :parked, :if => :auto_shop_busy?
|
109
105
|
end
|
110
106
|
|
111
107
|
state :parked do
|
@@ -129,11 +125,11 @@ Class definition:
|
|
129
125
|
|
130
126
|
state_machine :hood_state, :initial => :closed, :namespace => 'hood' do
|
131
127
|
event :open do
|
132
|
-
transition
|
128
|
+
transition all => :opened
|
133
129
|
end
|
134
130
|
|
135
131
|
event :close do
|
136
|
-
transition
|
132
|
+
transition all => :closed
|
137
133
|
end
|
138
134
|
|
139
135
|
state :opened, :value => 1
|
@@ -232,13 +228,17 @@ saving the record, named scopes, and observers. For example,
|
|
232
228
|
|
233
229
|
class Vehicle < ActiveRecord::Base
|
234
230
|
state_machine :initial => :parked do
|
235
|
-
before_transition
|
236
|
-
after_transition
|
231
|
+
before_transition any => :idling, :do => :put_on_seatbelt
|
232
|
+
after_transition any => :parked do |vehicle, transition|
|
237
233
|
vehicle.seatbelt = 'off'
|
238
234
|
end
|
239
235
|
|
240
236
|
event :ignite do
|
241
|
-
transition :
|
237
|
+
transition :parked => :idling
|
238
|
+
end
|
239
|
+
|
240
|
+
state :first_gear, :second_gear do
|
241
|
+
validates_presence_of :seatbelt_on
|
242
242
|
end
|
243
243
|
end
|
244
244
|
|
@@ -275,13 +275,17 @@ callbacks, and observers. For example,
|
|
275
275
|
property :state, String
|
276
276
|
|
277
277
|
state_machine :initial => :parked do
|
278
|
-
before_transition
|
279
|
-
after_transition
|
278
|
+
before_transition any => :idling, :do => :put_on_seatbelt
|
279
|
+
after_transition any => :parked do |transition|
|
280
280
|
self.seatbelt = 'off' # self is the record
|
281
281
|
end
|
282
282
|
|
283
283
|
event :ignite do
|
284
|
-
transition :
|
284
|
+
transition :parked => :idling
|
285
|
+
end
|
286
|
+
|
287
|
+
state :first_gear, :second_gear do
|
288
|
+
validates_present :seatbelt_on
|
285
289
|
end
|
286
290
|
end
|
287
291
|
|
@@ -320,13 +324,17 @@ callbacks. For example,
|
|
320
324
|
|
321
325
|
class Vehicle < Sequel::Model
|
322
326
|
state_machine :initial => :parked do
|
323
|
-
before_transition
|
324
|
-
after_transition
|
327
|
+
before_transition any => :idling, :do => :put_on_seatbelt
|
328
|
+
after_transition any => :parked do |transition|
|
325
329
|
self.seatbelt = 'off' # self is the record
|
326
330
|
end
|
327
331
|
|
328
332
|
event :ignite do
|
329
|
-
transition :
|
333
|
+
transition :parked => :idling
|
334
|
+
end
|
335
|
+
|
336
|
+
state :first_gear, :second_gear do
|
337
|
+
validates_presence_of :seatbelt_on
|
330
338
|
end
|
331
339
|
end
|
332
340
|
|
data/Rakefile
CHANGED
@@ -5,7 +5,7 @@ require 'rake/contrib/sshpublisher'
|
|
5
5
|
|
6
6
|
spec = Gem::Specification.new do |s|
|
7
7
|
s.name = 'state_machine'
|
8
|
-
s.version = '0.
|
8
|
+
s.version = '0.6.0'
|
9
9
|
s.platform = Gem::Platform::RUBY
|
10
10
|
s.summary = 'Adds support for creating state machines for attributes on any Ruby class'
|
11
11
|
|
data/examples/auto_shop.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
class AutoShop
|
2
2
|
state_machine :initial => :available do
|
3
3
|
event :tow_vehicle do
|
4
|
-
transition :
|
4
|
+
transition :available => :busy
|
5
5
|
end
|
6
6
|
|
7
7
|
event :fix_vehicle do
|
8
|
-
transition :
|
8
|
+
transition :busy => :available
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
data/examples/car.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
class Car < Vehicle
|
2
2
|
state_machine do
|
3
3
|
event :reverse do
|
4
|
-
transition
|
4
|
+
transition [:parked, :idling, :first_gear] => :backing_up
|
5
5
|
end
|
6
6
|
|
7
7
|
event :park do
|
8
|
-
transition :
|
8
|
+
transition :backing_up => :parked
|
9
9
|
end
|
10
10
|
|
11
11
|
event :idle do
|
12
|
-
transition :
|
12
|
+
transition :backing_up => :idling
|
13
13
|
end
|
14
14
|
|
15
15
|
event :shift_up do
|
16
|
-
transition :
|
16
|
+
transition :backing_up => :first_gear
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
data/examples/traffic_light.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
class TrafficLight
|
2
2
|
state_machine :initial => :stop do
|
3
3
|
event :cycle do
|
4
|
-
transition :
|
5
|
-
transition :to => :caution, :from => :proceed
|
6
|
-
transition :to => :stop, :from => :caution
|
4
|
+
transition :stop => :proceed, :proceed => :caution, :caution => :stop
|
7
5
|
end
|
8
6
|
end
|
9
7
|
end
|
data/examples/vehicle.rb
CHANGED
@@ -1,35 +1,31 @@
|
|
1
1
|
class Vehicle
|
2
2
|
state_machine :initial => :parked do
|
3
3
|
event :park do
|
4
|
-
transition :
|
4
|
+
transition [:idling, :first_gear] => :parked
|
5
5
|
end
|
6
6
|
|
7
7
|
event :ignite do
|
8
|
-
transition :
|
9
|
-
transition :to => :idling, :from => :parked
|
8
|
+
transition :stalled => same, :parked => :idling
|
10
9
|
end
|
11
10
|
|
12
11
|
event :idle do
|
13
|
-
transition :
|
12
|
+
transition :first_gear => :idling
|
14
13
|
end
|
15
14
|
|
16
15
|
event :shift_up do
|
17
|
-
transition :
|
18
|
-
transition :to => :second_gear, :from => :first_gear
|
19
|
-
transition :to => :third_gear, :from => :second_gear
|
16
|
+
transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
|
20
17
|
end
|
21
18
|
|
22
19
|
event :shift_down do
|
23
|
-
transition :
|
24
|
-
transition :to => :first_gear, :from => :second_gear
|
20
|
+
transition :third_gear => :second_gear, :second_gear => :first_gear
|
25
21
|
end
|
26
22
|
|
27
23
|
event :crash do
|
28
|
-
transition
|
24
|
+
transition [:first_gear, :second_gear, :third_gear] => :stalled
|
29
25
|
end
|
30
26
|
|
31
27
|
event :repair do
|
32
|
-
transition :
|
28
|
+
transition :stalled => :parked
|
33
29
|
end
|
34
30
|
end
|
35
31
|
end
|
data/lib/state_machine.rb
CHANGED
@@ -199,7 +199,7 @@ module StateMachine
|
|
199
199
|
# class Vehicle
|
200
200
|
# state_machine :initial => :parked do
|
201
201
|
# event :ignite do
|
202
|
-
# transition
|
202
|
+
# transition all => :idling
|
203
203
|
# end
|
204
204
|
# end
|
205
205
|
# end
|
@@ -242,21 +242,21 @@ module StateMachine
|
|
242
242
|
# class Vehicle
|
243
243
|
# state_machine :heater_state, :initial => :off :namespace => 'heater' do
|
244
244
|
# event :turn_on do
|
245
|
-
# transition
|
245
|
+
# transition all => :on
|
246
246
|
# end
|
247
247
|
#
|
248
248
|
# event :turn_off do
|
249
|
-
# transition
|
249
|
+
# transition all => :off
|
250
250
|
# end
|
251
251
|
# end
|
252
252
|
#
|
253
253
|
# state_machine :hood_state, :initial => :closed, :namespace => 'hood' do
|
254
254
|
# event :open do
|
255
|
-
# transition
|
255
|
+
# transition all => :opened
|
256
256
|
# end
|
257
257
|
#
|
258
258
|
# event :close do
|
259
|
-
# transition
|
259
|
+
# transition all => :closed
|
260
260
|
# end
|
261
261
|
# end
|
262
262
|
# end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'state_machine/eval_helpers'
|
2
|
+
|
3
|
+
module StateMachine
|
4
|
+
# Represents a type of module in which class-level methods are proxied to
|
5
|
+
# another class, injecting a custom :if condition along with method.
|
6
|
+
#
|
7
|
+
# This is used for being able to automatically include conditionals which
|
8
|
+
# check the current state in class-level methods that have configuration
|
9
|
+
# options.
|
10
|
+
#
|
11
|
+
# == Examples
|
12
|
+
#
|
13
|
+
# class Vehicle
|
14
|
+
# class << self
|
15
|
+
# attr_accessor :validations
|
16
|
+
#
|
17
|
+
# def validate(options, &block)
|
18
|
+
# validations << options
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# self.validations = []
|
23
|
+
# attr_accessor :state, :simulate
|
24
|
+
#
|
25
|
+
# def moving?
|
26
|
+
# self.class.validations.all? {|validation| validation[:if].call(self)}
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# In the above class, a simple set of validation behaviors have been defined.
|
31
|
+
# Each validation consists of a configuration like so:
|
32
|
+
#
|
33
|
+
# Vehicle.validate :unless => :simulate
|
34
|
+
# Vehicle.validate :if => lambda {|vehicle| ...}
|
35
|
+
#
|
36
|
+
# In order to scope conditions, a condition proxy can be created to the
|
37
|
+
# Vehicle class. For example,
|
38
|
+
#
|
39
|
+
# proxy = StateMachine::ConditionProxy.new(Vehicle, lambda {|vehicle| vehicle.state == 'first_gear'})
|
40
|
+
# proxy.validate(:unless => :simulate)
|
41
|
+
#
|
42
|
+
# vehicle = Vehicle.new # => #<Vehicle:0xb7ce491c @simulate=nil, @state=nil>
|
43
|
+
# vehicle.moving? # => false
|
44
|
+
#
|
45
|
+
# vehicle.state = 'first_gear'
|
46
|
+
# vehicle.moving? # => true
|
47
|
+
#
|
48
|
+
# vehicle.simulate = true
|
49
|
+
# vehicle.moving? # => false
|
50
|
+
class ConditionProxy < Module
|
51
|
+
include EvalHelpers
|
52
|
+
|
53
|
+
# Creates a new proxy to the given class, merging in the given condition
|
54
|
+
def initialize(klass, condition)
|
55
|
+
@klass = klass
|
56
|
+
@condition = condition
|
57
|
+
end
|
58
|
+
|
59
|
+
# Hooks in condition merging to methods that don't exist in this module
|
60
|
+
def method_missing(*args, &block)
|
61
|
+
# Get the configuration
|
62
|
+
if args.last.is_a?(Hash)
|
63
|
+
options = args.last
|
64
|
+
else
|
65
|
+
args << options = {}
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get any existing condition that may need to be merged
|
69
|
+
if_condition = options.delete(:if)
|
70
|
+
unless_condition = options.delete(:unless)
|
71
|
+
|
72
|
+
# Provide scope access to configuration in case the block is evaluated
|
73
|
+
# within the object instance
|
74
|
+
proxy = self
|
75
|
+
proxy_condition = @condition
|
76
|
+
|
77
|
+
# Replace the configuration condition with the one configured for this
|
78
|
+
# proxy, merging together any existing conditions
|
79
|
+
options[:if] = lambda do |*args|
|
80
|
+
# Block may be executed within the context of the actual object, so it'll
|
81
|
+
# either be the first argument or the executing context
|
82
|
+
object = args.first || self
|
83
|
+
|
84
|
+
proxy.evaluate_method(object, proxy_condition) &&
|
85
|
+
Array(if_condition).all? {|condition| proxy.evaluate_method(object, condition)} &&
|
86
|
+
!Array(unless_condition).any? {|condition| proxy.evaluate_method(object, condition)}
|
87
|
+
end
|
88
|
+
|
89
|
+
# Evaluate the method on the original class with the condition proxied
|
90
|
+
# through
|
91
|
+
@klass.send(*args, &block)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/state_machine/event.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'state_machine/transition'
|
2
2
|
require 'state_machine/guard'
|
3
3
|
require 'state_machine/assertions'
|
4
|
+
require 'state_machine/matcher_helpers'
|
4
5
|
|
5
6
|
module StateMachine
|
6
7
|
# An event defines an action that transitions an attribute from one state to
|
@@ -8,6 +9,7 @@ module StateMachine
|
|
8
9
|
# guards configured for the event.
|
9
10
|
class Event
|
10
11
|
include Assertions
|
12
|
+
include MatcherHelpers
|
11
13
|
|
12
14
|
# The state machine for which this event is defined
|
13
15
|
attr_accessor :machine
|
@@ -41,15 +43,75 @@ module StateMachine
|
|
41
43
|
@known_states = @known_states.dup
|
42
44
|
end
|
43
45
|
|
44
|
-
# Creates a new transition that
|
46
|
+
# Creates a new transition that determines what to change the current state
|
47
|
+
# to when this event fires.
|
45
48
|
#
|
46
|
-
#
|
49
|
+
# == Defining transitions
|
50
|
+
#
|
51
|
+
# The options for a new transition uses the Hash syntax to map beginning
|
52
|
+
# states to ending states. For example,
|
53
|
+
#
|
54
|
+
# transition :parked => :idling, :idling => :first_gear
|
55
|
+
#
|
56
|
+
# In this case, when the event is fired, this transition will cause the
|
57
|
+
# state to be +idling+ if it's current state is +parked+ or +first_gear+ if
|
58
|
+
# it's current state is +idling+.
|
59
|
+
#
|
60
|
+
# To help defining these implicit transitions, a set of helpers are available
|
61
|
+
# for defining slightly more complex matching:
|
62
|
+
# * <tt>all</tt> - Matches every state in the machine
|
63
|
+
# * <tt>all - [:parked, :idling, ...]</tt> - Matches every state except those specified
|
64
|
+
# * <tt>any</tt> - An alias for +all+ (matches every state in the machine)
|
65
|
+
# * <tt>same</tt> - Matches the same state being transitioned from
|
66
|
+
#
|
67
|
+
# See StateMachine::MatcherHelpers for more information.
|
68
|
+
#
|
69
|
+
# Examples:
|
70
|
+
#
|
71
|
+
# transition all => nil # Transitions to nil regardless of the current state
|
72
|
+
# transition all => :idling # Transitions to :idling regardless of the current state
|
73
|
+
# transition all - [:idling, :first_gear] => :idling # Transitions every state but :idling and :first_gear to :idling
|
74
|
+
# transition nil => :idling # Transitions to :idling from the nil state
|
75
|
+
# transition :parked => :idling # Transitions to :idling if :parked
|
76
|
+
# transition [:parked, :stalled] => :idling # Transitions to :idling if :parked or :stalled
|
77
|
+
#
|
78
|
+
# transition :parked => same # Loops :parked back to :parked
|
79
|
+
# transition [:parked, :stalled] => same # Loops either :parked or :stalled back to the same state
|
80
|
+
# transition all - :parked => same # Loops every state but :parked back to the same state
|
81
|
+
#
|
82
|
+
# == Verbose transitions
|
83
|
+
#
|
84
|
+
# Transitions can also be defined use an explicit set of deprecated
|
85
|
+
# configuration options:
|
47
86
|
# * <tt>:from</tt> - A state or array of states that can be transitioned from.
|
48
87
|
# If not specified, then the transition can occur for *any* state.
|
49
88
|
# * <tt>:to</tt> - The state that's being transitioned to. If not specified,
|
50
89
|
# then the transition will simply loop back (i.e. the state will not change).
|
51
90
|
# * <tt>:except_from</tt> - A state or array of states that *cannot* be
|
52
91
|
# transitioned from.
|
92
|
+
#
|
93
|
+
# Examples:
|
94
|
+
#
|
95
|
+
# transition :to => nil
|
96
|
+
# transition :to => :idling
|
97
|
+
# transition :except_from => [:idling, :first_gear], :to => :idling
|
98
|
+
# transition :from => nil, :to => :idling
|
99
|
+
# transition :from => [:parked, :stalled], :to => :idling
|
100
|
+
#
|
101
|
+
# transition :from => :parked
|
102
|
+
# transition :from => [:parked, :stalled]
|
103
|
+
# transition :except_from => :parked
|
104
|
+
#
|
105
|
+
# Notice that the above examples are the verbose equivalent of the examples
|
106
|
+
# described initially.
|
107
|
+
#
|
108
|
+
# == Conditions
|
109
|
+
#
|
110
|
+
# In addition to the state requirements for each transition, a condition
|
111
|
+
# can also be defined to help determine whether that transition is
|
112
|
+
# available. These options will work on both the normal and verbose syntax.
|
113
|
+
#
|
114
|
+
# Configuration options:
|
53
115
|
# * <tt>:if</tt> - A method, proc or string to call to determine if the
|
54
116
|
# transition should occur (e.g. :if => :moving?, or :if => lambda {|vehicle| vehicle.speed > 60}).
|
55
117
|
# The condition should return or evaluate to true or false.
|
@@ -57,26 +119,25 @@ module StateMachine
|
|
57
119
|
# transition should not occur (e.g. :unless => :stopped?, or :unless => lambda {|vehicle| vehicle.speed <= 60}).
|
58
120
|
# The condition should return or evaluate to true or false.
|
59
121
|
#
|
122
|
+
# Examples:
|
123
|
+
#
|
124
|
+
# transition :parked => :idling, :if => :moving?
|
125
|
+
# transition :parked => :idling, :unless => :stopped?
|
126
|
+
#
|
127
|
+
# transition :from => :parked, :to => :idling, :if => :moving?
|
128
|
+
# transition :from => :parked, :to => :idling, :unless => :stopped?
|
129
|
+
#
|
60
130
|
# == Order of operations
|
61
131
|
#
|
62
132
|
# Transitions are evaluated in the order in which they're defined. As a
|
63
133
|
# result, if more than one transition applies to a given object, then the
|
64
134
|
# first transition that matches will be performed.
|
65
|
-
#
|
66
|
-
# == Examples
|
67
|
-
#
|
68
|
-
# transition :from => nil, :to => :parked
|
69
|
-
# transition :from => [:first_gear, :reverse]
|
70
|
-
# transition :except_from => :parked
|
71
|
-
# transition :to => nil
|
72
|
-
# transition :to => :parked
|
73
|
-
# transition :to => :parked, :from => :first_gear
|
74
|
-
# transition :to => :parked, :from => [:first_gear, :reverse]
|
75
|
-
# transition :to => :parked, :from => :first_gear, :if => :moving?
|
76
|
-
# transition :to => :parked, :from => :first_gear, :unless => :stopped?
|
77
|
-
# transition :to => :parked, :except_from => :parked
|
78
135
|
def transition(options)
|
79
|
-
|
136
|
+
raise ArgumentError, 'Must specify as least one transition requirement' if options.empty?
|
137
|
+
|
138
|
+
# Only a certain subset of explicit options are allowed for transition
|
139
|
+
# requirements
|
140
|
+
assert_valid_keys(options, :from, :to, :except_from, :if, :unless) if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on, :if, :unless]).empty?
|
80
141
|
|
81
142
|
guards << guard = Guard.new(options)
|
82
143
|
@known_states |= guard.known_states
|
@@ -96,11 +157,17 @@ module StateMachine
|
|
96
157
|
def next_transition(object)
|
97
158
|
from = machine.state_for(object).name
|
98
159
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
160
|
+
guards.each do |guard|
|
161
|
+
if match = guard.match(object, :from => from)
|
162
|
+
# Guard allows for the transition to occur
|
163
|
+
to = match[:to].values.empty? ? from : match[:to].values.first
|
164
|
+
|
165
|
+
return Transition.new(object, machine, name, from, to)
|
166
|
+
end
|
103
167
|
end
|
168
|
+
|
169
|
+
# No transition matched
|
170
|
+
nil
|
104
171
|
end
|
105
172
|
|
106
173
|
# Attempts to perform the next available transition on the given object.
|
@@ -139,11 +206,13 @@ module StateMachine
|
|
139
206
|
# For example,
|
140
207
|
#
|
141
208
|
# event = StateMachine::Event.new(machine, :park)
|
142
|
-
# event.transition :
|
143
|
-
# event # => #<StateMachine::Event name=:park transitions=[:idling => :parked]>
|
209
|
+
# event.transition all - :idling => :parked, :idling => same
|
210
|
+
# event # => #<StateMachine::Event name=:park transitions=[all - :idling => :parked, :idling => same]>
|
144
211
|
def inspect
|
145
212
|
transitions = guards.map do |guard|
|
146
|
-
|
213
|
+
guard.state_requirements.map do |state_requirement|
|
214
|
+
"#{state_requirement[:from].description} => #{state_requirement[:to].description}"
|
215
|
+
end * ', '
|
147
216
|
end
|
148
217
|
|
149
218
|
"#<#{self.class} name=#{name.inspect} transitions=[#{transitions * ', '}]>"
|