enum_state_machine 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +12 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. metadata +83 -130
  6. data/.rvmrc +0 -1
  7. data/enum_state_machine.gemspec +0 -25
  8. data/lib/enum_state_machine.rb +0 -9
  9. data/lib/enum_state_machine/assertions.rb +0 -36
  10. data/lib/enum_state_machine/branch.rb +0 -225
  11. data/lib/enum_state_machine/callback.rb +0 -232
  12. data/lib/enum_state_machine/core.rb +0 -12
  13. data/lib/enum_state_machine/core_ext.rb +0 -2
  14. data/lib/enum_state_machine/core_ext/class/state_machine.rb +0 -5
  15. data/lib/enum_state_machine/error.rb +0 -13
  16. data/lib/enum_state_machine/eval_helpers.rb +0 -87
  17. data/lib/enum_state_machine/event.rb +0 -257
  18. data/lib/enum_state_machine/event_collection.rb +0 -141
  19. data/lib/enum_state_machine/extensions.rb +0 -149
  20. data/lib/enum_state_machine/graph.rb +0 -92
  21. data/lib/enum_state_machine/helper_module.rb +0 -17
  22. data/lib/enum_state_machine/initializers.rb +0 -4
  23. data/lib/enum_state_machine/initializers/rails.rb +0 -22
  24. data/lib/enum_state_machine/integrations.rb +0 -97
  25. data/lib/enum_state_machine/integrations/active_model.rb +0 -585
  26. data/lib/enum_state_machine/integrations/active_model/locale.rb +0 -11
  27. data/lib/enum_state_machine/integrations/active_model/observer.rb +0 -33
  28. data/lib/enum_state_machine/integrations/active_model/observer_update.rb +0 -42
  29. data/lib/enum_state_machine/integrations/active_model/versions.rb +0 -31
  30. data/lib/enum_state_machine/integrations/active_record.rb +0 -548
  31. data/lib/enum_state_machine/integrations/active_record/locale.rb +0 -20
  32. data/lib/enum_state_machine/integrations/active_record/versions.rb +0 -123
  33. data/lib/enum_state_machine/integrations/base.rb +0 -100
  34. data/lib/enum_state_machine/machine.rb +0 -2292
  35. data/lib/enum_state_machine/machine_collection.rb +0 -86
  36. data/lib/enum_state_machine/macro_methods.rb +0 -518
  37. data/lib/enum_state_machine/matcher.rb +0 -123
  38. data/lib/enum_state_machine/matcher_helpers.rb +0 -54
  39. data/lib/enum_state_machine/node_collection.rb +0 -222
  40. data/lib/enum_state_machine/path.rb +0 -120
  41. data/lib/enum_state_machine/path_collection.rb +0 -90
  42. data/lib/enum_state_machine/state.rb +0 -297
  43. data/lib/enum_state_machine/state_collection.rb +0 -112
  44. data/lib/enum_state_machine/state_context.rb +0 -138
  45. data/lib/enum_state_machine/state_enum.rb +0 -23
  46. data/lib/enum_state_machine/transition.rb +0 -470
  47. data/lib/enum_state_machine/transition_collection.rb +0 -245
  48. data/lib/enum_state_machine/version.rb +0 -3
  49. data/lib/enum_state_machine/yard.rb +0 -8
  50. data/lib/enum_state_machine/yard/handlers.rb +0 -12
  51. data/lib/enum_state_machine/yard/handlers/base.rb +0 -32
  52. data/lib/enum_state_machine/yard/handlers/event.rb +0 -25
  53. data/lib/enum_state_machine/yard/handlers/machine.rb +0 -344
  54. data/lib/enum_state_machine/yard/handlers/state.rb +0 -25
  55. data/lib/enum_state_machine/yard/handlers/transition.rb +0 -47
  56. data/lib/enum_state_machine/yard/templates.rb +0 -3
  57. data/lib/enum_state_machine/yard/templates/default/class/html/setup.rb +0 -30
  58. data/lib/enum_state_machine/yard/templates/default/class/html/state_machines.erb +0 -12
  59. data/lib/tasks/enum_state_machine.rake +0 -1
  60. data/lib/tasks/enum_state_machine.rb +0 -24
  61. data/lib/yard-enum_state_machine.rb +0 -2
  62. data/test/functional/state_machine_test.rb +0 -1066
  63. data/test/unit/integrations/active_model_test.rb +0 -1245
  64. data/test/unit/integrations/active_record_test.rb +0 -2551
  65. data/test/unit/integrations/base_test.rb +0 -104
  66. data/test/unit/integrations_test.rb +0 -71
  67. data/test/unit/invalid_event_test.rb +0 -20
  68. data/test/unit/invalid_parallel_transition_test.rb +0 -18
  69. data/test/unit/invalid_transition_test.rb +0 -115
  70. data/test/unit/machine_collection_test.rb +0 -603
  71. data/test/unit/machine_test.rb +0 -3395
  72. data/test/unit/state_machine_test.rb +0 -31
@@ -1,123 +0,0 @@
1
- require 'singleton'
2
-
3
- module EnumStateMachine
4
- # Provides a general strategy pattern for determining whether a match is found
5
- # for a value. The algorithm that actually determines the match depends on
6
- # the matcher in use.
7
- class Matcher
8
- # The list of values against which queries are matched
9
- attr_reader :values
10
-
11
- # Creates a new matcher for querying against the given set of values
12
- def initialize(values = [])
13
- @values = values.is_a?(Array) ? values : [values]
14
- end
15
-
16
- # Generates a subset of values that exists in both the set of values being
17
- # filtered and the values configured for the matcher
18
- def filter(values)
19
- self.values & values
20
- end
21
- end
22
-
23
- # Matches any given value. Since there is no configuration for this type of
24
- # matcher, it must be used as a singleton.
25
- class AllMatcher < Matcher
26
- include Singleton
27
-
28
- # Generates a blacklist matcher based on the given set of values
29
- #
30
- # == Examples
31
- #
32
- # matcher = EnumStateMachine::AllMatcher.instance - [:parked, :idling]
33
- # matcher.matches?(:parked) # => false
34
- # matcher.matches?(:first_gear) # => true
35
- def -(blacklist)
36
- BlacklistMatcher.new(blacklist)
37
- end
38
-
39
- # Always returns true
40
- def matches?(value, context = {})
41
- true
42
- end
43
-
44
- # Always returns the given set of values
45
- def filter(values)
46
- values
47
- end
48
-
49
- # A human-readable description of this matcher. Always "all".
50
- def description
51
- 'all'
52
- end
53
- end
54
-
55
- # Matches a specific set of values
56
- class WhitelistMatcher < Matcher
57
- # Checks whether the given value exists within the whitelist configured
58
- # for this matcher.
59
- #
60
- # == Examples
61
- #
62
- # matcher = EnumStateMachine::WhitelistMatcher.new([:parked, :idling])
63
- # matcher.matches?(:parked) # => true
64
- # matcher.matches?(:first_gear) # => false
65
- def matches?(value, context = {})
66
- values.include?(value)
67
- end
68
-
69
- # A human-readable description of this matcher
70
- def description
71
- values.length == 1 ? values.first.inspect : values.inspect
72
- end
73
- end
74
-
75
- # Matches everything but a specific set of values
76
- class BlacklistMatcher < Matcher
77
- # Checks whether the given value exists outside the blacklist configured
78
- # for this matcher.
79
- #
80
- # == Examples
81
- #
82
- # matcher = EnumStateMachine::BlacklistMatcher.new([:parked, :idling])
83
- # matcher.matches?(:parked) # => false
84
- # matcher.matches?(:first_gear) # => true
85
- def matches?(value, context = {})
86
- !values.include?(value)
87
- end
88
-
89
- # Finds all values that are *not* within the blacklist configured for this
90
- # matcher
91
- def filter(values)
92
- values - self.values
93
- end
94
-
95
- # A human-readable description of this matcher
96
- def description
97
- "all - #{values.length == 1 ? values.first.inspect : values.inspect}"
98
- end
99
- end
100
-
101
- # Matches a loopback of two values within a context. Since there is no
102
- # configuration for this type of matcher, it must be used as a singleton.
103
- class LoopbackMatcher < Matcher
104
- include Singleton
105
-
106
- # Checks whether the given value matches what the value originally was.
107
- # This value should be defined in the context.
108
- #
109
- # == Examples
110
- #
111
- # matcher = EnumStateMachine::LoopbackMatcher.instance
112
- # matcher.matches?(:parked, :from => :parked) # => true
113
- # matcher.matches?(:parked, :from => :idling) # => false
114
- def matches?(value, context)
115
- context[:from] == value
116
- end
117
-
118
- # A human-readable description of this matcher. Always "same".
119
- def description
120
- 'same'
121
- end
122
- end
123
- end
@@ -1,54 +0,0 @@
1
- module EnumStateMachine
2
- # Provides a set of helper methods for generating matchers
3
- module MatcherHelpers
4
- # Represents a state that matches all known states in a machine.
5
- #
6
- # == Examples
7
- #
8
- # class Vehicle
9
- # state_machine do
10
- # before_transition any => :parked, :do => lambda {...}
11
- # before_transition all - :parked => all - :idling, :do => lambda {}
12
- #
13
- # event :park
14
- # transition all => :parked
15
- # end
16
- #
17
- # event :crash
18
- # transition all - :parked => :stalled
19
- # end
20
- # end
21
- # end
22
- #
23
- # In the above example, +all+ will match the following states since they
24
- # are known:
25
- # * +parked+
26
- # * +stalled+
27
- # * +idling+
28
- def all
29
- AllMatcher.instance
30
- end
31
- alias_method :any, :all
32
-
33
- # Represents a state that matches the original +from+ state. This is useful
34
- # for defining transitions which are loopbacks.
35
- #
36
- # == Examples
37
- #
38
- # class Vehicle
39
- # state_machine do
40
- # event :ignite
41
- # transition [:idling, :first_gear] => same
42
- # end
43
- # end
44
- # end
45
- #
46
- # In the above example, +same+ will match whichever the from state is. In
47
- # the case of the +ignite+ event, it is essential the same as the following:
48
- #
49
- # transition :parked => :parked, :first_gear => :first_gear
50
- def same
51
- LoopbackMatcher.instance
52
- end
53
- end
54
- end
@@ -1,222 +0,0 @@
1
- require 'enum_state_machine/assertions'
2
-
3
- module EnumStateMachine
4
- # Represents a collection of nodes in a state machine, be it events or states.
5
- # Nodes will not differentiate between the String and Symbol versions of the
6
- # values being indexed.
7
- class NodeCollection
8
- include Enumerable
9
- include Assertions
10
-
11
- # The machine associated with the nodes
12
- attr_reader :machine
13
-
14
- # Creates a new collection of nodes for the given state machine. By default,
15
- # the collection is empty.
16
- #
17
- # Configuration options:
18
- # * <tt>:index</tt> - One or more attributes to automatically generate
19
- # hashed indices for in order to perform quick lookups. Default is to
20
- # index by the :name attribute
21
- def initialize(machine, options = {})
22
- assert_valid_keys(options, :index)
23
- options = {:index => :name}.merge(options)
24
-
25
- @machine = machine
26
- @nodes = []
27
- @index_names = Array(options[:index])
28
- @indices = @index_names.inject({}) do |indices, name|
29
- indices[name] = {}
30
- indices[:"#{name}_to_s"] = {}
31
- indices[:"#{name}_to_sym"] = {}
32
- indices
33
- end
34
- @default_index = Array(options[:index]).first
35
- @contexts = []
36
- end
37
-
38
- # Creates a copy of this collection such that modifications don't affect
39
- # the original collection
40
- def initialize_copy(orig) #:nodoc:
41
- super
42
-
43
- nodes = @nodes
44
- contexts = @contexts
45
- @nodes = []
46
- @contexts = []
47
- @indices = @indices.inject({}) {|indices, (name, *)| indices[name] = {}; indices}
48
-
49
- # Add nodes *prior* to copying over the contexts so that they don't get
50
- # evaluated multiple times
51
- concat(nodes.map {|n| n.dup})
52
- @contexts = contexts.dup
53
- end
54
-
55
- # Changes the current machine associated with the collection. In turn, this
56
- # will change the state machine associated with each node in the collection.
57
- def machine=(new_machine)
58
- @machine = new_machine
59
- each {|node| node.machine = new_machine}
60
- end
61
-
62
- # Gets the number of nodes in this collection
63
- def length
64
- @nodes.length
65
- end
66
-
67
- # Gets the set of unique keys for the given index
68
- def keys(index_name = @default_index)
69
- index(index_name).keys
70
- end
71
-
72
- # Tracks a context that should be evaluated for any nodes that get added
73
- # which match the given set of nodes. Matchers can be used so that the
74
- # context can get added once and evaluated after multiple adds.
75
- def context(nodes, &block)
76
- nodes = nodes.first.is_a?(Matcher) ? nodes.first : WhitelistMatcher.new(nodes)
77
- @contexts << context = {:nodes => nodes, :block => block}
78
-
79
- # Evaluate the new context for existing nodes
80
- each {|node| eval_context(context, node)}
81
-
82
- context
83
- end
84
-
85
- # Adds a new node to the collection. By doing so, this will also add it to
86
- # the configured indices. This will also evaluate any existings contexts
87
- # that match the new node.
88
- def <<(node)
89
- @nodes << node
90
- @index_names.each {|name| add_to_index(name, value(node, name), node)}
91
- @contexts.each {|context| eval_context(context, node)}
92
- self
93
- end
94
-
95
- # Appends a group of nodes to the collection
96
- def concat(nodes)
97
- nodes.each {|node| self << node}
98
- end
99
-
100
- # Updates the indexed keys for the given node. If the node's attribute
101
- # has changed since it was added to the collection, the old indexed keys
102
- # will be replaced with the updated ones.
103
- def update(node)
104
- @index_names.each {|name| update_index(name, node)}
105
- end
106
-
107
- # Calls the block once for each element in self, passing that element as a
108
- # parameter.
109
- #
110
- # states = EnumStateMachine::NodeCollection.new
111
- # states << EnumStateMachine::State.new(machine, :parked)
112
- # states << EnumStateMachine::State.new(machine, :idling)
113
- # states.each {|state| puts state.name, ' -- '}
114
- #
115
- # ...produces:
116
- #
117
- # parked -- idling --
118
- def each
119
- @nodes.each {|node| yield node}
120
- self
121
- end
122
-
123
- # Gets the node at the given index.
124
- #
125
- # states = EnumStateMachine::NodeCollection.new
126
- # states << EnumStateMachine::State.new(machine, :parked)
127
- # states << EnumStateMachine::State.new(machine, :idling)
128
- #
129
- # states.at(0).name # => :parked
130
- # states.at(1).name # => :idling
131
- def at(index)
132
- @nodes[index]
133
- end
134
-
135
- # Gets the node indexed by the given key. By default, this will look up the
136
- # key in the first index configured for the collection. A custom index can
137
- # be specified like so:
138
- #
139
- # collection['parked', :value]
140
- #
141
- # The above will look up the "parked" key in a hash indexed by each node's
142
- # +value+ attribute.
143
- #
144
- # If the key cannot be found, then nil will be returned.
145
- def [](key, index_name = @default_index)
146
- self.index(index_name)[key] ||
147
- self.index(:"#{index_name}_to_s")[key.to_s] ||
148
- to_sym?(key) && self.index(:"#{index_name}_to_sym")[:"#{key}"] ||
149
- nil
150
- end
151
-
152
- # Gets the node indexed by the given key. By default, this will look up the
153
- # key in the first index configured for the collection. A custom index can
154
- # be specified like so:
155
- #
156
- # collection['parked', :value]
157
- #
158
- # The above will look up the "parked" key in a hash indexed by each node's
159
- # +value+ attribute.
160
- #
161
- # If the key cannot be found, then an IndexError exception will be raised:
162
- #
163
- # collection['invalid', :value] # => IndexError: "invalid" is an invalid value
164
- def fetch(key, index_name = @default_index)
165
- self[key, index_name] || raise(IndexError, "#{key.inspect} is an invalid #{index_name}")
166
- end
167
-
168
- protected
169
- # Gets the given index. If the index does not exist, then an ArgumentError
170
- # is raised.
171
- def index(name)
172
- raise ArgumentError, 'No indices configured' unless @indices.any?
173
- @indices[name] || raise(ArgumentError, "Invalid index: #{name.inspect}")
174
- end
175
-
176
- # Gets the value for the given attribute on the node
177
- def value(node, attribute)
178
- node.send(attribute)
179
- end
180
-
181
- # Adds the given key / node combination to an index, including the string
182
- # and symbol versions of the index
183
- def add_to_index(name, key, node)
184
- index(name)[key] = node
185
- index(:"#{name}_to_s")[key.to_s] = node
186
- index(:"#{name}_to_sym")[:"#{key}"] = node if to_sym?(key)
187
- end
188
-
189
- # Removes the given key from an index, including the string and symbol
190
- # versions of the index
191
- def remove_from_index(name, key)
192
- index(name).delete(key)
193
- index(:"#{name}_to_s").delete(key.to_s)
194
- index(:"#{name}_to_sym").delete(:"#{key}") if to_sym?(key)
195
- end
196
-
197
- # Updates the node for the given index, including the string and symbol
198
- # versions of the index
199
- def update_index(name, node)
200
- index = self.index(name)
201
- old_key = RUBY_VERSION < '1.9' ? index.index(node) : index.key(node)
202
- new_key = value(node, name)
203
-
204
- # Only replace the key if it's changed
205
- if old_key != new_key
206
- remove_from_index(name, old_key)
207
- add_to_index(name, new_key, node)
208
- end
209
- end
210
-
211
- # Determines whether the given value can be converted to a symbol
212
- def to_sym?(value)
213
- "#{value}" != ''
214
- end
215
-
216
- # Evaluates the given context for a particular node. This will only
217
- # evaluate the context if the node matches.
218
- def eval_context(context, node)
219
- node.context(&context[:block]) if context[:nodes].matches?(node.name)
220
- end
221
- end
222
- end
@@ -1,120 +0,0 @@
1
- module EnumStateMachine
2
- # A path represents a sequence of transitions that can be run for a particular
3
- # object. Paths can walk to new transitions, revealing all of the possible
4
- # branches that can be encountered in the object's state machine.
5
- class Path < Array
6
- include Assertions
7
-
8
- # The object whose state machine is being walked
9
- attr_reader :object
10
-
11
- # The state machine this path is walking
12
- attr_reader :machine
13
-
14
- # Creates a new transition path for the given object. Initially this is an
15
- # empty path. In order to start walking the path, it must be populated with
16
- # an initial transition.
17
- #
18
- # Configuration options:
19
- # * <tt>:target</tt> - The target state to end the path on
20
- # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
21
- # conditionals defined for each one
22
- def initialize(object, machine, options = {})
23
- assert_valid_keys(options, :target, :guard)
24
-
25
- @object = object
26
- @machine = machine
27
- @target = options[:target]
28
- @guard = options[:guard]
29
- end
30
-
31
- def initialize_copy(orig) #:nodoc:
32
- super
33
- @transitions = nil
34
- end
35
-
36
- # The initial state name for this path
37
- def from_name
38
- first && first.from_name
39
- end
40
-
41
- # Lists all of the from states that can be reached through this path.
42
- #
43
- # For example,
44
- #
45
- # path.to_states # => [:parked, :idling, :first_gear, ...]
46
- def from_states
47
- map {|transition| transition.from_name}.uniq
48
- end
49
-
50
- # The end state name for this path. If a target state was specified for
51
- # the path, then that will be returned if the path is complete.
52
- def to_name
53
- last && last.to_name
54
- end
55
-
56
- # Lists all of the to states that can be reached through this path.
57
- #
58
- # For example,
59
- #
60
- # path.to_states # => [:parked, :idling, :first_gear, ...]
61
- def to_states
62
- map {|transition| transition.to_name}.uniq
63
- end
64
-
65
- # Lists all of the events that can be fired through this path.
66
- #
67
- # For example,
68
- #
69
- # path.events # => [:park, :ignite, :shift_up, ...]
70
- def events
71
- map {|transition| transition.event}.uniq
72
- end
73
-
74
- # Walks down the next transitions at the end of this path. This will only
75
- # walk down paths that are considered valid.
76
- def walk
77
- transitions.each {|transition| yield dup.push(transition)}
78
- end
79
-
80
- # Determines whether or not this path has completed. A path is considered
81
- # complete when one of the following conditions is met:
82
- # * The last transition in the path ends on the target state
83
- # * There are no more transitions remaining to walk and there is no target
84
- # state
85
- def complete?
86
- !empty? && (@target ? to_name == @target : transitions.empty?)
87
- end
88
-
89
- private
90
- # Calculates the number of times the given state has been walked to
91
- def times_walked_to(state)
92
- select {|transition| transition.to_name == state}.length
93
- end
94
-
95
- # Determines whether the given transition has been recently walked down in
96
- # this path. If a target is configured for this path, then this will only
97
- # look at transitions walked down since the target was last reached.
98
- def recently_walked?(transition)
99
- transitions = self
100
- if @target && @target != to_name && target_transition = detect {|t| t.to_name == @target}
101
- transitions = transitions[index(target_transition) + 1..-1]
102
- end
103
- transitions.include?(transition)
104
- end
105
-
106
- # Determines whether it's possible to walk to the given transition from
107
- # the current path. A transition can be walked to if:
108
- # * It has not been recently walked and
109
- # * If a target is specified, it has not been walked to twice yet
110
- def can_walk_to?(transition)
111
- !recently_walked?(transition) && (!@target || times_walked_to(@target) < 2)
112
- end
113
-
114
- # Get the next set of transitions that can be walked to starting from the
115
- # end of this path
116
- def transitions
117
- @transitions ||= empty? ? [] : machine.events.transitions_for(object, :from => to_name, :guard => @guard).select {|transition| can_walk_to?(transition)}
118
- end
119
- end
120
- end