finite_machine 0.11.2 → 0.14.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.
Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +80 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +679 -624
  5. data/lib/finite_machine.rb +35 -45
  6. data/lib/finite_machine/async_call.rb +5 -21
  7. data/lib/finite_machine/callable.rb +4 -4
  8. data/lib/finite_machine/catchable.rb +24 -14
  9. data/lib/finite_machine/choice_merger.rb +20 -20
  10. data/lib/finite_machine/const.rb +16 -0
  11. data/lib/finite_machine/definition.rb +3 -3
  12. data/lib/finite_machine/dsl.rb +98 -151
  13. data/lib/finite_machine/env.rb +4 -2
  14. data/lib/finite_machine/event_definition.rb +7 -15
  15. data/lib/finite_machine/{events_chain.rb → events_map.rb} +40 -53
  16. data/lib/finite_machine/hook_event.rb +60 -61
  17. data/lib/finite_machine/hooks.rb +44 -36
  18. data/lib/finite_machine/listener.rb +2 -2
  19. data/lib/finite_machine/logger.rb +5 -4
  20. data/lib/finite_machine/{event_queue.rb → message_queue.rb} +76 -32
  21. data/lib/finite_machine/observer.rb +71 -34
  22. data/lib/finite_machine/safety.rb +16 -14
  23. data/lib/finite_machine/state_definition.rb +3 -5
  24. data/lib/finite_machine/state_machine.rb +93 -76
  25. data/lib/finite_machine/state_parser.rb +55 -83
  26. data/lib/finite_machine/subscribers.rb +2 -2
  27. data/lib/finite_machine/threadable.rb +3 -1
  28. data/lib/finite_machine/transition.rb +34 -34
  29. data/lib/finite_machine/transition_builder.rb +23 -32
  30. data/lib/finite_machine/transition_event.rb +12 -11
  31. data/lib/finite_machine/two_phase_lock.rb +8 -6
  32. data/lib/finite_machine/undefined_transition.rb +5 -6
  33. data/lib/finite_machine/version.rb +2 -2
  34. metadata +58 -142
  35. data/.gitignore +0 -18
  36. data/.rspec +0 -5
  37. data/.ruby-gemset +0 -1
  38. data/.ruby-version +0 -1
  39. data/.travis.yml +0 -26
  40. data/Gemfile +0 -15
  41. data/Rakefile +0 -8
  42. data/assets/finite_machine_logo.png +0 -0
  43. data/examples/atm.rb +0 -45
  44. data/examples/bug_system.rb +0 -145
  45. data/finite_machine.gemspec +0 -23
  46. data/lib/finite_machine/async_proxy.rb +0 -30
  47. data/spec/integration/system_spec.rb +0 -95
  48. data/spec/spec_helper.rb +0 -33
  49. data/spec/unit/alias_target_spec.rb +0 -108
  50. data/spec/unit/async_events_spec.rb +0 -138
  51. data/spec/unit/callable/call_spec.rb +0 -113
  52. data/spec/unit/callbacks_spec.rb +0 -942
  53. data/spec/unit/can_spec.rb +0 -98
  54. data/spec/unit/choice_spec.rb +0 -331
  55. data/spec/unit/define_spec.rb +0 -55
  56. data/spec/unit/definition_spec.rb +0 -115
  57. data/spec/unit/event_names_spec.rb +0 -19
  58. data/spec/unit/event_queue_spec.rb +0 -52
  59. data/spec/unit/events_chain/add_spec.rb +0 -25
  60. data/spec/unit/events_chain/cancel_transitions_spec.rb +0 -22
  61. data/spec/unit/events_chain/choice_transition_spec.rb +0 -28
  62. data/spec/unit/events_chain/clear_spec.rb +0 -15
  63. data/spec/unit/events_chain/events_spec.rb +0 -18
  64. data/spec/unit/events_chain/inspect_spec.rb +0 -24
  65. data/spec/unit/events_chain/match_transition_spec.rb +0 -37
  66. data/spec/unit/events_chain/move_to_spec.rb +0 -48
  67. data/spec/unit/events_chain/states_for_spec.rb +0 -17
  68. data/spec/unit/events_spec.rb +0 -459
  69. data/spec/unit/handlers_spec.rb +0 -152
  70. data/spec/unit/hook_event/build_spec.rb +0 -15
  71. data/spec/unit/hook_event/eql_spec.rb +0 -36
  72. data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
  73. data/spec/unit/hook_event/initialize_spec.rb +0 -25
  74. data/spec/unit/hook_event/notify_spec.rb +0 -14
  75. data/spec/unit/hooks/call_spec.rb +0 -24
  76. data/spec/unit/hooks/clear_spec.rb +0 -16
  77. data/spec/unit/hooks/inspect_spec.rb +0 -17
  78. data/spec/unit/hooks/register_spec.rb +0 -22
  79. data/spec/unit/if_unless_spec.rb +0 -353
  80. data/spec/unit/initial_spec.rb +0 -222
  81. data/spec/unit/inspect_spec.rb +0 -17
  82. data/spec/unit/is_spec.rb +0 -55
  83. data/spec/unit/log_transitions_spec.rb +0 -30
  84. data/spec/unit/logger_spec.rb +0 -38
  85. data/spec/unit/respond_to_spec.rb +0 -38
  86. data/spec/unit/state_parser/inspect_spec.rb +0 -25
  87. data/spec/unit/state_parser/parse_spec.rb +0 -59
  88. data/spec/unit/states_spec.rb +0 -34
  89. data/spec/unit/subscribers_spec.rb +0 -42
  90. data/spec/unit/target_spec.rb +0 -225
  91. data/spec/unit/terminated_spec.rb +0 -95
  92. data/spec/unit/transition/check_conditions_spec.rb +0 -54
  93. data/spec/unit/transition/inspect_spec.rb +0 -25
  94. data/spec/unit/transition/matches_spec.rb +0 -23
  95. data/spec/unit/transition/states_spec.rb +0 -31
  96. data/spec/unit/transition/to_state_spec.rb +0 -27
  97. data/spec/unit/trigger_spec.rb +0 -22
  98. data/spec/unit/undefined_transition/eql_spec.rb +0 -17
  99. data/tasks/console.rake +0 -11
  100. data/tasks/coverage.rake +0 -11
  101. data/tasks/spec.rake +0 -29
@@ -1,7 +1,9 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "hook_event"
2
4
 
3
5
  module FiniteMachine
4
- # Module for responsible for safety checks against known methods
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,11 +35,11 @@ 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
- fail FiniteMachine::AlreadyDefinedError, EVENT_CONFLICT_MESSAGE % {
38
+ raise FiniteMachine::AlreadyDefinedError, EVENT_CONFLICT_MESSAGE % {
37
39
  name: event_name,
38
40
  type: :instance,
39
41
  method: method_name,
40
- source: 'FiniteMachine'
42
+ source: "FiniteMachine"
41
43
  }
42
44
  end
43
45
  end
@@ -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.to_s}",
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.to_s}",
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 && fail_invalid_callback_error(message)
76
+ message && raise_invalid_callback_error(message)
75
77
  end
76
78
 
77
79
  private
@@ -87,8 +89,8 @@ module FiniteMachine
87
89
  # @api private
88
90
  def wrong_event_name?(name, event_type)
89
91
  machine.states.include?(name) &&
90
- !machine.event_names.include?(name) &&
91
- event_type < HookEvent::Anyaction
92
+ !machine.events.include?(name) &&
93
+ event_type < HookEvent::Anyaction
92
94
  end
93
95
 
94
96
  # Check if state name exists
@@ -101,14 +103,14 @@ module FiniteMachine
101
103
  #
102
104
  # @api private
103
105
  def wrong_state_name?(name, event_type)
104
- machine.event_names.include?(name) &&
105
- !machine.states.include?(name) &&
106
- event_type < HookEvent::Anystate
106
+ machine.events.include?(name) &&
107
+ !machine.states.include?(name) &&
108
+ event_type < HookEvent::Anystate
107
109
  end
108
110
 
109
- def fail_invalid_callback_error(message)
111
+ def raise_invalid_callback_error(message)
110
112
  exception = InvalidCallbackNameError
111
- machine.catch_error(exception) || fail(exception, message)
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
- # encoding: utf-8
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
- self.machine = machine
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
- attr_threadsafe :machine
35
+ attr_reader :machine
38
36
 
39
37
  # Define helper state mehods for the transition states
40
38
  #
@@ -1,4 +1,15 @@
1
- # encoding: utf-8
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,11 +18,18 @@ 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
 
13
- # Final state, defaults to :none
14
- attr_threadsafe :final_state
31
+ # Final state, defaults to nil
32
+ attr_threadsafe :terminal_states
15
33
 
16
34
  # The prefix used to name events.
17
35
  attr_threadsafe :namespace
@@ -20,21 +38,14 @@ module FiniteMachine
20
38
  attr_threadsafe :env
21
39
 
22
40
  # The state machine event definitions
23
- attr_threadsafe :events_chain
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
- # Events DSL
43
+ # Machine dsl
33
44
  #
34
- # @return [EventsDSL]
45
+ # @return [DSL]
35
46
  #
36
47
  # @api private
37
- attr_threadsafe :events_dsl
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]
@@ -57,73 +61,81 @@ module FiniteMachine
57
61
  # @api private
58
62
  attr_threadsafe :subscribers
59
63
 
60
- # The queue for asynchronoous events
61
- #
62
- # @return [EventQueue]
63
- #
64
- # @api private
65
- attr_threadsafe :event_queue
66
-
67
64
  # Allow or not logging of transitions
68
65
  attr_threadsafe :log_transitions
69
66
 
70
- def_delegators :@dsl, :initial, :terminal, :target, :trigger_init,
67
+ def_delegators :dsl, :initial, :terminal, :event, :trigger_init,
71
68
  :alias_target
72
69
 
73
- def_delegator :events_dsl, :event
74
-
75
70
  # Initialize state machine
76
71
  #
72
+ # @example
73
+ # fsm = FiniteMachine::StateMachine.new(target_alias: :car) do
74
+ # initial :red
75
+ #
76
+ # event :go, :red => :green
77
+ #
78
+ # on_transition do |event|
79
+ # car.state = event.to
80
+ # end
81
+ # end
82
+ #
83
+ # @param [Hash] options
84
+ # the options to create state machine with
85
+ # @option options [String] :alias_target
86
+ # the alias for target object
87
+ #
77
88
  # @api private
78
89
  def initialize(*args, &block)
79
- attributes = args.last.is_a?(Hash) ? args.pop : {}
80
-
81
- @event_queue = EventQueue.new
90
+ options = args.last.is_a?(::Hash) ? args.pop : {}
82
91
  @initial_state = DEFAULT_STATE
83
- @async_proxy = AsyncProxy.new(self)
92
+ @auto_methods = options.fetch(:auto_methods, true)
84
93
  @subscribers = Subscribers.new
85
94
  @observer = Observer.new(self)
86
- @events_chain = EventsChain.new
95
+ @events_map = EventsMap.new
87
96
  @env = Env.new(self, [])
88
- @events_dsl = EventsDSL.new(self)
89
- @errors_dsl = ErrorsDSL.new(self)
90
- @dsl = DSL.new(self, attributes)
97
+ @dsl = DSL.new(self, options)
91
98
 
92
- @dsl.call(&block) if block_given?
99
+ env.target = args.pop unless args.empty?
100
+ env.aliases << options[:alias_target] if options[:alias_target]
101
+ dsl.call(&block) if block_given?
93
102
  trigger_init
94
103
  end
95
104
 
96
- # Subscribe observer for event notifications
105
+ # Check if event methods should be auto generated
97
106
  #
98
- # @example
99
- # machine.subscribe(Observer.new(machine))
107
+ # @return [Boolean]
100
108
  #
101
109
  # @api public
102
- def subscribe(*observers)
103
- sync_exclusive { subscribers.subscribe(*observers) }
110
+ def auto_methods?
111
+ @auto_methods
104
112
  end
105
113
 
106
- # Help to mark the event as synchronous
114
+ # Attach state machine to an object
115
+ #
116
+ # This allows state machine to initiate events in the context
117
+ # of a particular object
107
118
  #
108
119
  # @example
109
- # fsm.sync.go
120
+ # FiniteMachine.define(target: object) do
121
+ # ...
122
+ # end
110
123
  #
111
- # @return [self]
124
+ # @return [Object|FiniteMachine::StateMachine]
112
125
  #
113
126
  # @api public
114
- alias_method :sync, :method_missing
127
+ def target
128
+ env.target
129
+ end
115
130
 
116
- # Explicitly invoke event on proxy or delegate to proxy
131
+ # Subscribe observer for event notifications
117
132
  #
118
- # @return [AsyncProxy]
133
+ # @example
134
+ # machine.subscribe(Observer.new(machine))
119
135
  #
120
136
  # @api public
121
- def async(method_name = nil, *args, &block)
122
- if method_name
123
- @async_proxy.method_missing method_name, *args, &block
124
- else
125
- @async_proxy
126
- end
137
+ def subscribe(*observers)
138
+ sync_exclusive { subscribers.subscribe(*observers) }
127
139
  end
128
140
 
129
141
  # Get current state
@@ -132,7 +144,7 @@ module FiniteMachine
132
144
  #
133
145
  # @api public
134
146
  def current
135
- state
147
+ sync_shared { state }
136
148
  end
137
149
 
138
150
  # Check if current state matches provided state
@@ -162,19 +174,19 @@ module FiniteMachine
162
174
  #
163
175
  # @api public
164
176
  def states
165
- sync_shared { events_chain.states }
177
+ sync_shared { events_map.states }
166
178
  end
167
179
 
168
180
  # Retireve all event names
169
181
  #
170
182
  # @example
171
- # fsm.event_names # => [:init, :start, :stop]
183
+ # fsm.events # => [:init, :start, :stop]
172
184
  #
173
185
  # @return [Array[Symbol]]
174
186
  #
175
187
  # @api public
176
- def event_names
177
- sync_shared { events_chain.events }
188
+ def events
189
+ events_map.events
178
190
  end
179
191
 
180
192
  # Checks if event can be triggered
@@ -183,7 +195,7 @@ module FiniteMachine
183
195
  # fsm.can?(:go) # => true
184
196
  #
185
197
  # @example
186
- # fsm.can?(:go, 'Piotr') # checks condition with parameter 'Piotr'
198
+ # fsm.can?(:go, "Piotr") # checks condition with parameter "Piotr"
187
199
  #
188
200
  # @param [String] event
189
201
  #
@@ -191,8 +203,8 @@ module FiniteMachine
191
203
  #
192
204
  # @api public
193
205
  def can?(*args)
194
- event_name = args.shift
195
- events_chain.can_perform?(event_name, current, *args)
206
+ event_name = args.shift
207
+ events_map.can_perform?(event_name, current, *args)
196
208
  end
197
209
 
198
210
  # Checks if event cannot be triggered
@@ -215,7 +227,7 @@ module FiniteMachine
215
227
  #
216
228
  # @api public
217
229
  def terminated?
218
- is?(final_state)
230
+ is?(terminal_states)
219
231
  end
220
232
 
221
233
  # Restore this machine to a known state
@@ -238,7 +250,7 @@ module FiniteMachine
238
250
  #
239
251
  # @api private
240
252
  def valid_state?(event_name)
241
- current_states = events_chain.states_for(event_name)
253
+ current_states = events_map.states_for(event_name)
242
254
  current_states.any? { |state| state == current || state == ANY_STATE }
243
255
  end
244
256
 
@@ -273,7 +285,7 @@ module FiniteMachine
273
285
  else
274
286
  exception = InvalidStateError
275
287
  catch_error(exception) ||
276
- fail(exception, "inappropriate current state '#{current}'")
288
+ raise(exception, "inappropriate current state '#{current}'")
277
289
 
278
290
  false
279
291
  end
@@ -290,9 +302,9 @@ module FiniteMachine
290
302
  #
291
303
  # @api public
292
304
  def trigger!(event_name, *data, &block)
293
- sync_exclusive do
294
- from = current # Save away current state
305
+ from = current # Save away current state
295
306
 
307
+ sync_exclusive do
296
308
  notify HookEvent::Before, event_name, from, *data
297
309
 
298
310
  status = try_trigger(event_name) do
@@ -313,6 +325,9 @@ module FiniteMachine
313
325
 
314
326
  status
315
327
  end
328
+ rescue Exception => err
329
+ self.state = from # rollback transition
330
+ raise err
316
331
  end
317
332
 
318
333
  # Trigger transition event without raising any errors
@@ -325,7 +340,7 @@ module FiniteMachine
325
340
  # @api public
326
341
  def trigger(event_name, *data, &block)
327
342
  trigger!(event_name, *data, &block)
328
- rescue InvalidStateError, TransitionError
343
+ rescue InvalidStateError, TransitionError, CallbackError
329
344
  false
330
345
  end
331
346
 
@@ -336,7 +351,7 @@ module FiniteMachine
336
351
  # @api private
337
352
  def transition!(event_name, *data, &block)
338
353
  from_state = current
339
- to_state = events_chain.move_to(event_name, from_state, *data)
354
+ to_state = events_map.move_to(event_name, from_state, *data)
340
355
 
341
356
  block.call(from_state, to_state) if block
342
357
 
@@ -376,9 +391,11 @@ module FiniteMachine
376
391
  # @api public
377
392
  def inspect
378
393
  sync_shared do
379
- "<##{self.class}:0x#{object_id.to_s(16)} @states=#{states}, " \
380
- "@events=#{event_names}, " \
381
- "@transitions=#{events_chain.state_transitions}>"
394
+ "<##{self.class}:0x#{object_id.to_s(16)} " \
395
+ "@current=#{current.inspect} " \
396
+ "@states=#{states} " \
397
+ "@events=#{events} " \
398
+ "@transitions=#{events_map.state_transitions}>"
382
399
  end
383
400
  end
384
401
 
@@ -393,7 +410,7 @@ module FiniteMachine
393
410
  #
394
411
  # @api private
395
412
  def raise_transition_error(error)
396
- fail TransitionError, Logger.format_error(error)
413
+ raise TransitionError, Logger.format_error(error)
397
414
  end
398
415
 
399
416
  # Forward the message to observer or self
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module FiniteMachine
4
4
  # A class responsible for converting transition arguments to states
@@ -7,148 +7,120 @@ module FiniteMachine
7
7
  #
8
8
  # @api private
9
9
  class StateParser
10
- include Threadable
10
+ NON_STATE_KEYS = %i[name if unless silent].freeze
11
11
 
12
- attr_threadsafe :attrs
12
+ STATE_KEYS = %i[from to].freeze
13
13
 
14
- BLACKLIST = [:name, :if, :unless, :silent].freeze
15
-
16
- # Initialize a StateParser
14
+ # Extract states from user defined attributes
17
15
  #
18
16
  # @example
19
- # StateParpser.new({from: [:green, :blue], to: :red})
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
17
+ # StateParser.parse({from: [:green, :blue], to: :red})
18
+ # # => {green: :red, green: :blue}
30
19
  #
31
20
  # @param [Proc] block
32
21
  #
33
- # @example
34
- # StateParpser.new(attr).parase_states
35
- #
36
22
  # @yield [Hash[Symbol]] the resolved states
37
23
  #
38
24
  # @return [Hash[Symbol]] the resolved states
39
25
  #
40
26
  # @api public
41
- def parse(&block)
42
- states = extract_states
27
+ def self.parse(attributes, &block)
28
+ attrs = ensure_only_states!(attributes)
29
+ states = extract_states(attrs)
43
30
  block ? states.each(&block) : states
44
31
  end
45
32
 
46
- # Check if attributes contain :from or :to key
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
33
+ # Extract only states from attributes
55
34
  #
56
- # @return [Boolean]
35
+ # @return [Hash[Symbol]]
57
36
  #
58
- # @api public
59
- def contains_from_to_keys?
60
- [:from, :to].any? { |key| attrs.keys.include?(key) }
37
+ # @api private
38
+ def self.ensure_only_states!(attrs)
39
+ attributes = attrs.dup
40
+ NON_STATE_KEYS.each { |key| attributes.delete(key) }
41
+ raise_not_enough_transitions unless attributes.any?
42
+ attributes
61
43
  end
44
+ private_class_method :ensure_only_states!
62
45
 
63
- # Return parser attributes
46
+ # Perform extraction of states from user supplied definitions
64
47
  #
65
- # @return [String]
48
+ # @return [Hash[Symbol]] the resolved states
66
49
  #
67
- # @api public
68
- def to_s
69
- attrs.to_s
50
+ # @api private
51
+ def self.extract_states(attrs)
52
+ if contains_from_to_keys?(attrs)
53
+ convert_from_to_attributes_to_states_hash(attrs)
54
+ else
55
+ convert_attributes_to_states_hash(attrs)
56
+ end
70
57
  end
58
+ private_class_method :extract_states
71
59
 
72
- # Return string representation
60
+ # Check if attributes contain :from or :to key
73
61
  #
74
- # @return [String]
62
+ # @example
63
+ # StateParser.contains_from_to_keys?({from: :green, to: :red})
64
+ # # => true
75
65
  #
76
- # @api public
77
- def inspect
78
- attributes = @attrs.map { |k, v| "#{k}:#{v}" }.join(', ')
79
- "<##{self.class} @attrs=#{attributes}>"
80
- end
81
-
82
- private
83
-
84
- # Extract only states from attributes
66
+ # @example
67
+ # StateParser.contains_from_to_keys?({:green => :red})
68
+ # # => false
85
69
  #
86
- # @return [Hash[Symbol]]
70
+ # @return [Boolean]
87
71
  #
88
- # @api private
89
- def ensure_only_states!(attrs)
90
- attributes = attrs.dup
91
- BLACKLIST.each { |key| attributes.delete(key) }
92
- raise_not_enough_transitions unless attributes.any?
93
- attributes
72
+ # @api public
73
+ def self.contains_from_to_keys?(attrs)
74
+ STATE_KEYS.any? { |key| attrs.keys.include?(key) }
94
75
  end
76
+ private_class_method :contains_from_to_keys?
95
77
 
96
78
  # Convert attrbiutes with :from, :to keys to states hash
97
79
  #
98
80
  # @return [Hash[Symbol]]
99
81
  #
100
82
  # @api private
101
- def convert_from_to_attributes_to_states_hash
102
- Array(attrs[:from] || ANY_STATE).reduce({}) do |hash, state|
83
+ def self.convert_from_to_attributes_to_states_hash(attrs)
84
+ Array(attrs[:from] || ANY_STATE).each_with_object({}) do |state, hash|
103
85
  hash[state] = attrs[:to] || state
104
- hash
105
86
  end
106
87
  end
88
+ private_class_method :convert_from_to_attributes_to_states_hash
107
89
 
108
90
  # Convert collapsed attributes to states hash
109
91
  #
110
92
  # @example
111
- # parser = StateParser.new([:green, :red] => :yellow)
112
- # parser.parse # => {green: :yellow, red: :yellow}
93
+ # StateParser.convert_attributes_to_states_hash([:green, :red] => :yellow)
94
+ # # => {green: :yellow, red: :yellow}
95
+ #
96
+ # @param [Hash] attrs
97
+ # the attributes to convert to a simple hash
113
98
  #
114
99
  # @return [Hash[Symbol]]
115
100
  #
116
101
  # @api private
117
- def convert_attributes_to_states_hash
118
- attrs.reduce({}) do |hash, (k, v)|
102
+ def self.convert_attributes_to_states_hash(attrs)
103
+ attrs.each_with_object({}) do |(k, v), hash|
119
104
  if k.respond_to?(:to_ary)
120
105
  k.each { |el| hash[el] = v }
121
106
  else
122
107
  hash[k] = v
123
108
  end
124
- hash
125
- end
126
- 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
109
  end
139
110
  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 not enough transition arguments
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
- fail NotEnoughTransitionsError, "please provide state transitions for" \
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