finite_machine 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/.travis.yml +6 -2
- data/CHANGELOG.md +12 -0
- data/README.md +345 -105
- data/finite_machine.gemspec +2 -2
- data/lib/finite_machine.rb +12 -0
- data/lib/finite_machine/callable.rb +2 -7
- data/lib/finite_machine/catchable.rb +112 -0
- data/lib/finite_machine/dsl.rb +59 -44
- data/lib/finite_machine/hooks.rb +43 -0
- data/lib/finite_machine/observer.rb +39 -40
- data/lib/finite_machine/state_machine.rb +37 -18
- data/lib/finite_machine/subscribers.rb +1 -1
- data/lib/finite_machine/threadable.rb +8 -2
- data/lib/finite_machine/transition.rb +45 -1
- data/lib/finite_machine/version.rb +1 -1
- data/spec/spec_helper.rb +9 -0
- data/spec/unit/callable/call_spec.rb +91 -0
- data/spec/unit/callbacks_spec.rb +96 -12
- data/spec/unit/events_spec.rb +1 -1
- data/spec/unit/handlers_spec.rb +99 -0
- data/spec/unit/if_unless_spec.rb +3 -3
- data/spec/unit/initialize_spec.rb +40 -0
- data/spec/unit/target_spec.rb +137 -0
- data/spec/unit/transition/parse_states_spec.rb +16 -5
- metadata +15 -4
@@ -5,6 +5,7 @@ module FiniteMachine
|
|
5
5
|
# Base class for state machine
|
6
6
|
class StateMachine
|
7
7
|
include Threadable
|
8
|
+
include Catchable
|
8
9
|
|
9
10
|
# Initial state, defaults to :none
|
10
11
|
attr_threadsafe :initial_state
|
@@ -18,6 +19,9 @@ module FiniteMachine
|
|
18
19
|
# Events DSL
|
19
20
|
attr_threadsafe :events
|
20
21
|
|
22
|
+
# Errors DSL
|
23
|
+
attr_threadsafe :errors
|
24
|
+
|
21
25
|
# The prefix used to name events.
|
22
26
|
attr_threadsafe :namespace
|
23
27
|
|
@@ -39,6 +43,7 @@ module FiniteMachine
|
|
39
43
|
def initialize(*args, &block)
|
40
44
|
@subscribers = Subscribers.new(self)
|
41
45
|
@events = EventsDSL.new(self)
|
46
|
+
@errors = ErrorsDSL.new(self)
|
42
47
|
@observer = Observer.new(self)
|
43
48
|
@transitions = Hash.new { |hash, name| hash[name] = Hash.new }
|
44
49
|
@env = Environment.new(target: self)
|
@@ -57,10 +62,12 @@ module FiniteMachine
|
|
57
62
|
#
|
58
63
|
# @api public
|
59
64
|
def notify(event_type, _transition, *data)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
65
|
+
sync_shared do
|
66
|
+
event_class = Event.const_get(event_type.capitalize.to_s)
|
67
|
+
state_or_action = event_class < Event::Anystate ? state : _transition.name
|
68
|
+
_event = event_class.new(state_or_action, _transition, *data)
|
69
|
+
subscribers.visit(_event)
|
70
|
+
end
|
64
71
|
end
|
65
72
|
|
66
73
|
# Get current state
|
@@ -136,21 +143,18 @@ module FiniteMachine
|
|
136
143
|
is?(final_state)
|
137
144
|
end
|
138
145
|
|
139
|
-
#
|
140
|
-
#
|
141
|
-
# @api public
|
142
|
-
def errors
|
143
|
-
end
|
144
|
-
|
145
146
|
private
|
146
147
|
|
147
148
|
# Check if state is reachable
|
148
149
|
#
|
149
150
|
# @api private
|
150
|
-
def
|
151
|
+
def valid_state?(_transition)
|
151
152
|
current_states = transitions[_transition.name].keys
|
152
|
-
if !current_states.include?(state)
|
153
|
-
|
153
|
+
if !current_states.include?(state) && !current_states.include?(ANY_STATE)
|
154
|
+
exception = InvalidStateError
|
155
|
+
catch_error(exception) ||
|
156
|
+
raise(exception, "inappropriate current state '#{state}'")
|
157
|
+
true
|
154
158
|
end
|
155
159
|
end
|
156
160
|
|
@@ -158,9 +162,11 @@ module FiniteMachine
|
|
158
162
|
#
|
159
163
|
# @api private
|
160
164
|
def transition(_transition, *args, &block)
|
161
|
-
|
165
|
+
return CANCELLED if valid_state?(_transition)
|
162
166
|
|
163
|
-
return CANCELLED unless _transition.conditions.all?
|
167
|
+
return CANCELLED unless _transition.conditions.all? do |condition|
|
168
|
+
condition.call(env.target)
|
169
|
+
end
|
164
170
|
return NOTRANSITION if state == _transition.to
|
165
171
|
|
166
172
|
sync_exclusive do
|
@@ -172,9 +178,10 @@ module FiniteMachine
|
|
172
178
|
|
173
179
|
notify :transitionstate, _transition, *args
|
174
180
|
notify :transitionaction, _transition, *args
|
175
|
-
rescue
|
176
|
-
|
177
|
-
"
|
181
|
+
rescue Exception => e
|
182
|
+
catch_error(e) ||
|
183
|
+
raise(TransitionError, "#(#{e.class}): #{e.message}\n" +
|
184
|
+
"occured at #{e.backtrace.join("\n")}")
|
178
185
|
end
|
179
186
|
|
180
187
|
notify :enterstate, _transition, *args
|
@@ -184,5 +191,17 @@ module FiniteMachine
|
|
184
191
|
SUCCEEDED
|
185
192
|
end
|
186
193
|
|
194
|
+
def method_missing(method_name, *args, &block)
|
195
|
+
if env.target.respond_to?(method_name.to_sym)
|
196
|
+
env.target.send(method_name.to_sym, *args, &block)
|
197
|
+
else
|
198
|
+
super
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def respond_to_missing?(method_name, include_private = false)
|
203
|
+
env.target.respond_to?(method_name.to_sym)
|
204
|
+
end
|
205
|
+
|
187
206
|
end # StateMachine
|
188
207
|
end # FiniteMachine
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module FiniteMachine
|
4
|
+
|
5
|
+
# A mixin to allow instance methods to be synchronized
|
4
6
|
module Threadable
|
5
7
|
module InstanceMethods
|
6
8
|
@@sync = Sync.new
|
@@ -27,8 +29,12 @@ module FiniteMachine
|
|
27
29
|
def attr_threadsafe(*attrs)
|
28
30
|
attrs.flatten.each do |attr|
|
29
31
|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
30
|
-
def #{attr}
|
31
|
-
|
32
|
+
def #{attr}(*args)
|
33
|
+
if args.empty?
|
34
|
+
sync_shared { @#{attr} }
|
35
|
+
else
|
36
|
+
self.#{attr} = args.shift
|
37
|
+
end
|
32
38
|
end
|
33
39
|
alias_method '#{attr}?', '#{attr}'
|
34
40
|
|
@@ -20,6 +20,9 @@ module FiniteMachine
|
|
20
20
|
# The current state machine
|
21
21
|
attr_threadsafe :machine
|
22
22
|
|
23
|
+
# The original from state
|
24
|
+
attr_threadsafe :from_state
|
25
|
+
|
23
26
|
# Initialize a Transition
|
24
27
|
#
|
25
28
|
# @api public
|
@@ -27,6 +30,7 @@ module FiniteMachine
|
|
27
30
|
@machine = machine
|
28
31
|
@name = attrs.fetch(:name, DEFAULT_STATE)
|
29
32
|
@from, @to = *parse_states(attrs)
|
33
|
+
@from_state = @from.first
|
30
34
|
@if = Array(attrs.fetch(:if, []))
|
31
35
|
@unless = Array(attrs.fetch(:unless, []))
|
32
36
|
@conditions = make_conditions
|
@@ -43,6 +47,7 @@ module FiniteMachine
|
|
43
47
|
def parse_states(attrs)
|
44
48
|
_attrs = attrs.dup
|
45
49
|
[:name, :if, :unless].each { |key| _attrs.delete(key) }
|
50
|
+
raise_not_enough_transitions(attrs) unless _attrs.any?
|
46
51
|
|
47
52
|
if [:from, :to].any? { |key| attrs.keys.include?(key) }
|
48
53
|
[Array(_attrs[:from] || ANY_STATE), _attrs[:to]]
|
@@ -51,18 +56,57 @@ module FiniteMachine
|
|
51
56
|
end
|
52
57
|
end
|
53
58
|
|
59
|
+
# Add transition to the machine
|
60
|
+
#
|
61
|
+
# @return [Transition]
|
62
|
+
#
|
63
|
+
# @api private
|
64
|
+
def define
|
65
|
+
from.each do |from|
|
66
|
+
machine.transitions[name][from] = to || from
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Define event on the machine
|
71
|
+
#
|
72
|
+
# @api private
|
73
|
+
def define_event
|
74
|
+
_transition = self
|
75
|
+
# TODO check if event is already defined and raise error
|
76
|
+
machine.class.__send__(:define_method, name) do |*args, &block|
|
77
|
+
transition(_transition, *args, &block)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
54
81
|
# Execute current transition
|
55
82
|
#
|
56
83
|
# @api private
|
57
84
|
def call
|
58
85
|
sync_exclusive do
|
59
86
|
transitions = machine.transitions[name]
|
87
|
+
self.from_state = machine.state
|
60
88
|
machine.state = transitions[machine.state] || transitions[ANY_STATE] || name
|
61
89
|
end
|
62
90
|
end
|
63
91
|
|
92
|
+
# Return transition name
|
93
|
+
#
|
94
|
+
# @api public
|
95
|
+
def to_s
|
96
|
+
@name
|
97
|
+
end
|
98
|
+
|
64
99
|
def inspect
|
65
|
-
|
100
|
+
"<#{self.class} name: #{@name}, transitions: #{@from} => #{@to}, when: #{@conditions}>"
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
# Raise error when not enough transitions are provided
|
106
|
+
#
|
107
|
+
# @api private
|
108
|
+
def raise_not_enough_transitions(attrs)
|
109
|
+
raise NotEnoughTransitionsError, "please provide state transitions for '#{attrs.inspect}'"
|
66
110
|
end
|
67
111
|
|
68
112
|
end # Transition
|
data/spec/spec_helper.rb
CHANGED
@@ -12,4 +12,13 @@ RSpec.configure do |config|
|
|
12
12
|
# the seed, which is printed after each run.
|
13
13
|
# --seed 1234
|
14
14
|
config.order = 'random'
|
15
|
+
|
16
|
+
# Remove defined constants
|
17
|
+
config.before :each do
|
18
|
+
[:Car, :Logger].each do |class_name|
|
19
|
+
if Object.const_defined?(class_name)
|
20
|
+
Object.send(:remove_const, class_name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
15
24
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe FiniteMachine::Callable, '#call' do
|
6
|
+
|
7
|
+
before(:each) {
|
8
|
+
Car = Class.new do
|
9
|
+
attr_reader :result
|
10
|
+
|
11
|
+
def turn_engine_on
|
12
|
+
@result = 'turn_engine_on'
|
13
|
+
@engine_on = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def set_engine(value)
|
17
|
+
@result = "set_engine(#{value})"
|
18
|
+
@engine = value.to_sym == :on
|
19
|
+
end
|
20
|
+
|
21
|
+
def turn_engine_off
|
22
|
+
@result = 'turn_engine_off'
|
23
|
+
@engine_on = false
|
24
|
+
end
|
25
|
+
|
26
|
+
def engine_on?
|
27
|
+
@result = 'engine_on'
|
28
|
+
!!@engine_on
|
29
|
+
end
|
30
|
+
end
|
31
|
+
}
|
32
|
+
|
33
|
+
let(:called) { [] }
|
34
|
+
|
35
|
+
let(:target) { Car.new }
|
36
|
+
|
37
|
+
let(:instance) { described_class.new(object) }
|
38
|
+
|
39
|
+
context 'when string' do
|
40
|
+
let(:object) { 'engine_on?' }
|
41
|
+
|
42
|
+
it 'executes method on target' do
|
43
|
+
instance.call(target)
|
44
|
+
expect(target.result).to eql('engine_on')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when string' do
|
49
|
+
let(:object) { 'set_engine(:on)' }
|
50
|
+
|
51
|
+
it 'executes method with arguments' do
|
52
|
+
instance.call(target)
|
53
|
+
expect(target.result).to eql('set_engine(on)')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when symbol' do
|
58
|
+
let(:object) { :engine_on? }
|
59
|
+
|
60
|
+
it 'executes method on target' do
|
61
|
+
instance.call(target)
|
62
|
+
expect(target.result).to eql('engine_on')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when proc without args' do
|
67
|
+
let(:object) { proc { |a| called << "block_with(#{a})" } }
|
68
|
+
|
69
|
+
it 'passes arguments' do
|
70
|
+
instance.call(target)
|
71
|
+
expect(called).to eql(["block_with(#{target})"])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'when proc with args' do
|
76
|
+
let(:object) { proc { |a,b| called << "block_with(#{a},#{b})" } }
|
77
|
+
|
78
|
+
it 'passes arguments' do
|
79
|
+
instance.call(target, :red)
|
80
|
+
expect(called).to eql(["block_with(#{target},red)"])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when unknown' do
|
85
|
+
let(:object) { Object.new }
|
86
|
+
|
87
|
+
it 'raises error' do
|
88
|
+
expect { instance.call(target) }.to raise_error(ArgumentError)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/spec/unit/callbacks_spec.rb
CHANGED
@@ -18,9 +18,17 @@ describe FiniteMachine, 'callbacks' do
|
|
18
18
|
|
19
19
|
callbacks {
|
20
20
|
# generic callbacks
|
21
|
-
on_enter
|
22
|
-
|
21
|
+
on_enter do |event| called << 'on_enter' end
|
22
|
+
on_enter_state do |event| called << 'on_enter_state' end
|
23
|
+
on_enter_event do |event| called << 'on_enter_event' end
|
24
|
+
|
25
|
+
on_transition do |event| called << 'on_transition' end
|
26
|
+
on_transition_state do |event| called << 'on_transition_state' end
|
27
|
+
on_transition_event do |event| called << 'on_transition_event' end
|
28
|
+
|
23
29
|
on_exit do |event| called << 'on_exit' end
|
30
|
+
on_exit_state do |event| called << 'on_exit_state' end
|
31
|
+
on_exit_event do |event| called << 'on_exit_event' end
|
24
32
|
|
25
33
|
# state callbacks
|
26
34
|
on_enter :none do |event| called << 'on_enter_none' end
|
@@ -42,16 +50,22 @@ describe FiniteMachine, 'callbacks' do
|
|
42
50
|
expect(called).to eql([
|
43
51
|
'on_exit_none',
|
44
52
|
'on_exit',
|
53
|
+
'on_exit_state',
|
45
54
|
'on_enter_init',
|
46
55
|
'on_enter',
|
56
|
+
'on_enter_event',
|
47
57
|
'on_transition_green',
|
48
58
|
'on_transition',
|
59
|
+
'on_transition_state',
|
49
60
|
'on_transition_init',
|
50
61
|
'on_transition',
|
62
|
+
'on_transition_event',
|
51
63
|
'on_enter_green',
|
52
64
|
'on_enter',
|
65
|
+
'on_enter_state',
|
53
66
|
'on_exit_init',
|
54
|
-
'on_exit'
|
67
|
+
'on_exit',
|
68
|
+
'on_exit_event'
|
55
69
|
])
|
56
70
|
end
|
57
71
|
|
@@ -69,9 +83,17 @@ describe FiniteMachine, 'callbacks' do
|
|
69
83
|
|
70
84
|
callbacks {
|
71
85
|
# generic callbacks
|
72
|
-
on_enter
|
73
|
-
|
86
|
+
on_enter do |event| called << 'on_enter' end
|
87
|
+
on_enter_state do |event| called << 'on_enter_state' end
|
88
|
+
on_enter_event do |event| called << 'on_enter_event' end
|
89
|
+
|
90
|
+
on_transition do |event| called << 'on_transition' end
|
91
|
+
on_transition_state do |event| called << 'on_transition_state' end
|
92
|
+
on_transition_event do |event| called << 'on_transition_event' end
|
93
|
+
|
74
94
|
on_exit do |event| called << 'on_exit' end
|
95
|
+
on_exit_state do |event| called << 'on_exit_state' end
|
96
|
+
on_exit_event do |event| called << 'on_exit_event' end
|
75
97
|
|
76
98
|
# state callbacks
|
77
99
|
on_enter :green do |event| called << 'on_enter_green' end
|
@@ -109,16 +131,22 @@ describe FiniteMachine, 'callbacks' do
|
|
109
131
|
expect(called).to eql([
|
110
132
|
'on_exit_green',
|
111
133
|
'on_exit',
|
134
|
+
'on_exit_state',
|
112
135
|
'on_enter_slow',
|
113
136
|
'on_enter',
|
137
|
+
'on_enter_event',
|
114
138
|
'on_transition_yellow',
|
115
139
|
'on_transition',
|
140
|
+
'on_transition_state',
|
116
141
|
'on_transition_slow',
|
117
142
|
'on_transition',
|
143
|
+
'on_transition_event',
|
118
144
|
'on_enter_yellow',
|
119
145
|
'on_enter',
|
146
|
+
'on_enter_state',
|
120
147
|
'on_exit_slow',
|
121
|
-
'on_exit'
|
148
|
+
'on_exit',
|
149
|
+
'on_exit_event'
|
122
150
|
])
|
123
151
|
|
124
152
|
called = []
|
@@ -126,16 +154,22 @@ describe FiniteMachine, 'callbacks' do
|
|
126
154
|
expect(called).to eql([
|
127
155
|
'on_exit_yellow',
|
128
156
|
'on_exit',
|
157
|
+
'on_exit_state',
|
129
158
|
'on_enter_stop',
|
130
159
|
'on_enter',
|
160
|
+
'on_enter_event',
|
131
161
|
'on_transition_red',
|
132
162
|
'on_transition',
|
163
|
+
'on_transition_state',
|
133
164
|
'on_transition_stop',
|
134
165
|
'on_transition',
|
166
|
+
'on_transition_event',
|
135
167
|
'on_enter_red',
|
136
168
|
'on_enter',
|
169
|
+
'on_enter_state',
|
137
170
|
'on_exit_stop',
|
138
|
-
'on_exit'
|
171
|
+
'on_exit',
|
172
|
+
'on_exit_event'
|
139
173
|
])
|
140
174
|
|
141
175
|
called = []
|
@@ -143,16 +177,22 @@ describe FiniteMachine, 'callbacks' do
|
|
143
177
|
expect(called).to eql([
|
144
178
|
'on_exit_red',
|
145
179
|
'on_exit',
|
180
|
+
'on_exit_state',
|
146
181
|
'on_enter_ready',
|
147
182
|
'on_enter',
|
183
|
+
'on_enter_event',
|
148
184
|
'on_transition_yellow',
|
149
185
|
'on_transition',
|
186
|
+
'on_transition_state',
|
150
187
|
'on_transition_ready',
|
151
188
|
'on_transition',
|
189
|
+
'on_transition_event',
|
152
190
|
'on_enter_yellow',
|
153
191
|
'on_enter',
|
192
|
+
'on_enter_state',
|
154
193
|
'on_exit_ready',
|
155
|
-
'on_exit'
|
194
|
+
'on_exit',
|
195
|
+
'on_exit_event'
|
156
196
|
])
|
157
197
|
|
158
198
|
called = []
|
@@ -160,16 +200,22 @@ describe FiniteMachine, 'callbacks' do
|
|
160
200
|
expect(called).to eql([
|
161
201
|
'on_exit_yellow',
|
162
202
|
'on_exit',
|
203
|
+
'on_exit_state',
|
163
204
|
'on_enter_go',
|
164
205
|
'on_enter',
|
206
|
+
'on_enter_event',
|
165
207
|
'on_transition_green',
|
166
208
|
'on_transition',
|
209
|
+
'on_transition_state',
|
167
210
|
'on_transition_go',
|
168
211
|
'on_transition',
|
212
|
+
'on_transition_event',
|
169
213
|
'on_enter_green',
|
170
214
|
'on_enter',
|
215
|
+
'on_enter_state',
|
171
216
|
'on_exit_go',
|
172
|
-
'on_exit'
|
217
|
+
'on_exit',
|
218
|
+
'on_exit_event'
|
173
219
|
])
|
174
220
|
end
|
175
221
|
|
@@ -279,7 +325,32 @@ describe FiniteMachine, 'callbacks' do
|
|
279
325
|
initial :green
|
280
326
|
|
281
327
|
events {
|
282
|
-
event :slow,
|
328
|
+
event :slow, :green => :yellow
|
329
|
+
}
|
330
|
+
|
331
|
+
callbacks {
|
332
|
+
on_enter(:yellow) { |e| evt = e }
|
333
|
+
}
|
334
|
+
end
|
335
|
+
|
336
|
+
expect(fsm.current).to eql(:green)
|
337
|
+
fsm.slow
|
338
|
+
expect(fsm.current).to eql(:yellow)
|
339
|
+
|
340
|
+
expect(evt.from).to eql(:green)
|
341
|
+
expect(evt.to).to eql(:yellow)
|
342
|
+
expect(evt.name).to eql(:slow)
|
343
|
+
end
|
344
|
+
|
345
|
+
it "identifies the from state for callback event parameter" do
|
346
|
+
evt = nil
|
347
|
+
|
348
|
+
fsm = FiniteMachine.define do
|
349
|
+
initial :green
|
350
|
+
|
351
|
+
events {
|
352
|
+
event :slow, [:red, :blue, :green] => :yellow
|
353
|
+
event :fast, :red => :purple
|
283
354
|
}
|
284
355
|
|
285
356
|
callbacks {
|
@@ -307,10 +378,13 @@ describe FiniteMachine, 'callbacks' do
|
|
307
378
|
expect(b).to eql(expected[:b])
|
308
379
|
expect(c).to eql(expected[:c])
|
309
380
|
}
|
381
|
+
context = self
|
310
382
|
|
311
383
|
fsm = FiniteMachine.define do
|
312
384
|
initial :green
|
313
385
|
|
386
|
+
target context
|
387
|
+
|
314
388
|
events {
|
315
389
|
event :slow, :green => :yellow
|
316
390
|
event :stop, :yellow => :red
|
@@ -355,7 +429,6 @@ describe FiniteMachine, 'callbacks' do
|
|
355
429
|
}
|
356
430
|
end
|
357
431
|
|
358
|
-
|
359
432
|
expected = {name: :slow, from: :green, to: :yellow, a: 1, b: 2, c: 3}
|
360
433
|
fsm.slow(1, 2, 3)
|
361
434
|
|
@@ -385,7 +458,18 @@ describe FiniteMachine, 'callbacks' do
|
|
385
458
|
}.to raise_error(FiniteMachine::InvalidCallbackNameError, /magic is not a valid callback name/)
|
386
459
|
end
|
387
460
|
|
388
|
-
|
461
|
+
it "propagates exceptions raised inside callback" do
|
462
|
+
fsm = FiniteMachine.define do
|
463
|
+
initial :green
|
464
|
+
|
465
|
+
events { event :slow, :green => :yellow }
|
466
|
+
|
467
|
+
callbacks { on_enter(:yellow) { raise RuntimeError } }
|
468
|
+
end
|
469
|
+
|
470
|
+
expect(fsm.current).to eql(:green)
|
471
|
+
expect { fsm.slow }.to raise_error(RuntimeError)
|
472
|
+
end
|
389
473
|
|
390
474
|
xit "executes callbacks with multiple 'from' transitions"
|
391
475
|
end
|