finite_machine 0.11.3 → 0.12.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 +5 -5
- data/CHANGELOG.md +34 -0
- data/README.md +564 -569
- data/Rakefile +5 -1
- data/benchmarks/memory_profile.rb +11 -0
- data/benchmarks/memory_usage.rb +16 -9
- data/finite_machine.gemspec +10 -3
- data/lib/finite_machine.rb +34 -46
- data/lib/finite_machine/async_call.rb +5 -21
- data/lib/finite_machine/callable.rb +4 -4
- data/lib/finite_machine/catchable.rb +4 -2
- data/lib/finite_machine/choice_merger.rb +19 -19
- data/lib/finite_machine/const.rb +16 -0
- data/lib/finite_machine/definition.rb +2 -2
- data/lib/finite_machine/dsl.rb +66 -149
- data/lib/finite_machine/env.rb +4 -2
- data/lib/finite_machine/event_definition.rb +7 -15
- data/lib/finite_machine/{events_chain.rb → events_map.rb} +39 -51
- data/lib/finite_machine/hook_event.rb +60 -61
- data/lib/finite_machine/hooks.rb +44 -36
- data/lib/finite_machine/listener.rb +2 -2
- data/lib/finite_machine/logger.rb +5 -4
- data/lib/finite_machine/message_queue.rb +39 -30
- data/lib/finite_machine/observer.rb +55 -37
- data/lib/finite_machine/safety.rb +12 -10
- data/lib/finite_machine/state_definition.rb +3 -5
- data/lib/finite_machine/state_machine.rb +83 -64
- data/lib/finite_machine/state_parser.rb +51 -79
- data/lib/finite_machine/subscribers.rb +1 -1
- data/lib/finite_machine/threadable.rb +3 -1
- data/lib/finite_machine/transition.rb +30 -31
- data/lib/finite_machine/transition_builder.rb +23 -32
- data/lib/finite_machine/transition_event.rb +12 -11
- data/lib/finite_machine/two_phase_lock.rb +3 -1
- data/lib/finite_machine/undefined_transition.rb +5 -6
- data/lib/finite_machine/version.rb +2 -2
- data/spec/integration/system_spec.rb +36 -38
- data/spec/performance/benchmark_spec.rb +13 -21
- data/spec/unit/alias_target_spec.rb +22 -41
- data/spec/unit/async_callbacks_spec.rb +8 -13
- data/spec/unit/auto_methods_spec.rb +44 -0
- data/spec/unit/callable/call_spec.rb +1 -3
- data/spec/unit/callbacks_spec.rb +372 -463
- data/spec/unit/can_spec.rb +13 -23
- data/spec/unit/cancel_callbacks_spec.rb +46 -0
- data/spec/unit/choice_spec.rb +105 -141
- data/spec/unit/define_spec.rb +31 -31
- data/spec/unit/definition_spec.rb +24 -41
- data/spec/unit/event_names_spec.rb +6 -10
- data/spec/unit/events_map/add_spec.rb +23 -0
- data/spec/unit/events_map/choice_transition_spec.rb +25 -0
- data/spec/unit/events_map/clear_spec.rb +13 -0
- data/spec/unit/events_map/events_spec.rb +16 -0
- data/spec/unit/events_map/inspect_spec.rb +22 -0
- data/spec/unit/{events_chain → events_map}/match_transition_spec.rb +12 -14
- data/spec/unit/{events_chain → events_map}/move_to_spec.rb +14 -17
- data/spec/unit/events_map/states_for_spec.rb +17 -0
- data/spec/unit/events_spec.rb +91 -160
- data/spec/unit/handlers_spec.rb +34 -66
- data/spec/unit/hook_event/any_state_or_event_spec.rb +13 -0
- data/spec/unit/hook_event/build_spec.rb +1 -3
- data/spec/unit/hook_event/eql_spec.rb +1 -3
- data/spec/unit/hook_event/initialize_spec.rb +2 -4
- data/spec/unit/hook_event/notify_spec.rb +2 -4
- data/spec/unit/hooks/clear_spec.rb +1 -1
- data/spec/unit/hooks/{call_spec.rb → find_spec.rb} +4 -9
- data/spec/unit/hooks/inspect_spec.rb +16 -8
- data/spec/unit/hooks/register_spec.rb +4 -9
- data/spec/unit/if_unless_spec.rb +76 -115
- data/spec/unit/initial_spec.rb +50 -82
- data/spec/unit/inspect_spec.rb +14 -9
- data/spec/unit/is_spec.rb +12 -18
- data/spec/unit/log_transitions_spec.rb +4 -10
- data/spec/unit/logger_spec.rb +1 -3
- data/spec/unit/{event_queue_spec.rb → message_queue_spec.rb} +15 -8
- data/spec/unit/new_spec.rb +50 -0
- data/spec/unit/respond_to_spec.rb +2 -6
- data/spec/unit/state_parser/parse_spec.rb +9 -12
- data/spec/unit/states_spec.rb +12 -18
- data/spec/unit/subscribers_spec.rb +1 -3
- data/spec/unit/target_spec.rb +60 -93
- data/spec/unit/terminated_spec.rb +15 -25
- data/spec/unit/transition/check_conditions_spec.rb +16 -15
- data/spec/unit/transition/inspect_spec.rb +6 -6
- data/spec/unit/transition/matches_spec.rb +5 -7
- data/spec/unit/transition/states_spec.rb +5 -7
- data/spec/unit/transition/to_state_spec.rb +5 -13
- data/spec/unit/trigger_spec.rb +5 -9
- data/spec/unit/undefined_transition/eql_spec.rb +1 -3
- metadata +86 -49
- data/.gitignore +0 -18
- data/.rspec +0 -5
- data/.travis.yml +0 -27
- data/Gemfile +0 -16
- data/assets/finite_machine_logo.png +0 -0
- data/lib/finite_machine/async_proxy.rb +0 -55
- data/spec/unit/async_events_spec.rb +0 -107
- data/spec/unit/events_chain/add_spec.rb +0 -25
- data/spec/unit/events_chain/cancel_transitions_spec.rb +0 -22
- data/spec/unit/events_chain/choice_transition_spec.rb +0 -28
- data/spec/unit/events_chain/clear_spec.rb +0 -15
- data/spec/unit/events_chain/events_spec.rb +0 -18
- data/spec/unit/events_chain/inspect_spec.rb +0 -24
- data/spec/unit/events_chain/states_for_spec.rb +0 -17
- data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
- data/spec/unit/state_parser/inspect_spec.rb +0 -25
@@ -1,7 +1,9 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'hook_event'
|
2
4
|
|
3
5
|
module FiniteMachine
|
4
|
-
# Module
|
6
|
+
# Module responsible for safety checks against known methods
|
5
7
|
module Safety
|
6
8
|
EVENT_CONFLICT_MESSAGE = \
|
7
9
|
"You tried to define an event named \"%{name}\", however this would " \
|
@@ -33,7 +35,7 @@ module FiniteMachine
|
|
33
35
|
# @api public
|
34
36
|
def detect_event_conflict!(event_name, method_name = event_name)
|
35
37
|
if method_already_implemented?(method_name)
|
36
|
-
|
38
|
+
raise FiniteMachine::AlreadyDefinedError, EVENT_CONFLICT_MESSAGE % {
|
37
39
|
name: event_name,
|
38
40
|
type: :instance,
|
39
41
|
method: method_name,
|
@@ -55,12 +57,12 @@ module FiniteMachine
|
|
55
57
|
def ensure_valid_callback_name!(event_type, name)
|
56
58
|
message = if wrong_event_name?(name, event_type)
|
57
59
|
EVENT_CALLBACK_CONFLICT_MESSAGE % {
|
58
|
-
type: "on_#{event_type
|
60
|
+
type: "on_#{event_type}",
|
59
61
|
name: name
|
60
62
|
}
|
61
63
|
elsif wrong_state_name?(name, event_type)
|
62
64
|
STATE_CALLBACK_CONFLICT_MESSAGE % {
|
63
|
-
type: "on_#{event_type
|
65
|
+
type: "on_#{event_type}",
|
64
66
|
name: name
|
65
67
|
}
|
66
68
|
elsif !callback_names.include?(name)
|
@@ -71,7 +73,7 @@ module FiniteMachine
|
|
71
73
|
else
|
72
74
|
nil
|
73
75
|
end
|
74
|
-
message &&
|
76
|
+
message && raise_invalid_callback_error(message)
|
75
77
|
end
|
76
78
|
|
77
79
|
private
|
@@ -87,7 +89,7 @@ module FiniteMachine
|
|
87
89
|
# @api private
|
88
90
|
def wrong_event_name?(name, event_type)
|
89
91
|
machine.states.include?(name) &&
|
90
|
-
!machine.
|
92
|
+
!machine.events.include?(name) &&
|
91
93
|
event_type < HookEvent::Anyaction
|
92
94
|
end
|
93
95
|
|
@@ -101,14 +103,14 @@ module FiniteMachine
|
|
101
103
|
#
|
102
104
|
# @api private
|
103
105
|
def wrong_state_name?(name, event_type)
|
104
|
-
machine.
|
106
|
+
machine.events.include?(name) &&
|
105
107
|
!machine.states.include?(name) &&
|
106
108
|
event_type < HookEvent::Anystate
|
107
109
|
end
|
108
110
|
|
109
|
-
def
|
111
|
+
def raise_invalid_callback_error(message)
|
110
112
|
exception = InvalidCallbackNameError
|
111
|
-
machine.catch_error(exception) ||
|
113
|
+
machine.catch_error(exception) || raise(exception, message)
|
112
114
|
end
|
113
115
|
|
114
116
|
# Check if method is already implemented inside StateMachine
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FiniteMachine
|
4
4
|
# A class responsible for defining state query methods on state machine
|
@@ -8,15 +8,13 @@ module FiniteMachine
|
|
8
8
|
#
|
9
9
|
# @api private
|
10
10
|
class StateDefinition
|
11
|
-
include Threadable
|
12
|
-
|
13
11
|
# Initialize a StateDefinition
|
14
12
|
#
|
15
13
|
# @param [StateMachine] machine
|
16
14
|
#
|
17
15
|
# @api public
|
18
16
|
def initialize(machine)
|
19
|
-
|
17
|
+
@machine = machine
|
20
18
|
end
|
21
19
|
|
22
20
|
# Define query methods for states
|
@@ -34,7 +32,7 @@ module FiniteMachine
|
|
34
32
|
private
|
35
33
|
|
36
34
|
# The current state machine
|
37
|
-
|
35
|
+
attr_reader :machine
|
38
36
|
|
39
37
|
# Define helper state mehods for the transition states
|
40
38
|
#
|
@@ -1,4 +1,15 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
require_relative 'catchable'
|
6
|
+
require_relative 'dsl'
|
7
|
+
require_relative 'env'
|
8
|
+
require_relative 'events_map'
|
9
|
+
require_relative 'hook_event'
|
10
|
+
require_relative 'observer'
|
11
|
+
require_relative 'threadable'
|
12
|
+
require_relative 'subscribers'
|
2
13
|
|
3
14
|
module FiniteMachine
|
4
15
|
# Base class for state machine
|
@@ -7,6 +18,13 @@ module FiniteMachine
|
|
7
18
|
include Catchable
|
8
19
|
extend Forwardable
|
9
20
|
|
21
|
+
# Current state
|
22
|
+
#
|
23
|
+
# @return [Symbol]
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
attr_threadsafe :state
|
27
|
+
|
10
28
|
# Initial state, defaults to :none
|
11
29
|
attr_threadsafe :initial_state
|
12
30
|
|
@@ -20,21 +38,14 @@ module FiniteMachine
|
|
20
38
|
attr_threadsafe :env
|
21
39
|
|
22
40
|
# The state machine event definitions
|
23
|
-
attr_threadsafe :
|
24
|
-
|
25
|
-
# Errors DSL
|
26
|
-
#
|
27
|
-
# @return [ErrorsDSL]
|
28
|
-
#
|
29
|
-
# @api private
|
30
|
-
attr_threadsafe :errors_dsl
|
41
|
+
attr_threadsafe :events_map
|
31
42
|
|
32
|
-
#
|
43
|
+
# Machine dsl
|
33
44
|
#
|
34
|
-
# @return [
|
45
|
+
# @return [DSL]
|
35
46
|
#
|
36
47
|
# @api private
|
37
|
-
attr_threadsafe :
|
48
|
+
attr_threadsafe :dsl
|
38
49
|
|
39
50
|
# The state machine observer
|
40
51
|
#
|
@@ -43,13 +54,6 @@ module FiniteMachine
|
|
43
54
|
# @api private
|
44
55
|
attr_threadsafe :observer
|
45
56
|
|
46
|
-
# Current state
|
47
|
-
#
|
48
|
-
# @return [Symbol]
|
49
|
-
#
|
50
|
-
# @api private
|
51
|
-
attr_threadsafe :state
|
52
|
-
|
53
57
|
# The state machine subscribers
|
54
58
|
#
|
55
59
|
# @return [Subscribers]
|
@@ -60,64 +64,76 @@ module FiniteMachine
|
|
60
64
|
# Allow or not logging of transitions
|
61
65
|
attr_threadsafe :log_transitions
|
62
66
|
|
63
|
-
def_delegators
|
64
|
-
:alias_target
|
65
|
-
|
66
|
-
def_delegator :events_dsl, :event
|
67
|
-
|
68
|
-
def_delegator :@async_proxy, :event_queue
|
67
|
+
def_delegators :dsl, :initial, :terminal, :event, :trigger_init
|
69
68
|
|
70
69
|
# Initialize state machine
|
71
70
|
#
|
71
|
+
# @example
|
72
|
+
# fsm = FiniteMachine::StateMachine.new(target_alias: :car) do
|
73
|
+
# initial :red
|
74
|
+
#
|
75
|
+
# event :go, :red => :green
|
76
|
+
#
|
77
|
+
# on_transition do |event|
|
78
|
+
# car.state = event.to
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# @param [Hash] options
|
83
|
+
# the options to create state machine with
|
84
|
+
# @option options [String] :alias_target
|
85
|
+
# the alias for target object
|
86
|
+
#
|
72
87
|
# @api private
|
73
|
-
def initialize(*args, &block)
|
74
|
-
attributes = args.last.is_a?(Hash) ? args.pop : {}
|
75
|
-
|
88
|
+
def initialize(*args, **options, &block)
|
76
89
|
@initial_state = DEFAULT_STATE
|
77
|
-
@
|
90
|
+
@auto_methods = options.fetch(:auto_methods, true)
|
78
91
|
@subscribers = Subscribers.new
|
79
92
|
@observer = Observer.new(self)
|
80
|
-
@
|
93
|
+
@events_map = EventsMap.new
|
81
94
|
@env = Env.new(self, [])
|
82
|
-
@
|
83
|
-
@errors_dsl = ErrorsDSL.new(self)
|
84
|
-
@dsl = DSL.new(self, attributes)
|
95
|
+
@dsl = DSL.new(self, options)
|
85
96
|
|
86
|
-
|
97
|
+
env.target = args.pop unless args.empty?
|
98
|
+
env.aliases << options[:alias_target] if options[:alias_target]
|
99
|
+
dsl.call(&block) if block_given?
|
87
100
|
trigger_init
|
88
101
|
end
|
89
102
|
|
90
|
-
#
|
103
|
+
# Check if event methods should be auto generated
|
91
104
|
#
|
92
|
-
# @
|
93
|
-
# machine.subscribe(Observer.new(machine))
|
105
|
+
# @return [Boolean]
|
94
106
|
#
|
95
107
|
# @api public
|
96
|
-
def
|
97
|
-
|
108
|
+
def auto_methods?
|
109
|
+
@auto_methods
|
98
110
|
end
|
99
111
|
|
100
|
-
#
|
112
|
+
# Attach state machine to an object
|
113
|
+
#
|
114
|
+
# This allows state machine to initiate events in the context
|
115
|
+
# of a particular object
|
101
116
|
#
|
102
117
|
# @example
|
103
|
-
#
|
118
|
+
# FiniteMachine.define(target: object) do
|
119
|
+
# ...
|
120
|
+
# end
|
104
121
|
#
|
105
|
-
# @return [
|
122
|
+
# @return [Object|FiniteMachine::StateMachine]
|
106
123
|
#
|
107
124
|
# @api public
|
108
|
-
|
125
|
+
def target
|
126
|
+
env.target
|
127
|
+
end
|
109
128
|
|
110
|
-
#
|
129
|
+
# Subscribe observer for event notifications
|
111
130
|
#
|
112
|
-
# @
|
131
|
+
# @example
|
132
|
+
# machine.subscribe(Observer.new(machine))
|
113
133
|
#
|
114
134
|
# @api public
|
115
|
-
def
|
116
|
-
|
117
|
-
@async_proxy.method_missing(method_name, *args, &block)
|
118
|
-
else
|
119
|
-
@async_proxy
|
120
|
-
end
|
135
|
+
def subscribe(*observers)
|
136
|
+
sync_exclusive { subscribers.subscribe(*observers) }
|
121
137
|
end
|
122
138
|
|
123
139
|
# Get current state
|
@@ -126,7 +142,7 @@ module FiniteMachine
|
|
126
142
|
#
|
127
143
|
# @api public
|
128
144
|
def current
|
129
|
-
state
|
145
|
+
sync_shared { state }
|
130
146
|
end
|
131
147
|
|
132
148
|
# Check if current state matches provided state
|
@@ -156,19 +172,19 @@ module FiniteMachine
|
|
156
172
|
#
|
157
173
|
# @api public
|
158
174
|
def states
|
159
|
-
sync_shared {
|
175
|
+
sync_shared { events_map.states }
|
160
176
|
end
|
161
177
|
|
162
178
|
# Retireve all event names
|
163
179
|
#
|
164
180
|
# @example
|
165
|
-
# fsm.
|
181
|
+
# fsm.events # => [:init, :start, :stop]
|
166
182
|
#
|
167
183
|
# @return [Array[Symbol]]
|
168
184
|
#
|
169
185
|
# @api public
|
170
|
-
def
|
171
|
-
|
186
|
+
def events
|
187
|
+
events_map.events
|
172
188
|
end
|
173
189
|
|
174
190
|
# Checks if event can be triggered
|
@@ -186,7 +202,7 @@ module FiniteMachine
|
|
186
202
|
# @api public
|
187
203
|
def can?(*args)
|
188
204
|
event_name = args.shift
|
189
|
-
|
205
|
+
events_map.can_perform?(event_name, current, *args)
|
190
206
|
end
|
191
207
|
|
192
208
|
# Checks if event cannot be triggered
|
@@ -232,7 +248,7 @@ module FiniteMachine
|
|
232
248
|
#
|
233
249
|
# @api private
|
234
250
|
def valid_state?(event_name)
|
235
|
-
current_states =
|
251
|
+
current_states = events_map.states_for(event_name)
|
236
252
|
current_states.any? { |state| state == current || state == ANY_STATE }
|
237
253
|
end
|
238
254
|
|
@@ -284,9 +300,9 @@ module FiniteMachine
|
|
284
300
|
#
|
285
301
|
# @api public
|
286
302
|
def trigger!(event_name, *data, &block)
|
287
|
-
|
288
|
-
from = current # Save away current state
|
303
|
+
from = current # Save away current state
|
289
304
|
|
305
|
+
sync_exclusive do
|
290
306
|
notify HookEvent::Before, event_name, from, *data
|
291
307
|
|
292
308
|
status = try_trigger(event_name) do
|
@@ -307,6 +323,9 @@ module FiniteMachine
|
|
307
323
|
|
308
324
|
status
|
309
325
|
end
|
326
|
+
rescue Exception => err
|
327
|
+
self.state = from # rollback transition
|
328
|
+
raise err
|
310
329
|
end
|
311
330
|
|
312
331
|
# Trigger transition event without raising any errors
|
@@ -319,7 +338,7 @@ module FiniteMachine
|
|
319
338
|
# @api public
|
320
339
|
def trigger(event_name, *data, &block)
|
321
340
|
trigger!(event_name, *data, &block)
|
322
|
-
rescue InvalidStateError, TransitionError
|
341
|
+
rescue InvalidStateError, TransitionError, CallbackError
|
323
342
|
false
|
324
343
|
end
|
325
344
|
|
@@ -330,7 +349,7 @@ module FiniteMachine
|
|
330
349
|
# @api private
|
331
350
|
def transition!(event_name, *data, &block)
|
332
351
|
from_state = current
|
333
|
-
to_state =
|
352
|
+
to_state = events_map.move_to(event_name, from_state, *data)
|
334
353
|
|
335
354
|
block.call(from_state, to_state) if block
|
336
355
|
|
@@ -371,8 +390,8 @@ module FiniteMachine
|
|
371
390
|
def inspect
|
372
391
|
sync_shared do
|
373
392
|
"<##{self.class}:0x#{object_id.to_s(16)} @states=#{states}, " \
|
374
|
-
"@events=#{
|
375
|
-
"@transitions=#{
|
393
|
+
"@events=#{events}, " \
|
394
|
+
"@transitions=#{events_map.state_transitions}>"
|
376
395
|
end
|
377
396
|
end
|
378
397
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FiniteMachine
|
4
4
|
# A class responsible for converting transition arguments to states
|
@@ -7,114 +7,98 @@ module FiniteMachine
|
|
7
7
|
#
|
8
8
|
# @api private
|
9
9
|
class StateParser
|
10
|
-
include Threadable
|
11
|
-
|
12
|
-
attr_threadsafe :attrs
|
13
|
-
|
14
10
|
BLACKLIST = [:name, :if, :unless, :silent].freeze
|
15
11
|
|
16
|
-
#
|
12
|
+
# Extract states from user defined attributes
|
17
13
|
#
|
18
14
|
# @example
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# @param [Hash] attrs
|
22
|
-
#
|
23
|
-
# @api public
|
24
|
-
def initialize(attrs)
|
25
|
-
@attrs = ensure_only_states!(attrs)
|
26
|
-
freeze
|
27
|
-
end
|
28
|
-
|
29
|
-
# Extract states from attributes
|
15
|
+
# StateParser.parse({from: [:green, :blue], to: :red})
|
16
|
+
# # => {green: :red, green: :blue}
|
30
17
|
#
|
31
18
|
# @param [Proc] block
|
32
19
|
#
|
33
|
-
# @example
|
34
|
-
# StateParpser.new(attr).parase_states
|
35
|
-
#
|
36
20
|
# @yield [Hash[Symbol]] the resolved states
|
37
21
|
#
|
38
22
|
# @return [Hash[Symbol]] the resolved states
|
39
23
|
#
|
40
24
|
# @api public
|
41
|
-
def parse(&block)
|
42
|
-
|
25
|
+
def self.parse(attributes, &block)
|
26
|
+
attrs = ensure_only_states!(attributes)
|
27
|
+
states = extract_states(attrs)
|
43
28
|
block ? states.each(&block) : states
|
44
29
|
end
|
45
30
|
|
46
|
-
#
|
47
|
-
#
|
48
|
-
# @example
|
49
|
-
# parser = StateParser.new({from: :green, to: :red})
|
50
|
-
# parser.contains_from_to_keys? # => true
|
51
|
-
#
|
52
|
-
# @example
|
53
|
-
# parser = StateParser.new({:green => :red})
|
54
|
-
# parser.contains_from_to_keys? # => false
|
31
|
+
# Extract only states from attributes
|
55
32
|
#
|
56
|
-
# @return [
|
33
|
+
# @return [Hash[Symbol]]
|
57
34
|
#
|
58
|
-
# @api
|
59
|
-
def
|
60
|
-
|
35
|
+
# @api private
|
36
|
+
def self.ensure_only_states!(attrs)
|
37
|
+
attributes = attrs.dup
|
38
|
+
BLACKLIST.each { |key| attributes.delete(key) }
|
39
|
+
raise_not_enough_transitions unless attributes.any?
|
40
|
+
attributes
|
61
41
|
end
|
42
|
+
private_class_method :ensure_only_states!
|
62
43
|
|
63
|
-
#
|
44
|
+
# Perform extraction of states from user supplied definitions
|
64
45
|
#
|
65
|
-
# @return [
|
46
|
+
# @return [Hash[Symbol]] the resolved states
|
66
47
|
#
|
67
|
-
# @api
|
68
|
-
def
|
69
|
-
attrs
|
48
|
+
# @api private
|
49
|
+
def self.extract_states(attrs)
|
50
|
+
if contains_from_to_keys?(attrs)
|
51
|
+
convert_from_to_attributes_to_states_hash(attrs)
|
52
|
+
else
|
53
|
+
convert_attributes_to_states_hash(attrs)
|
54
|
+
end
|
70
55
|
end
|
56
|
+
private_class_method :extract_states
|
71
57
|
|
72
|
-
#
|
58
|
+
# Check if attributes contain :from or :to key
|
73
59
|
#
|
74
|
-
# @
|
60
|
+
# @example
|
61
|
+
# StateParser.contains_from_to_keys?({from: :green, to: :red})
|
62
|
+
# # => true
|
75
63
|
#
|
76
|
-
# @
|
77
|
-
|
78
|
-
|
79
|
-
"<##{self.class} @attrs=#{attributes}>"
|
80
|
-
end
|
81
|
-
|
82
|
-
private
|
83
|
-
|
84
|
-
# Extract only states from attributes
|
64
|
+
# @example
|
65
|
+
# StateParser.contains_from_to_keys?({:green => :red})
|
66
|
+
# # => false
|
85
67
|
#
|
86
|
-
# @return [
|
68
|
+
# @return [Boolean]
|
87
69
|
#
|
88
|
-
# @api
|
89
|
-
def
|
90
|
-
|
91
|
-
BLACKLIST.each { |key| attributes.delete(key) }
|
92
|
-
raise_not_enough_transitions unless attributes.any?
|
93
|
-
attributes
|
70
|
+
# @api public
|
71
|
+
def self.contains_from_to_keys?(attrs)
|
72
|
+
[:from, :to].any? { |key| attrs.keys.include?(key) }
|
94
73
|
end
|
74
|
+
private_class_method :contains_from_to_keys?
|
95
75
|
|
96
76
|
# Convert attrbiutes with :from, :to keys to states hash
|
97
77
|
#
|
98
78
|
# @return [Hash[Symbol]]
|
99
79
|
#
|
100
80
|
# @api private
|
101
|
-
def convert_from_to_attributes_to_states_hash
|
81
|
+
def self.convert_from_to_attributes_to_states_hash(attrs)
|
102
82
|
Array(attrs[:from] || ANY_STATE).reduce({}) do |hash, state|
|
103
83
|
hash[state] = attrs[:to] || state
|
104
84
|
hash
|
105
85
|
end
|
106
86
|
end
|
87
|
+
private_class_method :convert_from_to_attributes_to_states_hash
|
107
88
|
|
108
89
|
# Convert collapsed attributes to states hash
|
109
90
|
#
|
110
91
|
# @example
|
111
|
-
#
|
112
|
-
#
|
92
|
+
# StateParser.convert_attributes_to_states_hash([:green, :red] => :yellow)
|
93
|
+
# # => {green: :yellow, red: :yellow}
|
94
|
+
#
|
95
|
+
# @param [Hash] attrs
|
96
|
+
# the attributes to convert to a simple hash
|
113
97
|
#
|
114
98
|
# @return [Hash[Symbol]]
|
115
99
|
#
|
116
100
|
# @api private
|
117
|
-
def convert_attributes_to_states_hash
|
101
|
+
def self.convert_attributes_to_states_hash(attrs)
|
118
102
|
attrs.reduce({}) do |hash, (k, v)|
|
119
103
|
if k.respond_to?(:to_ary)
|
120
104
|
k.each { |el| hash[el] = v }
|
@@ -124,31 +108,19 @@ module FiniteMachine
|
|
124
108
|
hash
|
125
109
|
end
|
126
110
|
end
|
127
|
-
|
128
|
-
# Perform extraction of states from user supplied definitions
|
129
|
-
#
|
130
|
-
# @return [Hash[Symbol]] the resolved states
|
131
|
-
#
|
132
|
-
# @api private
|
133
|
-
def extract_states
|
134
|
-
if contains_from_to_keys?
|
135
|
-
convert_from_to_attributes_to_states_hash
|
136
|
-
else
|
137
|
-
convert_attributes_to_states_hash
|
138
|
-
end
|
139
|
-
end
|
111
|
+
private_class_method :convert_attributes_to_states_hash
|
140
112
|
|
141
113
|
# Raise error when not enough transitions are provided
|
142
114
|
#
|
143
115
|
# @raise [NotEnoughTransitionsError]
|
144
|
-
# if the event has
|
116
|
+
# if the event has no transitions
|
145
117
|
#
|
146
118
|
# @return [nil]
|
147
119
|
#
|
148
120
|
# @api private
|
149
|
-
def raise_not_enough_transitions
|
150
|
-
|
151
|
-
" '#{attrs.inspect}'"
|
121
|
+
def self.raise_not_enough_transitions
|
122
|
+
raise NotEnoughTransitionsError, 'please provide state transitions'
|
152
123
|
end
|
124
|
+
private_class_method :raise_not_enough_transitions
|
153
125
|
end # StateParser
|
154
126
|
end # FiniteMachine
|