finite_machine 0.11.2 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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