state_machines 0.10.0 → 0.30.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +177 -2
  3. data/lib/state_machines/branch.rb +16 -15
  4. data/lib/state_machines/callback.rb +11 -12
  5. data/lib/state_machines/core.rb +1 -3
  6. data/lib/state_machines/error.rb +5 -4
  7. data/lib/state_machines/eval_helpers.rb +83 -45
  8. data/lib/state_machines/event.rb +37 -27
  9. data/lib/state_machines/event_collection.rb +4 -5
  10. data/lib/state_machines/extensions.rb +5 -5
  11. data/lib/state_machines/helper_module.rb +1 -1
  12. data/lib/state_machines/integrations/base.rb +1 -1
  13. data/lib/state_machines/integrations.rb +11 -14
  14. data/lib/state_machines/machine/action_hooks.rb +53 -0
  15. data/lib/state_machines/machine/callbacks.rb +59 -0
  16. data/lib/state_machines/machine/class_methods.rb +25 -11
  17. data/lib/state_machines/machine/configuration.rb +124 -0
  18. data/lib/state_machines/machine/event_methods.rb +59 -0
  19. data/lib/state_machines/machine/helper_generators.rb +125 -0
  20. data/lib/state_machines/machine/integration.rb +70 -0
  21. data/lib/state_machines/machine/parsing.rb +77 -0
  22. data/lib/state_machines/machine/rendering.rb +17 -0
  23. data/lib/state_machines/machine/scoping.rb +44 -0
  24. data/lib/state_machines/machine/state_methods.rb +101 -0
  25. data/lib/state_machines/machine/utilities.rb +85 -0
  26. data/lib/state_machines/machine/validation.rb +39 -0
  27. data/lib/state_machines/machine.rb +75 -618
  28. data/lib/state_machines/machine_collection.rb +21 -15
  29. data/lib/state_machines/macro_methods.rb +2 -2
  30. data/lib/state_machines/matcher.rb +6 -6
  31. data/lib/state_machines/matcher_helpers.rb +1 -1
  32. data/lib/state_machines/node_collection.rb +21 -18
  33. data/lib/state_machines/options_validator.rb +72 -0
  34. data/lib/state_machines/path.rb +5 -5
  35. data/lib/state_machines/path_collection.rb +5 -4
  36. data/lib/state_machines/state.rb +29 -11
  37. data/lib/state_machines/state_collection.rb +3 -3
  38. data/lib/state_machines/state_context.rb +9 -8
  39. data/lib/state_machines/stdio_renderer.rb +16 -16
  40. data/lib/state_machines/syntax_validator.rb +57 -0
  41. data/lib/state_machines/test_helper.rb +568 -0
  42. data/lib/state_machines/transition.rb +43 -41
  43. data/lib/state_machines/transition_collection.rb +25 -26
  44. data/lib/state_machines/version.rb +1 -1
  45. metadata +25 -10
  46. data/lib/state_machines/assertions.rb +0 -42
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'options_validator'
4
+
3
5
  module StateMachines
4
6
  # Represents a collection of state machines for a class
5
7
  class MachineCollection < Hash
@@ -22,21 +24,25 @@ module StateMachines
22
24
  # * <tt>:to</tt> - A hash to write the initialized state to instead of
23
25
  # writing to the object. Default is to write directly to the object.
24
26
  def initialize_states(object, options = {}, attributes = {})
25
- options.assert_valid_keys( :static, :dynamic, :to)
26
- options = {static: true, dynamic: true}.merge(options)
27
+ StateMachines::OptionsValidator.assert_valid_keys!(options, :static, :dynamic, :to)
28
+ options = { static: true, dynamic: true }.merge(options)
27
29
 
28
30
  result = yield if block_given?
29
31
 
30
- each_value do |machine|
31
- unless machine.dynamic_initial_state?
32
- force = options[:static] == :force || !attributes.keys.map(&:to_sym).include?(machine.attribute)
33
- machine.initialize_state(object, force: force, to: options[:to])
32
+ if options[:static]
33
+ each_value do |machine|
34
+ unless machine.dynamic_initial_state?
35
+ force = options[:static] == :force || !attributes.keys.map(&:to_sym).include?(machine.attribute)
36
+ machine.initialize_state(object, force: force, to: options[:to])
37
+ end
34
38
  end
35
- end if options[:static]
39
+ end
36
40
 
37
- each_value do |machine|
38
- machine.initialize_state(object, force: options[:dynamic] == :force, to: options[:to]) if machine.dynamic_initial_state?
39
- end if options[:dynamic]
41
+ if options[:dynamic]
42
+ each_value do |machine|
43
+ machine.initialize_state(object, force: options[:dynamic] == :force, to: options[:to]) if machine.dynamic_initial_state?
44
+ end
45
+ end
40
46
 
41
47
  result
42
48
  end
@@ -50,7 +56,7 @@ module StateMachines
50
56
  transitions = events.collect do |event_name|
51
57
  # Find the actual event being run
52
58
  event = nil
53
- detect { |name, machine| event = machine.events[event_name, :qualified_name] }
59
+ detect { |_name, machine| event = machine.events[event_name, :qualified_name] }
54
60
 
55
61
  raise(InvalidEvent.new(object, event_name)) unless event
56
62
 
@@ -64,7 +70,7 @@ module StateMachines
64
70
  # Run the events in parallel only if valid transitions were found for
65
71
  # all of them
66
72
  if events.length == transitions.length
67
- TransitionCollection.new(transitions, {use_transactions: resolve_use_transactions, actions: run_action}).perform
73
+ TransitionCollection.new(transitions, { use_transactions: resolve_use_transactions, actions: run_action }).perform
68
74
  else
69
75
  false
70
76
  end
@@ -76,14 +82,14 @@ module StateMachines
76
82
  #
77
83
  # These should only be fired as a result of the action being run.
78
84
  def transitions(object, action, options = {})
79
- transitions = map do |name, machine|
85
+ transitions = map do |_name, machine|
80
86
  machine.events.attribute_transition_for(object, true) if machine.action == action
81
87
  end
82
88
 
83
- AttributeTransitionCollection.new(transitions.compact, {use_transactions: resolve_use_transactions}.merge(options))
89
+ AttributeTransitionCollection.new(transitions.compact, { use_transactions: resolve_use_transactions }.merge(options))
84
90
  end
85
91
 
86
- protected
92
+ protected
87
93
 
88
94
  def resolve_use_transactions
89
95
  use_transactions = nil
@@ -515,8 +515,8 @@ module StateMachines
515
515
  # See StateMachines::Machine for more information about using integrations
516
516
  # and the individual integration docs for information about the actual
517
517
  # scopes that are generated.
518
- def state_machine(*args, &block)
519
- StateMachines::Machine.find_or_create(self, *args, &block)
518
+ def state_machine(*, &)
519
+ StateMachines::Machine.find_or_create(self, *, &)
520
520
  end
521
521
  end
522
522
  end
@@ -32,13 +32,13 @@ module StateMachines
32
32
  # matcher = StateMachines::AllMatcher.instance - [:parked, :idling]
33
33
  # matcher.matches?(:parked) # => false
34
34
  # matcher.matches?(:first_gear) # => true
35
- def -(blacklist)
36
- BlacklistMatcher.new(blacklist)
35
+ def -(other)
36
+ BlacklistMatcher.new(other)
37
37
  end
38
- alias_method :except, :-
38
+ alias except -
39
39
 
40
40
  # Always returns true
41
- def matches?(value, context = {})
41
+ def matches?(_value, _context = {})
42
42
  true
43
43
  end
44
44
 
@@ -63,7 +63,7 @@ module StateMachines
63
63
  # matcher = StateMachines::WhitelistMatcher.new([:parked, :idling])
64
64
  # matcher.matches?(:parked) # => true
65
65
  # matcher.matches?(:first_gear) # => false
66
- def matches?(value, context = {})
66
+ def matches?(value, _context = {})
67
67
  values.include?(value)
68
68
  end
69
69
 
@@ -83,7 +83,7 @@ module StateMachines
83
83
  # matcher = StateMachines::BlacklistMatcher.new([:parked, :idling])
84
84
  # matcher.matches?(:parked) # => false
85
85
  # matcher.matches?(:first_gear) # => true
86
- def matches?(value, context = {})
86
+ def matches?(value, _context = {})
87
87
  !values.include?(value)
88
88
  end
89
89
 
@@ -30,7 +30,7 @@ module StateMachines
30
30
  def all
31
31
  AllMatcher.instance
32
32
  end
33
- alias_method :any, :all
33
+ alias any all
34
34
 
35
35
  # Represents a state that matches the original +from+ state. This is useful
36
36
  # for defining transitions which are loopbacks.
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'options_validator'
4
+
3
5
  module StateMachines
4
6
  # Represents a collection of nodes in a state machine, be it events or states.
5
7
  # Nodes will not differentiate between the String and Symbol versions of the
@@ -18,17 +20,16 @@ module StateMachines
18
20
  # hashed indices for in order to perform quick lookups. Default is to
19
21
  # index by the :name attribute
20
22
  def initialize(machine, options = {})
21
- options.assert_valid_keys(:index)
23
+ StateMachines::OptionsValidator.assert_valid_keys!(options, :index)
22
24
  options = { index: :name }.merge(options)
23
25
 
24
26
  @machine = machine
25
27
  @nodes = []
26
28
  @index_names = Array(options[:index])
27
- @indices = @index_names.reduce({}) do |indices, name|
29
+ @indices = @index_names.each_with_object({}) do |name, indices|
28
30
  indices[name] = {}
29
31
  indices[:"#{name}_to_s"] = {}
30
32
  indices[:"#{name}_to_sym"] = {}
31
- indices
32
33
  end
33
34
  @default_index = Array(options[:index]).first
34
35
  @contexts = []
@@ -36,14 +37,16 @@ module StateMachines
36
37
 
37
38
  # Creates a copy of this collection such that modifications don't affect
38
39
  # the original collection
39
- def initialize_copy(orig) #:nodoc:
40
+ def initialize_copy(orig) # :nodoc:
40
41
  super
41
42
 
42
43
  nodes = @nodes
43
44
  contexts = @contexts
44
45
  @nodes = []
45
46
  @contexts = []
46
- @indices = @indices.reduce({}) { |indices, (name, *)| indices[name] = {}; indices }
47
+ @indices = @indices.each_with_object({}) do |(name, *), indices|
48
+ indices[name] = {}
49
+ end
47
50
 
48
51
  # Add nodes *prior* to copying over the contexts so that they don't get
49
52
  # evaluated multiple times
@@ -114,8 +117,8 @@ module StateMachines
114
117
  # ...produces:
115
118
  #
116
119
  # parked -- idling --
117
- def each
118
- @nodes.each { |node| yield node }
120
+ def each(&)
121
+ @nodes.each(&)
119
122
  self
120
123
  end
121
124
 
@@ -143,9 +146,9 @@ module StateMachines
143
146
  # If the key cannot be found, then nil will be returned.
144
147
  def [](key, index_name = @default_index)
145
148
  index(index_name)[key] ||
146
- index(:"#{index_name}_to_s")[key.to_s] ||
147
- to_sym?(key) && index(:"#{index_name}_to_sym")[:"#{key}"] ||
148
- nil
149
+ index(:"#{index_name}_to_s")[key.to_s] ||
150
+ (to_sym?(key) && index(:"#{index_name}_to_sym")[:"#{key}"]) ||
151
+ nil
149
152
  end
150
153
 
151
154
  # Gets the node indexed by the given key. By default, this will look up the
@@ -161,17 +164,17 @@ module StateMachines
161
164
  #
162
165
  # collection['invalid', :value] # => IndexError: "invalid" is an invalid value
163
166
  def fetch(key, index_name = @default_index)
164
- self[key, index_name] || fail(IndexError, "#{key.inspect} is an invalid #{index_name}")
167
+ self[key, index_name] || raise(IndexError, "#{key.inspect} is an invalid #{index_name}")
165
168
  end
166
169
 
167
- protected
170
+ protected
168
171
 
169
172
  # Gets the given index. If the index does not exist, then an ArgumentError
170
173
  # is raised.
171
174
  def index(name)
172
- fail ArgumentError, 'No indices configured' unless @indices.any?
175
+ raise ArgumentError, 'No indices configured' unless @indices.any?
173
176
 
174
- @indices[name] || fail(ArgumentError, "Invalid index: #{name.inspect}")
177
+ @indices[name] || raise(ArgumentError, "Invalid index: #{name.inspect}")
175
178
  end
176
179
 
177
180
  # Gets the value for the given attribute on the node
@@ -203,10 +206,10 @@ module StateMachines
203
206
  new_key = value(node, name)
204
207
 
205
208
  # Only replace the key if it's changed
206
- if old_key != new_key
207
- remove_from_index(name, old_key)
208
- add_to_index(name, new_key, node)
209
- end
209
+ return unless old_key != new_key
210
+
211
+ remove_from_index(name, old_key)
212
+ add_to_index(name, new_key, node)
210
213
  end
211
214
 
212
215
  # Determines whether the given value can be converted to a symbol
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StateMachines
4
+ # Define the module if it doesn't exist yet
5
+ # Module for validating options without monkey-patching Hash
6
+ # Provides the same functionality as the Hash monkey patch but in a cleaner way
7
+ module OptionsValidator
8
+ class << self
9
+ # Validates that all keys in the options hash are in the list of valid keys
10
+ #
11
+ # @param options [Hash] The options hash to validate
12
+ # @param valid_keys [Array<Symbol>] List of valid key names
13
+ # @param caller_info [String] Information about the calling method for better error messages
14
+ # @raise [ArgumentError] If any invalid keys are found
15
+ def assert_valid_keys!(options, *valid_keys, caller_info: nil)
16
+ return if options.empty?
17
+
18
+ valid_keys.flatten!
19
+ invalid_keys = options.keys - valid_keys
20
+
21
+ return if invalid_keys.empty?
22
+
23
+ caller_context = caller_info ? " in #{caller_info}" : ''
24
+ raise ArgumentError, "Unknown key#{'s' if invalid_keys.length > 1}: #{invalid_keys.map(&:inspect).join(', ')}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}#{caller_context}"
25
+ end
26
+
27
+ # Validates that at most one of the exclusive keys is present in the options hash
28
+ #
29
+ # @param options [Hash] The options hash to validate
30
+ # @param exclusive_keys [Array<Symbol>] List of mutually exclusive keys
31
+ # @param caller_info [String] Information about the calling method for better error messages
32
+ # @raise [ArgumentError] If more than one exclusive key is found
33
+ def assert_exclusive_keys!(options, *exclusive_keys, caller_info: nil)
34
+ return if options.empty?
35
+
36
+ conflicting_keys = exclusive_keys & options.keys
37
+ return if conflicting_keys.length <= 1
38
+
39
+ caller_context = caller_info ? " in #{caller_info}" : ''
40
+ raise ArgumentError, "Conflicting keys: #{conflicting_keys.join(', ')}#{caller_context}"
41
+ end
42
+
43
+ # Validates options using a more convenient interface that works with both
44
+ # hash-style and kwargs-style method definitions
45
+ #
46
+ # @param valid_keys [Array<Symbol>] List of valid key names
47
+ # @param exclusive_key_groups [Array<Array<Symbol>>] Groups of mutually exclusive keys
48
+ # @param caller_info [String] Information about the calling method
49
+ # @return [Proc] A validation proc that can be called with options
50
+ def validator(valid_keys: [], exclusive_key_groups: [], caller_info: nil)
51
+ proc do |options|
52
+ assert_valid_keys!(options, *valid_keys, caller_info: caller_info) unless valid_keys.empty?
53
+
54
+ exclusive_key_groups.each do |group|
55
+ assert_exclusive_keys!(options, *group, caller_info: caller_info)
56
+ end
57
+ end
58
+ end
59
+
60
+ # Helper method for backwards compatibility - allows gradual migration
61
+ # from Hash monkey patch to this module
62
+ #
63
+ # @param options [Hash] The options to validate
64
+ # @param valid_keys [Array<Symbol>] Valid keys
65
+ # @return [Hash] The same options hash (for chaining)
66
+ def validate_and_return(options, *valid_keys)
67
+ assert_valid_keys!(options, *valid_keys)
68
+ options
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'options_validator'
4
+
3
5
  module StateMachines
4
6
  # A path represents a sequence of transitions that can be run for a particular
5
7
  # object. Paths can walk to new transitions, revealing all of the possible
6
8
  # branches that can be encountered in the object's state machine.
7
9
  class Path < Array
8
-
9
-
10
10
  # The object whose state machine is being walked
11
11
  attr_reader :object
12
12
 
@@ -22,7 +22,7 @@ module StateMachines
22
22
  # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
23
23
  # conditionals defined for each one
24
24
  def initialize(object, machine, options = {})
25
- options.assert_valid_keys(:target, :guard)
25
+ StateMachines::OptionsValidator.assert_valid_keys!(options, :target, :guard)
26
26
 
27
27
  @object = object
28
28
  @machine = machine
@@ -30,7 +30,7 @@ module StateMachines
30
30
  @guard = options[:guard]
31
31
  end
32
32
 
33
- def initialize_copy(orig) #:nodoc:
33
+ def initialize_copy(orig) # :nodoc:
34
34
  super
35
35
  @transitions = nil
36
36
  end
@@ -88,7 +88,7 @@ module StateMachines
88
88
  !empty? && (@target ? to_name == @target : transitions.empty?)
89
89
  end
90
90
 
91
- private
91
+ private
92
92
 
93
93
  # Calculates the number of times the given state has been walked to
94
94
  def times_walked_to(state)
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'options_validator'
4
+
3
5
  module StateMachines
4
6
  # Represents a collection of paths that are generated based on a set of
5
7
  # requirements regarding what states to start and end on
6
8
  class PathCollection < Array
7
-
8
9
  # The object whose state machine is being walked
9
10
  attr_reader :object
10
11
 
@@ -26,8 +27,8 @@ module StateMachines
26
27
  # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
27
28
  # conditionals defined for each one
28
29
  def initialize(object, machine, options = {})
29
- options = {deep: false, from: machine.states.match!(object).name}.merge(options)
30
- options.assert_valid_keys( :from, :to, :deep, :guard)
30
+ options = { deep: false, from: machine.states.match!(object).name }.merge(options)
31
+ StateMachines::OptionsValidator.assert_valid_keys!(options, :from, :to, :deep, :guard)
31
32
 
32
33
  @object = object
33
34
  @machine = machine
@@ -69,7 +70,7 @@ module StateMachines
69
70
  flat_map(&:events).uniq
70
71
  end
71
72
 
72
- private
73
+ private
73
74
 
74
75
  # Gets the initial set of paths to walk
75
76
  def initial_paths
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'options_validator'
4
+
3
5
  module StateMachines
4
6
  # A state defines a value that an attribute can be in after being transitioned
5
7
  # 0 or more times. States can represent a value of any type in Ruby, though
@@ -51,17 +53,33 @@ module StateMachines
51
53
  # (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}).
52
54
  # By default, the configured value is matched.
53
55
  # * <tt>:human_name</tt> - The human-readable version of this state's name
54
- def initialize(machine, name, options = {}) # :nodoc:
55
- options.assert_valid_keys(:initial, :value, :cache, :if, :human_name)
56
+ def initialize(machine, name, options = nil, initial: false, value: :__not_provided__, cache: nil, if: nil, human_name: nil, **extra_options) # :nodoc:
57
+ # Handle both old hash style and new kwargs style for backward compatibility
58
+ if options.is_a?(Hash)
59
+ # Old style: initialize(machine, name, {initial: true, value: 'foo'})
60
+ StateMachines::OptionsValidator.assert_valid_keys!(options, :initial, :value, :cache, :if, :human_name)
61
+ initial = options.fetch(:initial, false)
62
+ value = options.include?(:value) ? options[:value] : :__not_provided__
63
+ cache = options[:cache]
64
+ if_condition = options[:if]
65
+ human_name = options[:human_name]
66
+ else
67
+ # New style: initialize(machine, name, initial: true, value: 'foo')
68
+ # options parameter should be nil in this case
69
+ raise ArgumentError, "Unexpected positional argument: #{options.inspect}" unless options.nil?
70
+
71
+ StateMachines::OptionsValidator.assert_valid_keys!(extra_options, :initial, :value, :cache, :if, :human_name) unless extra_options.empty?
72
+ if_condition = binding.local_variable_get(:if) # 'if' is a keyword, need special handling
73
+ end
56
74
 
57
75
  @machine = machine
58
76
  @name = name
59
77
  @qualified_name = name && machine.namespace ? :"#{machine.namespace}_#{name}" : name
60
- @human_name = options[:human_name] || (@name ? @name.to_s.tr('_', ' ') : 'nil')
61
- @value = options.include?(:value) ? options[:value] : name&.to_s
62
- @cache = options[:cache]
63
- @matcher = options[:if]
64
- @initial = options[:initial] == true
78
+ @human_name = human_name || (@name ? @name.to_s.tr('_', ' ') : 'nil')
79
+ @value = value == :__not_provided__ ? name&.to_s : value
80
+ @cache = cache
81
+ @matcher = if_condition
82
+ @initial = initial == true
65
83
  @context = StateContext.new(self)
66
84
 
67
85
  return unless name
@@ -183,14 +201,14 @@ module StateMachines
183
201
  #
184
202
  # This can be called multiple times. Each time a new context is created,
185
203
  # a new module will be included in the owner class.
186
- def context(&block)
204
+ def context(&)
187
205
  # Include the context
188
206
  context = @context
189
207
  machine.owner_class.class_eval { include context }
190
208
 
191
209
  # Evaluate the method definitions and track which ones were added
192
210
  old_methods = context_methods
193
- context.class_eval(&block)
211
+ context.class_eval(&)
194
212
  new_methods = context_methods.to_a.reject { |(name, method)| old_methods[name] == method }
195
213
 
196
214
  # Alias new methods so that the only execute when the object is in this state
@@ -221,13 +239,13 @@ module StateMachines
221
239
  #
222
240
  # If the method has never been defined for this state, then a NoMethodError
223
241
  # will be raised.
224
- def call(object, method, *args, &block)
242
+ def call(object, method, *args, &)
225
243
  options = args.last.is_a?(Hash) ? args.pop : {}
226
244
  options = { method_name: method }.merge(options)
227
245
  state = machine.states.match!(object)
228
246
 
229
247
  if state == self && object.respond_to?(method)
230
- object.send(method, *args, &block)
248
+ object.send(method, *args, &)
231
249
  elsif (method_missing = options[:method_missing])
232
250
  # Dispatch to the superclass since the object either isn't in this state
233
251
  # or this state doesn't handle the method
@@ -3,8 +3,8 @@
3
3
  module StateMachines
4
4
  # Represents a collection of states in a state machine
5
5
  class StateCollection < NodeCollection
6
- def initialize(machine) #:nodoc:
7
- super(machine, index: [:name, :qualified_name, :value])
6
+ def initialize(machine) # :nodoc:
7
+ super(machine, index: %i[name qualified_name value])
8
8
  end
9
9
 
10
10
  # Determines whether the given object is in a specific state. If the
@@ -103,7 +103,7 @@ module StateMachines
103
103
  order
104
104
  end
105
105
 
106
- private
106
+ private
107
107
 
108
108
  # Gets the value for the given attribute on the node
109
109
  def value(node, attribute)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'options_validator'
4
+
3
5
  module StateMachines
4
6
  # Represents a module which will get evaluated within the context of a state.
5
7
  #
@@ -52,7 +54,6 @@ module StateMachines
52
54
  # vehicle.simulate = true
53
55
  # vehicle.moving? # => false
54
56
  class StateContext < Module
55
-
56
57
  include EvalHelpers
57
58
 
58
59
  # The state machine for which this context's state is defined
@@ -68,7 +69,7 @@ module StateMachines
68
69
 
69
70
  state_name = state.name
70
71
  machine_name = machine.name
71
- @condition = lambda { |object| object.class.state_machine(machine_name).states.matches?(object, state_name) }
72
+ @condition = ->(object) { object.class.state_machine(machine_name).states.matches?(object, state_name) }
72
73
  end
73
74
 
74
75
  # Creates a new transition that determines what to change the current state
@@ -88,15 +89,15 @@ module StateMachines
88
89
  # See StateMachines::Machine#transition for a description of the possible
89
90
  # configurations for defining transitions.
90
91
  def transition(options)
91
- options.assert_valid_keys(:from, :to, :on, :if, :unless)
92
+ StateMachines::OptionsValidator.assert_valid_keys!(options, :from, :to, :on, :if, :unless)
92
93
  raise ArgumentError, 'Must specify :on event' unless options[:on]
93
94
  raise ArgumentError, 'Must specify either :to or :from state' unless !options[:to] ^ !options[:from]
94
95
 
95
- machine.transition(options.merge(options[:to] ? {from: state.name} : {to: state.name}))
96
+ machine.transition(options.merge(options[:to] ? { from: state.name } : { to: state.name }))
96
97
  end
97
98
 
98
99
  # Hooks in condition-merging to methods that don't exist in this module
99
- def method_missing(*args, &block)
100
+ def method_missing(*args, &)
100
101
  # Get the configuration
101
102
  if args.last.is_a?(Hash)
102
103
  options = args.last
@@ -121,13 +122,13 @@ module StateMachines
121
122
  object = condition_args.first || self
122
123
 
123
124
  proxy.evaluate_method(object, proxy_condition) &&
124
- Array(if_condition).all? { |condition| proxy.evaluate_method(object, condition) } &&
125
- !Array(unless_condition).any? { |condition| proxy.evaluate_method(object, condition) }
125
+ Array(if_condition).all? { |condition| proxy.evaluate_method(object, condition) } &&
126
+ !Array(unless_condition).any? { |condition| proxy.evaluate_method(object, condition) }
126
127
  end
127
128
 
128
129
  # Evaluate the method on the owner class with the condition proxied
129
130
  # through
130
- machine.owner_class.send(*args, &block)
131
+ machine.owner_class.send(*args, &)
131
132
  end
132
133
  end
133
134
  end
@@ -13,9 +13,9 @@ module StateMachines
13
13
  end
14
14
 
15
15
  module_function def draw_states(machine:, io: $stdout)
16
- io.puts " States:"
16
+ io.puts ' States:'
17
17
  if machine.states.to_a.empty?
18
- io.puts " - None"
18
+ io.puts ' - None'
19
19
  else
20
20
  machine.states.each do |state|
21
21
  io.puts " - #{state.name}"
@@ -23,31 +23,31 @@ module StateMachines
23
23
  end
24
24
  end
25
25
 
26
- module_function def draw_event(event, graph, options: {}, io: $stdout)
26
+ module_function def draw_event(event, _graph, options: {}, io: $stdout)
27
27
  io = io || options[:io] || $stdout
28
28
  io.puts " Event: #{event.name}"
29
29
  end
30
30
 
31
- module_function def draw_branch(branch, graph, event, options: {}, io: $stdout)
31
+ module_function def draw_branch(branch, _graph, _event, options: {}, io: $stdout)
32
32
  io = io || options[:io] || $stdout
33
33
  io.puts " Branch: #{branch.inspect}"
34
34
  end
35
35
 
36
- module_function def draw_state(state, graph, options: {}, io: $stdout)
36
+ module_function def draw_state(state, _graph, options: {}, io: $stdout)
37
37
  io = io || options[:io] || $stdout
38
38
  io.puts " State: #{state.name}"
39
39
  end
40
40
 
41
41
  module_function def draw_events(machine:, io: $stdout)
42
- io.puts " Events:"
42
+ io.puts ' Events:'
43
43
  if machine.events.to_a.empty?
44
- io.puts " - None"
44
+ io.puts ' - None'
45
45
  else
46
46
  machine.events.each do |event|
47
47
  io.puts " - #{event.name}"
48
48
  event.branches.each do |branch|
49
49
  branch.state_requirements.each do |requirement|
50
- out = +" - "
50
+ out = +' - '
51
51
  out << "#{draw_requirement(requirement[:from])} => #{draw_requirement(requirement[:to])}"
52
52
  out << " IF #{branch.if_condition}" if branch.if_condition
53
53
  out << " UNLESS #{branch.unless_condition}" if branch.unless_condition
@@ -60,14 +60,14 @@ module StateMachines
60
60
 
61
61
  module_function def draw_requirement(requirement)
62
62
  case requirement
63
- when StateMachines::BlacklistMatcher
64
- "ALL EXCEPT #{requirement.values.join(', ')}"
65
- when StateMachines::AllMatcher
66
- "ALL"
67
- when StateMachines::LoopbackMatcher
68
- "SAME"
69
- else
70
- requirement.values.join(', ')
63
+ when StateMachines::BlacklistMatcher
64
+ "ALL EXCEPT #{requirement.values.join(', ')}"
65
+ when StateMachines::AllMatcher
66
+ 'ALL'
67
+ when StateMachines::LoopbackMatcher
68
+ 'SAME'
69
+ else
70
+ requirement.values.join(', ')
71
71
  end
72
72
  end
73
73
  end