state_machine 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 * ', '}]>"
|