finite_machine 0.6.1 → 0.7.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 +7 -0
- data/.travis.yml +2 -0
- data/CHANGELOG.md +17 -0
- data/README.md +85 -50
- data/lib/finite_machine.rb +5 -4
- data/lib/finite_machine/dsl.rb +8 -8
- data/lib/finite_machine/event.rb +63 -47
- data/lib/finite_machine/hook_event.rb +65 -0
- data/lib/finite_machine/hooks.rb +35 -7
- data/lib/finite_machine/observer.rb +37 -56
- data/lib/finite_machine/safety.rb +113 -0
- data/lib/finite_machine/state_machine.rb +61 -34
- data/lib/finite_machine/state_parser.rb +5 -2
- data/lib/finite_machine/transition.rb +54 -16
- data/lib/finite_machine/version.rb +1 -1
- data/spec/unit/async_events_spec.rb +6 -6
- data/spec/unit/callbacks_spec.rb +197 -186
- data/spec/unit/define_spec.rb +2 -0
- data/spec/unit/event/add_spec.rb +18 -0
- data/spec/unit/event/inspect_spec.rb +17 -0
- data/spec/unit/event/next_transition_spec.rb +48 -0
- data/spec/unit/events_spec.rb +41 -2
- data/spec/unit/hooks/call_spec.rb +24 -0
- data/spec/unit/hooks/inspect_spec.rb +17 -0
- data/spec/unit/hooks/register_spec.rb +22 -0
- data/spec/unit/if_unless_spec.rb +28 -0
- data/spec/unit/inspect_spec.rb +9 -16
- data/spec/unit/respond_to_spec.rb +37 -0
- data/spec/unit/target_spec.rb +1 -1
- data/spec/unit/transition/inspect_spec.rb +25 -0
- data/spec/unit/transition/parse_states_spec.rb +8 -25
- metadata +31 -18
@@ -0,0 +1,65 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FiniteMachine
|
4
|
+
# A class responsible for event notification
|
5
|
+
class HookEvent
|
6
|
+
include Threadable
|
7
|
+
|
8
|
+
MESSAGE = :trigger
|
9
|
+
|
10
|
+
# HookEvent state
|
11
|
+
attr_threadsafe :state
|
12
|
+
|
13
|
+
# HookEvent type
|
14
|
+
attr_threadsafe :type
|
15
|
+
|
16
|
+
# Data associated with the event
|
17
|
+
attr_threadsafe :data
|
18
|
+
|
19
|
+
# Transition associated with the event
|
20
|
+
attr_threadsafe :transition
|
21
|
+
|
22
|
+
def initialize(state, transition, *data, &block)
|
23
|
+
@state = state
|
24
|
+
@transition = transition
|
25
|
+
@data = *data
|
26
|
+
@type = self.class
|
27
|
+
end
|
28
|
+
|
29
|
+
def notify(subscriber, *args, &block)
|
30
|
+
if subscriber.respond_to? MESSAGE
|
31
|
+
subscriber.public_send(MESSAGE, self, *args, &block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Anystate < HookEvent; end
|
36
|
+
|
37
|
+
class Enter < Anystate; end
|
38
|
+
|
39
|
+
class Transition < Anystate; end
|
40
|
+
|
41
|
+
class Exit < Anystate; end
|
42
|
+
|
43
|
+
class Anyaction < HookEvent; end
|
44
|
+
|
45
|
+
class Before < Anyaction; end
|
46
|
+
|
47
|
+
class After < Anyaction; end
|
48
|
+
|
49
|
+
EVENTS = Anystate, Enter, Transition, Exit, Anyaction, Before, After
|
50
|
+
|
51
|
+
def self.event_name
|
52
|
+
name.split('::').last.downcase.to_sym
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.to_s
|
56
|
+
event_name
|
57
|
+
end
|
58
|
+
|
59
|
+
EVENTS.each do |event|
|
60
|
+
(class << self; self; end).class_eval do
|
61
|
+
define_method(event.event_name) { event }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end # HookEvent
|
65
|
+
end # FiniteMachine
|
data/lib/finite_machine/hooks.rb
CHANGED
@@ -13,7 +13,7 @@ module FiniteMachine
|
|
13
13
|
# Hoosk.new(machine)
|
14
14
|
#
|
15
15
|
# @api public
|
16
|
-
def initialize
|
16
|
+
def initialize
|
17
17
|
@collection = Hash.new do |events_hash, event_type|
|
18
18
|
events_hash[event_type] = Hash.new do |state_hash, name|
|
19
19
|
state_hash[name] = []
|
@@ -28,13 +28,13 @@ module FiniteMachine
|
|
28
28
|
# @param [Proc] callback
|
29
29
|
#
|
30
30
|
# @example
|
31
|
-
# hooks.register
|
31
|
+
# hooks.register HookEvent::Enter, :green do ... end
|
32
32
|
#
|
33
33
|
# @return [Hash]
|
34
34
|
#
|
35
35
|
# @api public
|
36
36
|
def register(event_type, name, callback)
|
37
|
-
|
37
|
+
collection[event_type][name] << callback
|
38
38
|
end
|
39
39
|
|
40
40
|
# Unregister callback
|
@@ -44,13 +44,14 @@ module FiniteMachine
|
|
44
44
|
# @param [Proc] callback
|
45
45
|
#
|
46
46
|
# @example
|
47
|
-
# hooks.unregister
|
47
|
+
# hooks.unregister HookEvent::Enter, :green do ... end
|
48
48
|
#
|
49
49
|
# @return [Hash]
|
50
50
|
#
|
51
51
|
# @api public
|
52
52
|
def unregister(event_type, name, callback)
|
53
|
-
|
53
|
+
callbacks = collection[event_type][name]
|
54
|
+
callbacks.delete(callback)
|
54
55
|
end
|
55
56
|
|
56
57
|
# Return all hooks matching event and state
|
@@ -65,10 +66,37 @@ module FiniteMachine
|
|
65
66
|
# @return [Hash]
|
66
67
|
#
|
67
68
|
# @api public
|
68
|
-
def call(event_type, event_state
|
69
|
-
|
69
|
+
def call(event_type, event_state)
|
70
|
+
collection[event_type][event_state].each do |hook|
|
70
71
|
yield hook
|
71
72
|
end
|
72
73
|
end
|
74
|
+
|
75
|
+
# Check if collection has any elements
|
76
|
+
#
|
77
|
+
# @return [Boolean]
|
78
|
+
#
|
79
|
+
# @api public
|
80
|
+
def empty?
|
81
|
+
collection.empty?
|
82
|
+
end
|
83
|
+
|
84
|
+
# String representation
|
85
|
+
#
|
86
|
+
# @return [String]
|
87
|
+
#
|
88
|
+
# @api public
|
89
|
+
def to_s
|
90
|
+
self.inspect
|
91
|
+
end
|
92
|
+
|
93
|
+
# String representation
|
94
|
+
#
|
95
|
+
# @return [String]
|
96
|
+
#
|
97
|
+
# @api public
|
98
|
+
def inspect
|
99
|
+
"<##{self.class}:0x#{object_id.to_s(16)} @collection=#{collection.inspect}>"
|
100
|
+
end
|
73
101
|
end # Hooks
|
74
102
|
end # FiniteMachine
|
@@ -6,6 +6,7 @@ module FiniteMachine
|
|
6
6
|
# A class responsible for observing state changes
|
7
7
|
class Observer
|
8
8
|
include Threadable
|
9
|
+
include Safety
|
9
10
|
|
10
11
|
# The current state machine
|
11
12
|
attr_threadsafe :machine
|
@@ -19,7 +20,7 @@ module FiniteMachine
|
|
19
20
|
def initialize(machine)
|
20
21
|
@machine = machine
|
21
22
|
@machine.subscribe(self)
|
22
|
-
@hooks = FiniteMachine::Hooks.new
|
23
|
+
@hooks = FiniteMachine::Hooks.new
|
23
24
|
end
|
24
25
|
|
25
26
|
# Evaluate in current context
|
@@ -31,19 +32,20 @@ module FiniteMachine
|
|
31
32
|
|
32
33
|
# Register callback for a given event type
|
33
34
|
#
|
34
|
-
# @param [Symbol, FiniteMachine::
|
35
|
+
# @param [Symbol, FiniteMachine::HookEvent] event_type
|
35
36
|
# @param [Array] args
|
36
37
|
# @param [Proc] callback
|
37
38
|
#
|
38
39
|
# @api public
|
39
|
-
|
40
|
+
# TODO: throw error if event type isn't handled
|
41
|
+
def on(event_type = HookEvent, *args, &callback)
|
40
42
|
sync_exclusive do
|
41
43
|
name, async, _ = args
|
42
44
|
name = ANY_EVENT if name.nil?
|
43
45
|
async = false if async.nil?
|
44
|
-
ensure_valid_callback_name!(name)
|
46
|
+
ensure_valid_callback_name!(event_type, name)
|
45
47
|
callback.extend(Async) if async == :async
|
46
|
-
hooks.register event_type
|
48
|
+
hooks.register event_type, name, callback
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
@@ -60,39 +62,44 @@ module FiniteMachine
|
|
60
62
|
|
61
63
|
module Async; end
|
62
64
|
|
63
|
-
def listen_on(type, *args, &callback)
|
64
|
-
name = args.first
|
65
|
-
events = []
|
66
|
-
case name
|
67
|
-
when *state_names then events << :"#{type}state"
|
68
|
-
when *event_names then events << :"#{type}action"
|
69
|
-
else events << :"#{type}state" << :"#{type}action"
|
70
|
-
end
|
71
|
-
events.each { |event| on(Event.send(event), *args, &callback) }
|
72
|
-
end
|
73
|
-
|
74
65
|
def on_enter(*args, &callback)
|
75
|
-
|
66
|
+
on HookEvent::Enter, *args, &callback
|
76
67
|
end
|
77
68
|
|
78
69
|
def on_transition(*args, &callback)
|
79
|
-
|
70
|
+
on HookEvent::Transition, *args, &callback
|
80
71
|
end
|
81
72
|
|
82
73
|
def on_exit(*args, &callback)
|
83
|
-
|
74
|
+
on HookEvent::Exit, *args, &callback
|
84
75
|
end
|
85
76
|
|
86
77
|
def once_on_enter(*args, &callback)
|
87
|
-
|
78
|
+
on HookEvent::Enter, *args, &callback.extend(Once)
|
88
79
|
end
|
89
80
|
|
90
81
|
def once_on_transition(*args, &callback)
|
91
|
-
|
82
|
+
on HookEvent::Transition, *args, &callback.extend(Once)
|
92
83
|
end
|
93
84
|
|
94
85
|
def once_on_exit(*args, &callback)
|
95
|
-
|
86
|
+
on HookEvent::Exit, *args, &callback.extend(Once)
|
87
|
+
end
|
88
|
+
|
89
|
+
def on_before(*args, &callback)
|
90
|
+
on HookEvent::Before, *args, &callback
|
91
|
+
end
|
92
|
+
|
93
|
+
def on_after(*args, &callback)
|
94
|
+
on HookEvent::After, *args, &callback
|
95
|
+
end
|
96
|
+
|
97
|
+
def once_on_before(*args, &callback)
|
98
|
+
on HookEvent::Before, *args, &callback.extend(Once)
|
99
|
+
end
|
100
|
+
|
101
|
+
def once_on_after(*args, &callback)
|
102
|
+
on HookEvent::After, *args, &callback.extend(Once)
|
96
103
|
end
|
97
104
|
|
98
105
|
# Trigger all listeners
|
@@ -101,9 +108,8 @@ module FiniteMachine
|
|
101
108
|
def trigger(event, *args, &block)
|
102
109
|
sync_exclusive do
|
103
110
|
[event.type, ANY_EVENT].each do |event_type|
|
104
|
-
[event.state, ANY_STATE
|
105
|
-
|
106
|
-
hooks.call(event_type, event_state, event) do |hook|
|
111
|
+
[event.state, ANY_STATE].each do |event_state|
|
112
|
+
hooks.call(event_type, event_state) do |hook|
|
107
113
|
handle_callback(hook, event)
|
108
114
|
off(event_type, event_state, &hook) if hook.is_a?(Once)
|
109
115
|
end
|
@@ -150,39 +156,14 @@ module FiniteMachine
|
|
150
156
|
event.transition.cancelled = (result == CANCELLED)
|
151
157
|
end
|
152
158
|
|
153
|
-
#
|
159
|
+
# Callback names including all states and events
|
154
160
|
#
|
155
|
-
# @return [
|
161
|
+
# @return [Array[Symbol]]
|
162
|
+
# valid callback names
|
156
163
|
#
|
157
164
|
# @api private
|
158
|
-
def state_names
|
159
|
-
@names = Set.new
|
160
|
-
@names.merge machine.states
|
161
|
-
@names.merge [ANY_STATE, ANY_STATE_HOOK]
|
162
|
-
end
|
163
|
-
|
164
|
-
# Set of all event names
|
165
|
-
#
|
166
|
-
# @return [Set]
|
167
|
-
#
|
168
|
-
# @api private
|
169
|
-
def event_names
|
170
|
-
@names = Set.new
|
171
|
-
@names.merge machine.event_names
|
172
|
-
@names.merge [ANY_EVENT, ANY_EVENT_HOOK]
|
173
|
-
end
|
174
|
-
|
175
165
|
def callback_names
|
176
|
-
|
177
|
-
end
|
178
|
-
|
179
|
-
def ensure_valid_callback_name!(name)
|
180
|
-
unless callback_names.include?(name)
|
181
|
-
exception = InvalidCallbackNameError
|
182
|
-
machine.catch_error(exception) ||
|
183
|
-
raise(exception, "#{name} is not a valid callback name." +
|
184
|
-
" Valid callback names are #{callback_names.to_a.inspect}")
|
185
|
-
end
|
166
|
+
machine.states + machine.event_names + [ANY_EVENT]
|
186
167
|
end
|
187
168
|
|
188
169
|
# Forward the message to observer
|
@@ -196,7 +177,7 @@ module FiniteMachine
|
|
196
177
|
# @api private
|
197
178
|
def method_missing(method_name, *args, &block)
|
198
179
|
_, event_name, callback_name = *method_name.to_s.match(/^(\w*?on_\w+?)_(\w+)$/)
|
199
|
-
if callback_names.include?(callback_name.to_sym)
|
180
|
+
if callback_name && callback_names.include?(callback_name.to_sym)
|
200
181
|
public_send(event_name, :"#{callback_name}", *args, &block)
|
201
182
|
else
|
202
183
|
super
|
@@ -214,7 +195,7 @@ module FiniteMachine
|
|
214
195
|
# @api private
|
215
196
|
def respond_to_missing?(method_name, include_private = false)
|
216
197
|
*_, callback_name = *method_name.to_s.match(/^(\w*?on_\w+?)_(\w+)$/)
|
217
|
-
callback_names.include?(:"#{callback_name}")
|
198
|
+
callback_name && callback_names.include?(:"#{callback_name}")
|
218
199
|
end
|
219
200
|
end # Observer
|
220
201
|
end # FiniteMachine
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module FiniteMachine
|
4
|
+
# Module for responsible for safety checks against known methods
|
5
|
+
module Safety
|
6
|
+
|
7
|
+
EVENT_CONFLICT_MESSAGE = \
|
8
|
+
"You tried to define an event named \"%{name}\", however this would " \
|
9
|
+
"generate \"%{type}\" method \"%{method}\", which is already defined " \
|
10
|
+
"by %{source}"
|
11
|
+
|
12
|
+
STATE_CALLBACK_CONFLICT_MESSAGE = \
|
13
|
+
"\"%{type}\" callback is a state listener and cannot be used " \
|
14
|
+
"with \"%{name}\" event name. Please use on_before or on_after instead."
|
15
|
+
|
16
|
+
EVENT_CALLBACK_CONFLICT_MESSAGE = \
|
17
|
+
"\"%{type}\" callback is an event listener and cannot be used " \
|
18
|
+
"with \"%{name}\" state name. Please use on_enter, on_transition or " \
|
19
|
+
"on_exit instead."
|
20
|
+
|
21
|
+
CALLBACK_INVALID_MESSAGE = \
|
22
|
+
"\"%{name}\" is not a valid callback name. " \
|
23
|
+
"Valid callback names are \"%{callbacks}"
|
24
|
+
|
25
|
+
# Raise error when the method is already defined
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# detect_event_conflict!(:test, "test=")
|
29
|
+
#
|
30
|
+
# @raise [FiniteMachine::AlreadyDefinedError]
|
31
|
+
#
|
32
|
+
# @return [nil]
|
33
|
+
#
|
34
|
+
# @api public
|
35
|
+
def detect_event_conflict!(event_name, method_name = event_name)
|
36
|
+
if method_already_implemented?(method_name)
|
37
|
+
fail FiniteMachine::AlreadyDefinedError, EVENT_CONFLICT_MESSAGE % {
|
38
|
+
name: event_name,
|
39
|
+
type: :instance,
|
40
|
+
method: method_name,
|
41
|
+
source: 'FiniteMachine'
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Raise error when the callback name is not valid
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# ensure_valid_callback_name!(HookEvent::Enter, ":state_name")
|
50
|
+
#
|
51
|
+
# @raise [FiniteMachine::InvalidCallbackNameError]
|
52
|
+
#
|
53
|
+
# @return [nil]
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def ensure_valid_callback_name!(event_type, name)
|
57
|
+
message = if machine.states.include?(name) &&
|
58
|
+
event_type < HookEvent::Anyaction
|
59
|
+
EVENT_CALLBACK_CONFLICT_MESSAGE % {
|
60
|
+
type: "on_#{event_type.to_s}",
|
61
|
+
name: name
|
62
|
+
}
|
63
|
+
elsif machine.event_names.include?(name) &&
|
64
|
+
event_type < HookEvent::Anystate
|
65
|
+
STATE_CALLBACK_CONFLICT_MESSAGE % {
|
66
|
+
type: "on_#{event_type.to_s}",
|
67
|
+
name: name
|
68
|
+
}
|
69
|
+
elsif !callback_names.include?(name)
|
70
|
+
CALLBACK_INVALID_MESSAGE % {
|
71
|
+
name: name,
|
72
|
+
callbacks: callback_names.to_a.inspect
|
73
|
+
}
|
74
|
+
else
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
message && fail_invalid_callback_error(message)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def fail_invalid_callback_error(message)
|
83
|
+
exception = InvalidCallbackNameError
|
84
|
+
machine.catch_error(exception) || fail(exception, message)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Check if method is already implemented inside StateMachine
|
88
|
+
#
|
89
|
+
# @param [String] name
|
90
|
+
# the method name
|
91
|
+
#
|
92
|
+
# @return [Boolean]
|
93
|
+
#
|
94
|
+
# @api private
|
95
|
+
def method_already_implemented?(name)
|
96
|
+
method_defined_within?(name, FiniteMachine::StateMachine)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Check if method is defined within a given class
|
100
|
+
#
|
101
|
+
# @param [String] name
|
102
|
+
# the method name
|
103
|
+
#
|
104
|
+
# @param [Object] klass
|
105
|
+
#
|
106
|
+
# @return [Boolean]
|
107
|
+
#
|
108
|
+
# @api private
|
109
|
+
def method_defined_within?(name, klass)
|
110
|
+
klass.method_defined?(name) || klass.private_method_defined?(name)
|
111
|
+
end
|
112
|
+
end # Safety
|
113
|
+
end # FiniteMachine
|
@@ -20,7 +20,7 @@ module FiniteMachine
|
|
20
20
|
attr_threadsafe :state
|
21
21
|
|
22
22
|
# Events DSL
|
23
|
-
attr_threadsafe :
|
23
|
+
attr_threadsafe :events_dsl
|
24
24
|
|
25
25
|
# Errors DSL
|
26
26
|
attr_threadsafe :errors
|
@@ -40,23 +40,27 @@ module FiniteMachine
|
|
40
40
|
# The state machine environment
|
41
41
|
attr_threadsafe :env
|
42
42
|
|
43
|
+
# The state machine event definitions
|
44
|
+
attr_threadsafe :events_chain
|
45
|
+
|
43
46
|
def_delegators :@dsl, :initial, :terminal, :target
|
44
47
|
|
45
|
-
def_delegator :@
|
48
|
+
def_delegator :@events_dsl, :event
|
46
49
|
|
47
50
|
# Initialize state machine
|
48
51
|
#
|
49
52
|
# @api private
|
50
53
|
def initialize(*args, &block)
|
51
|
-
attributes
|
54
|
+
attributes = args.last.is_a?(Hash) ? args.pop : {}
|
52
55
|
@initial_state = DEFAULT_STATE
|
53
|
-
@subscribers
|
54
|
-
@
|
55
|
-
@errors
|
56
|
-
@observer
|
57
|
-
@transitions
|
58
|
-
@
|
59
|
-
@
|
56
|
+
@subscribers = Subscribers.new(self)
|
57
|
+
@events_dsl = EventsDSL.new(self)
|
58
|
+
@errors = ErrorsDSL.new(self)
|
59
|
+
@observer = Observer.new(self)
|
60
|
+
@transitions = Hash.new { |hash, name| hash[name] = Hash.new }
|
61
|
+
@events_chain = {}
|
62
|
+
@env = Environment.new(target: self)
|
63
|
+
@dsl = DSL.new(self, attributes)
|
60
64
|
|
61
65
|
@dsl.call(&block) if block_given?
|
62
66
|
end
|
@@ -75,9 +79,8 @@ module FiniteMachine
|
|
75
79
|
# @api public
|
76
80
|
def notify(event_type, _transition, *data)
|
77
81
|
sync_shared do
|
78
|
-
|
79
|
-
|
80
|
-
_event = event_class.new(state_or_action, _transition, *data)
|
82
|
+
state_or_action = event_type < HookEvent::Anystate ? state : _transition.name
|
83
|
+
_event = event_type.new(state_or_action, _transition, *data)
|
81
84
|
subscribers.visit(_event)
|
82
85
|
end
|
83
86
|
end
|
@@ -142,7 +145,9 @@ module FiniteMachine
|
|
142
145
|
#
|
143
146
|
# @api public
|
144
147
|
def states
|
145
|
-
|
148
|
+
sync_shared do
|
149
|
+
event_names.map { |event| transitions[event].to_a }.flatten.uniq
|
150
|
+
end
|
146
151
|
end
|
147
152
|
|
148
153
|
# Retireve all event names
|
@@ -151,7 +156,7 @@ module FiniteMachine
|
|
151
156
|
#
|
152
157
|
# @api public
|
153
158
|
def event_names
|
154
|
-
transitions.keys
|
159
|
+
sync_shared { transitions.keys }
|
155
160
|
end
|
156
161
|
|
157
162
|
# Checks if event can be triggered
|
@@ -191,6 +196,18 @@ module FiniteMachine
|
|
191
196
|
is?(final_state)
|
192
197
|
end
|
193
198
|
|
199
|
+
# String representation of this machine
|
200
|
+
#
|
201
|
+
# @return [String]
|
202
|
+
#
|
203
|
+
# @api public
|
204
|
+
def inspect
|
205
|
+
sync_shared do
|
206
|
+
"<##{self.class}:0x#{object_id.to_s(16)} @states=#{states}, " \
|
207
|
+
"@events=#{event_names}, @transitions=#{transitions.inspect}>"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
194
211
|
private
|
195
212
|
|
196
213
|
# Check if state is reachable
|
@@ -216,32 +233,40 @@ module FiniteMachine
|
|
216
233
|
#
|
217
234
|
# @api private
|
218
235
|
def transition(_transition, *args, &block)
|
219
|
-
|
236
|
+
sync_exclusive do
|
237
|
+
notify HookEvent::Before, _transition, *args
|
220
238
|
|
221
|
-
|
222
|
-
|
223
|
-
end
|
224
|
-
return NOTRANSITION if _transition.different?(state)
|
239
|
+
return CANCELLED if valid_state?(_transition)
|
240
|
+
return CANCELLED unless _transition.valid?(*args, &block)
|
225
241
|
|
226
|
-
|
227
|
-
notify :exitstate, _transition, *args
|
242
|
+
notify HookEvent::Exit, _transition, *args
|
228
243
|
|
229
244
|
begin
|
230
245
|
_transition.call
|
231
|
-
|
232
|
-
notify
|
233
|
-
notify :transitionaction, _transition, *args
|
246
|
+
|
247
|
+
notify HookEvent::Transition, _transition, *args
|
234
248
|
rescue Exception => e
|
235
|
-
catch_error(e) ||
|
236
|
-
raise(TransitionError, "#(#{e.class}): #{e.message}\n" +
|
237
|
-
"occured at #{e.backtrace.join("\n")}")
|
249
|
+
catch_error(e) || raise_transition_error(e)
|
238
250
|
end
|
239
251
|
|
240
|
-
notify
|
241
|
-
notify
|
252
|
+
notify HookEvent::Enter, _transition, *args
|
253
|
+
notify HookEvent::After, _transition, *args
|
254
|
+
|
255
|
+
_transition.same?(state) ? NOTRANSITION : SUCCEEDED
|
242
256
|
end
|
257
|
+
end
|
243
258
|
|
244
|
-
|
259
|
+
# Raise when failed to transition between states
|
260
|
+
#
|
261
|
+
# @param [Exception] error
|
262
|
+
# the error to describe
|
263
|
+
#
|
264
|
+
# @raise [FiniteMachine::TransitionError]
|
265
|
+
#
|
266
|
+
# @api private
|
267
|
+
def raise_transition_error(error)
|
268
|
+
fail(TransitionError, "#(#{error.class}): #{error.message}\n" \
|
269
|
+
"occured at #{error.backtrace.join("\n")}")
|
245
270
|
end
|
246
271
|
|
247
272
|
# Forward the message to target, observer or self
|
@@ -254,8 +279,8 @@ module FiniteMachine
|
|
254
279
|
#
|
255
280
|
# @api private
|
256
281
|
def method_missing(method_name, *args, &block)
|
257
|
-
if
|
258
|
-
|
282
|
+
if target.respond_to?(method_name.to_sym)
|
283
|
+
target.public_send(method_name.to_sym, *args, &block)
|
259
284
|
elsif observer.respond_to?(method_name.to_sym)
|
260
285
|
observer.public_send(method_name.to_sym, *args, &block)
|
261
286
|
else
|
@@ -273,7 +298,9 @@ module FiniteMachine
|
|
273
298
|
#
|
274
299
|
# @api private
|
275
300
|
def respond_to_missing?(method_name, include_private = false)
|
276
|
-
env.target.respond_to?(method_name.to_sym)
|
301
|
+
env.target.respond_to?(method_name.to_sym) ||
|
302
|
+
observer.respond_to?(method_name.to_sym) ||
|
303
|
+
super
|
277
304
|
end
|
278
305
|
end # StateMachine
|
279
306
|
end # FiniteMachine
|