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,4 +1,6 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "threadable"
2
4
 
3
5
  module FiniteMachine
4
6
  # Holds references to targets and aliases
@@ -11,7 +13,7 @@ module FiniteMachine
11
13
 
12
14
  attr_threadsafe :aliases
13
15
 
14
- def initialize(target, aliases)
16
+ def initialize(target, aliases = [])
15
17
  @target = target
16
18
  @aliases = aliases
17
19
  end
@@ -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 event methods on state machine
@@ -8,10 +8,8 @@ module FiniteMachine
8
8
  #
9
9
  # @api private
10
10
  class EventDefinition
11
- include Threadable
12
-
13
11
  # The current state machine
14
- attr_threadsafe :machine
12
+ attr_reader :machine
15
13
 
16
14
  # Initialize an EventDefinition
17
15
  #
@@ -19,7 +17,7 @@ module FiniteMachine
19
17
  #
20
18
  # @api private
21
19
  def initialize(machine)
22
- self.machine = machine
20
+ @machine = machine
23
21
  end
24
22
 
25
23
  # Define transition event names as state machine events
@@ -50,11 +48,8 @@ module FiniteMachine
50
48
  # @api private
51
49
  def define_event_transition(event_name, silent)
52
50
  machine.send(:define_singleton_method, event_name) do |*data, &block|
53
- if silent
54
- machine.transition(event_name, *data, &block)
55
- else
56
- machine.trigger(event_name, *data, &block)
57
- end
51
+ method = silent ? :transition : :trigger
52
+ machine.public_send(method, event_name, *data, &block)
58
53
  end
59
54
  end
60
55
 
@@ -71,11 +66,8 @@ module FiniteMachine
71
66
  # @api private
72
67
  def define_event_bang(event_name, silent)
73
68
  machine.send(:define_singleton_method, "#{event_name}!") do |*data, &block|
74
- if silent
75
- machine.transition!(event_name, *data, &block)
76
- else
77
- machine.trigger!(event_name, *data, &block)
78
- end
69
+ method = silent ? :transition! : :trigger!
70
+ machine.public_send(method, event_name, *data, &block)
79
71
  end
80
72
  end
81
73
  end # EventBuilder
@@ -1,35 +1,34 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'finite_machine/undefined_transition'
3
+ require "concurrent/map"
4
+ require "forwardable"
5
+
6
+ require_relative "threadable"
7
+ require_relative "undefined_transition"
4
8
 
5
9
  module FiniteMachine
6
- # A class responsible for storing chain of events.
10
+ # A class responsible for storing mappings between event namess and
11
+ # their transition objects.
7
12
  #
8
13
  # Used internally by {StateMachine}.
9
14
  #
10
15
  # @api private
11
- class EventsChain
12
- include Threadable
16
+ class EventsMap
13
17
  extend Forwardable
14
18
 
15
- # The chain of events
16
- #
17
- # @api private
18
- attr_threadsafe :chain
19
-
20
- def_delegators :@chain, :empty?, :size
19
+ def_delegators :@events_map, :empty?, :size
21
20
 
22
- # Initialize a EventsChain
21
+ # Initialize a EventsMap
23
22
  #
24
23
  # @api private
25
24
  def initialize
26
- @chain = {}
25
+ @events_map = Concurrent::Map.new
27
26
  end
28
27
 
29
28
  # Check if event is present
30
29
  #
31
30
  # @example
32
- # events_chain.exists?(:go) # => true
31
+ # events_map.exists?(:go) # => true
33
32
  #
34
33
  # @param [Symbol] name
35
34
  # the event name
@@ -39,7 +38,7 @@ module FiniteMachine
39
38
  #
40
39
  # @api public
41
40
  def exists?(name)
42
- !chain[name].nil?
41
+ @events_map.key?(name)
43
42
  end
44
43
 
45
44
  # Add transition under name
@@ -54,9 +53,9 @@ module FiniteMachine
54
53
  # @api public
55
54
  def add(name, transition)
56
55
  if exists?(name)
57
- chain[name] << transition
56
+ @events_map[name] << transition
58
57
  else
59
- chain[name] = [transition]
58
+ @events_map[name] = [transition]
60
59
  end
61
60
  self
62
61
  end
@@ -66,41 +65,41 @@ module FiniteMachine
66
65
  # @param [Symbol] name
67
66
  #
68
67
  # @example
69
- # events_chain[:start] # => []
68
+ # events_map[:start] # => []
70
69
  #
71
70
  # @return [Array[Transition]]
72
71
  # the transitions matching event name
73
72
  #
74
73
  # @api public
75
74
  def find(name)
76
- chain.fetch(name) { [] }
75
+ @events_map.fetch(name) { [] }
77
76
  end
78
- alias_method :[], :find
77
+ alias [] find
79
78
 
80
79
  # Retrieve all event names
81
80
  #
82
81
  # @example
83
- # events_chain.events # => [:init, :start, :stop]
82
+ # events_map.events # => [:init, :start, :stop]
84
83
  #
85
84
  # @return [Array[Symbol]]
86
85
  # All event names
87
86
  #
88
87
  # @api public
89
88
  def events
90
- chain.keys
89
+ @events_map.keys
91
90
  end
92
91
 
93
92
  # Retreive all unique states
94
93
  #
95
94
  # @example
96
- # events_chain.states # => [:yellow, :green, :red]
95
+ # events_map.states # => [:yellow, :green, :red]
97
96
  #
98
97
  # @return [Array[Symbol]]
99
98
  # the array of all unique states
100
99
  #
101
100
  # @api public
102
101
  def states
103
- chain.values.flatten.map(&:states).map(&:to_a).flatten.uniq
102
+ @events_map.values.flatten.map(&:states).map(&:to_a).flatten.uniq
104
103
  end
105
104
 
106
105
  # Retrieves all state transitions
@@ -109,7 +108,7 @@ module FiniteMachine
109
108
  #
110
109
  # @api public
111
110
  def state_transitions
112
- chain.values.flatten.map(&:states)
111
+ @events_map.values.flatten.map(&:states)
113
112
  end
114
113
 
115
114
  # Retrieve from states for the event name
@@ -117,7 +116,7 @@ module FiniteMachine
117
116
  # @param [Symbol] event_name
118
117
  #
119
118
  # @example
120
- # events_chain.states_for(:start) # => [:yellow, :green]
119
+ # events_map.states_for(:start) # => [:yellow, :green]
121
120
  #
122
121
  # @api public
123
122
  def states_for(name)
@@ -136,7 +135,7 @@ module FiniteMachine
136
135
  # Check if event has branching choice transitions or not
137
136
  #
138
137
  # @example
139
- # events_chain.choice_transition?(:go, :green) # => true
138
+ # events_map.choice_transition?(:go, :green) # => true
140
139
  #
141
140
  # @param [Symbol] name
142
141
  # the event name
@@ -183,8 +182,7 @@ module FiniteMachine
183
182
  # @api public
184
183
  def match_transition_with(name, from_state, *conditions)
185
184
  find(name).find do |trans|
186
- trans.matches?(from_state) &&
187
- trans.check_conditions(*conditions)
185
+ trans.matches?(from_state) && trans.check_conditions(*conditions)
188
186
  end
189
187
  end
190
188
 
@@ -211,7 +209,7 @@ module FiniteMachine
211
209
  # Find state that this machine can move to
212
210
  #
213
211
  # @example
214
- # evenst_chain.move_to(:go, :green) # => :red
212
+ # evenst_map.move_to(:go, :green) # => :red
215
213
  #
216
214
  # @param [Symbol] name
217
215
  # the event name
@@ -229,53 +227,42 @@ module FiniteMachine
229
227
  def move_to(name, from_state, *conditions)
230
228
  transition = select_transition(name, from_state, *conditions)
231
229
  transition ||= UndefinedTransition.new(name)
232
-
233
230
  transition.to_state(from_state)
234
231
  end
235
232
 
236
- # Set status to cancelled for all transitions matching event name
237
- #
238
- # @param [Symbol] name
239
- # the event name
240
- #
241
- # @return [nil]
242
- #
243
- # @api public
244
- def cancel_transitions(name)
245
- chain[name].each do |trans|
246
- trans.cancelled = true
247
- end
248
- end
249
-
250
- # Reset chain
233
+ # Reset map
251
234
  #
252
235
  # @return [self]
253
236
  #
254
237
  # @api public
255
238
  def clear
256
- @chain.clear
239
+ @events_map.clear
257
240
  self
258
241
  end
259
242
 
260
- # Return string representation of this chain
243
+ # Return string representation of this map
261
244
  #
262
245
  # @return [String]
263
246
  #
264
247
  # @api public
265
248
  def to_s
266
- chain.to_s
249
+ hash = {}
250
+ @events_map.each_pair do |name, trans|
251
+ hash[name] = trans
252
+ end
253
+ hash.to_s
267
254
  end
268
255
 
269
- # Inspect chain content
256
+ # Inspect map content
270
257
  #
271
258
  # @example
272
- # events_chain.inspect
259
+ # events_map.inspect
273
260
  #
274
261
  # @return [String]
275
262
  #
276
263
  # @api public
277
264
  def inspect
278
- "<##{self.class} @chain=#{chain.inspect}>"
265
+ "<##{self.class} @events_map=#{self}>"
279
266
  end
280
- end # EventsChain
267
+ end # EventsMap
281
268
  end # FiniteMachine
@@ -1,9 +1,8 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module FiniteMachine
4
4
  # A class responsible for event notification
5
5
  class HookEvent
6
- include Threadable
7
6
  include Comparable
8
7
 
9
8
  class Anystate < HookEvent; end
@@ -24,38 +23,34 @@ module FiniteMachine
24
23
 
25
24
  MESSAGE = :emit
26
25
 
27
- # HookEvent state or action
28
- attr_threadsafe :name
29
-
30
- # HookEvent type
31
- attr_threadsafe :type
32
-
33
- # The from state this hook has been fired
34
- attr_threadsafe :from
35
-
36
- # The event name triggering this hook event
37
- attr_threadsafe :event_name
26
+ # Extract event name
27
+ #
28
+ # @return [String] the event name
29
+ #
30
+ # @api public
31
+ def self.event_name
32
+ name.split("::").last.downcase.to_sym
33
+ end
38
34
 
39
- # Instantiate a new HookEvent object
35
+ # String representation
40
36
  #
41
- # @param [Symbol] name
42
- # The action or state name
37
+ # @return [String] the event name
43
38
  #
44
- # @param [Symbol] event_name
45
- # The event name associated with this hook event.
39
+ # @api public
40
+ def self.to_s
41
+ event_name.to_s
42
+ end
43
+
44
+ # Choose any state or event name based on even type
46
45
  #
47
- # @example
48
- # HookEvent.new(:green, :move, :green)
46
+ # @param [HookEvent] event_type
49
47
  #
50
- # @return [self]
48
+ # @return [Symbol]
49
+ # out of :any or :any_event
51
50
  #
52
51
  # @api public
53
- def initialize(name, event_name, from)
54
- @name = name
55
- @type = self.class
56
- @event_name = event_name
57
- @from = from
58
- freeze
52
+ def self.any_state_or_event(event_type)
53
+ event_type < Anyaction ? ANY_EVENT : ANY_STATE
59
54
  end
60
55
 
61
56
  # Build event hook
@@ -74,16 +69,44 @@ module FiniteMachine
74
69
  new(state_or_action, event_name, from)
75
70
  end
76
71
 
77
- # Deduce default name based on even type
72
+ EVENTS.each do |event|
73
+ (class << self; self; end).class_eval do
74
+ define_method(event.event_name) { event }
75
+ end
76
+ end
77
+
78
+ # HookEvent state or action name
79
+ attr_reader :name
80
+
81
+ # HookEvent type
82
+ attr_reader :type
83
+
84
+ # The from state this hook has been fired
85
+ attr_reader :from
86
+
87
+ # The event name triggering this hook event
88
+ attr_reader :event_name
89
+
90
+ # Instantiate a new HookEvent object
78
91
  #
79
- # @param [HookEvent] event_type
92
+ # @param [Symbol] name
93
+ # The action or state name
80
94
  #
81
- # @return [Symbol]
82
- # out of :any or :any_event
95
+ # @param [Symbol] event_name
96
+ # The event name associated with this hook event.
97
+ #
98
+ # @example
99
+ # HookEvent.new(:green, :move, :green)
100
+ #
101
+ # @return [self]
83
102
  #
84
103
  # @api public
85
- def self.infer_default_name(event_type)
86
- event_type < Anyaction ? ANY_EVENT : ANY_STATE
104
+ def initialize(name, event_name, from)
105
+ @name = name
106
+ @type = self.class
107
+ @event_name = event_name
108
+ @from = from
109
+ freeze
87
110
  end
88
111
 
89
112
  # Notify subscriber about this event
@@ -98,27 +121,9 @@ module FiniteMachine
98
121
  #
99
122
  # @api public
100
123
  def notify(subscriber, *data)
101
- if subscriber.respond_to?(MESSAGE)
102
- subscriber.public_send(MESSAGE, self, *data)
103
- end
104
- end
124
+ return unless subscriber.respond_to?(MESSAGE)
105
125
 
106
- # Extract event name
107
- #
108
- # @return [String] the event name
109
- #
110
- # @api public
111
- def self.event_name
112
- name.split('::').last.downcase.to_sym
113
- end
114
-
115
- # String representation
116
- #
117
- # @return [String] the event name
118
- #
119
- # @api public
120
- def self.to_s
121
- event_name.to_s
126
+ subscriber.public_send(MESSAGE, self, *data)
122
127
  end
123
128
 
124
129
  # Compare whether the instance is greater, less then or equal to other
@@ -128,14 +133,8 @@ module FiniteMachine
128
133
  # @api public
129
134
  def <=>(other)
130
135
  other.is_a?(type) &&
131
- [name, from, event_name] <=> [other.name, other.from, other.event_name]
132
- end
133
- alias_method :eql?, :==
134
-
135
- EVENTS.each do |event|
136
- (class << self; self; end).class_eval do
137
- define_method(event.event_name) { event }
138
- end
136
+ [name, from, event_name] <=> [other.name, other.from, other.event_name]
139
137
  end
138
+ alias eql? ==
140
139
  end # HookEvent
141
140
  end # FiniteMachine