substation 0.0.9 → 0.0.10.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +0 -1
  3. data/Changelog.md +24 -82
  4. data/Gemfile.devtools +17 -24
  5. data/README.md +46 -116
  6. data/config/flay.yml +2 -2
  7. data/config/flog.yml +1 -1
  8. data/config/mutant.yml +0 -1
  9. data/config/reek.yml +5 -10
  10. data/lib/substation.rb +3 -8
  11. data/lib/substation/chain.rb +64 -108
  12. data/lib/substation/chain/dsl.rb +30 -57
  13. data/lib/substation/dispatcher.rb +1 -3
  14. data/lib/substation/environment.rb +23 -9
  15. data/lib/substation/environment/dsl.rb +3 -4
  16. data/lib/substation/observer.rb +2 -4
  17. data/lib/substation/processor.rb +7 -106
  18. data/lib/substation/processor/evaluator.rb +42 -83
  19. data/lib/substation/processor/pivot.rb +25 -0
  20. data/lib/substation/processor/wrapper.rb +2 -4
  21. data/lib/substation/request.rb +1 -10
  22. data/lib/substation/response.rb +0 -11
  23. data/lib/substation/utils.rb +1 -3
  24. data/lib/substation/version.rb +1 -3
  25. data/spec/integration/substation/dispatcher/call_spec.rb +12 -12
  26. data/spec/spec_helper.rb +21 -30
  27. data/spec/unit/substation/chain/call_spec.rb +32 -202
  28. data/spec/unit/substation/chain/dsl/builder/class_methods/call_spec.rb +2 -2
  29. data/spec/unit/substation/chain/dsl/builder/dsl_spec.rb +6 -8
  30. data/spec/unit/substation/chain/dsl/builder/failure_chain_spec.rb +30 -0
  31. data/spec/unit/substation/chain/dsl/chain_spec.rb +2 -1
  32. data/spec/unit/substation/chain/dsl/class_methods/processors_spec.rb +24 -0
  33. data/spec/unit/substation/chain/dsl/initialize_spec.rb +19 -0
  34. data/spec/unit/substation/chain/dsl/processors_spec.rb +21 -9
  35. data/spec/unit/substation/chain/dsl/use_spec.rb +3 -2
  36. data/spec/unit/substation/chain/each_spec.rb +9 -5
  37. data/spec/unit/substation/chain/incoming/result_spec.rb +21 -0
  38. data/spec/unit/substation/chain/outgoing/call_spec.rb +25 -0
  39. data/spec/unit/substation/{processor → chain/outgoing}/result_spec.rb +5 -6
  40. data/spec/unit/substation/dispatcher/action/call_spec.rb +6 -7
  41. data/spec/unit/substation/dispatcher/action/class_methods/coerce_spec.rb +5 -7
  42. data/spec/unit/substation/dispatcher/action_names_spec.rb +1 -1
  43. data/spec/unit/substation/dispatcher/call_spec.rb +3 -3
  44. data/spec/unit/substation/dispatcher/class_methods/coerce_spec.rb +7 -7
  45. data/spec/unit/substation/environment/chain_spec.rb +32 -22
  46. data/spec/unit/substation/environment/class_methods/build_spec.rb +4 -11
  47. data/spec/unit/substation/environment/dsl/class_methods/registry_spec.rb +3 -5
  48. data/spec/unit/substation/environment/dsl/register_spec.rb +3 -8
  49. data/spec/unit/substation/environment/dsl/registry_spec.rb +3 -5
  50. data/spec/unit/substation/observer/chain/call_spec.rb +3 -5
  51. data/spec/unit/substation/observer/class_methods/coerce_spec.rb +2 -4
  52. data/spec/unit/substation/observer/null/call_spec.rb +1 -3
  53. data/spec/unit/substation/processor/evaluator/call_spec.rb +35 -21
  54. data/spec/unit/substation/processor/pivot/call_spec.rb +17 -0
  55. data/spec/unit/substation/processor/wrapper/call_spec.rb +7 -8
  56. data/spec/unit/substation/request/env_spec.rb +4 -5
  57. data/spec/unit/substation/request/error_spec.rb +4 -5
  58. data/spec/unit/substation/request/input_spec.rb +4 -5
  59. data/spec/unit/substation/request/success_spec.rb +4 -5
  60. data/spec/unit/substation/response/env_spec.rb +5 -6
  61. data/spec/unit/substation/response/failure/success_predicate_spec.rb +4 -5
  62. data/spec/unit/substation/response/input_spec.rb +5 -6
  63. data/spec/unit/substation/response/output_spec.rb +4 -5
  64. data/spec/unit/substation/response/request_spec.rb +5 -6
  65. data/spec/unit/substation/response/success/success_predicate_spec.rb +4 -5
  66. data/spec/unit/substation/utils/class_methods/coerce_callable_spec.rb +13 -15
  67. data/spec/unit/substation/utils/class_methods/const_get_spec.rb +6 -6
  68. data/spec/unit/substation/utils/class_methods/symbolize_keys_spec.rb +4 -4
  69. data/substation.gemspec +1 -1
  70. metadata +18 -45
  71. data/config/rubocop.yml +0 -35
  72. data/lib/substation/processor/transformer.rb +0 -26
  73. data/spec/unit/substation/chain/class_methods/failure_response_spec.rb +0 -16
  74. data/spec/unit/substation/chain/dsl/class_methods/build_spec.rb +0 -24
  75. data/spec/unit/substation/chain/dsl/failure_chain_spec.rb +0 -35
  76. data/spec/unit/substation/chain/failure_data/equalizer_spec.rb +0 -46
  77. data/spec/unit/substation/chain/failure_data/hash_spec.rb +0 -13
  78. data/spec/unit/substation/environment/equalizer_spec.rb +0 -25
  79. data/spec/unit/substation/processor/evaluator/class_methods/new_spec.rb +0 -9
  80. data/spec/unit/substation/processor/evaluator/data/call_spec.rb +0 -34
  81. data/spec/unit/substation/processor/evaluator/pivot/call_spec.rb +0 -34
  82. data/spec/unit/substation/processor/evaluator/request/call_spec.rb +0 -34
  83. data/spec/unit/substation/processor/fallible/name_spec.rb +0 -15
  84. data/spec/unit/substation/processor/fallible/with_failure_chain_spec.rb +0 -18
  85. data/spec/unit/substation/processor/incoming/result_spec.rb +0 -25
  86. data/spec/unit/substation/processor/outgoing/call_spec.rb +0 -28
  87. data/spec/unit/substation/processor/outgoing/name_spec.rb +0 -14
  88. data/spec/unit/substation/processor/outgoing/success_predicate_spec.rb +0 -15
  89. data/spec/unit/substation/processor/success_predicate_spec.rb +0 -22
  90. data/spec/unit/substation/processor/transformer/call_spec.rb +0 -21
  91. data/spec/unit/substation/request/name_spec.rb +0 -15
  92. data/spec/unit/substation/response/to_request_spec.rb +0 -19
data/config/flay.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
- threshold: 9
3
- total_score: 134
2
+ threshold: 6
3
+ total_score: 87
data/config/flog.yml CHANGED
@@ -1,2 +1,2 @@
1
1
  ---
2
- threshold: 13.3
2
+ threshold: 16.4
data/config/mutant.yml CHANGED
@@ -1,4 +1,3 @@
1
1
  ---
2
2
  name: substation
3
3
  namespace: Substation
4
- strategy: --rspec-dm2
data/config/reek.yml CHANGED
@@ -25,10 +25,9 @@ FeatureEnvy:
25
25
  enabled: true
26
26
  exclude:
27
27
  - Substation::Chain#call # loops over instance state
28
- - Substation::Processor::Outgoing#respond_with #defined in a module
28
+ - Substation::Chain::Incoming#result # defined in a module
29
+ - Substation::Chain::Outgoing#respond_with #defined in a module
29
30
  - Substation::Processor::Evaluator#call # method object
30
- - Substation::Processor::Evaluator#on_success # method object
31
- - Substation::Processor#success? # in a module to be included in a method object
32
31
  IrresponsibleModule:
33
32
  enabled: true
34
33
  exclude: []
@@ -37,8 +36,6 @@ LongParameterList:
37
36
  exclude:
38
37
  - Substation::Dispatcher#call
39
38
  - Substation::Chain::DSL::Builder#define_dsl_method
40
- - Substation::Chain#self.failure_response
41
- - Substation::Chain#on_failure
42
39
  max_params: 2
43
40
  LongYieldList:
44
41
  enabled: true
@@ -65,8 +62,7 @@ TooManyInstanceVariables:
65
62
  max_instance_variables: 3
66
63
  TooManyMethods:
67
64
  enabled: true
68
- exclude:
69
- - Substation::Chain::DSL
65
+ exclude: []
70
66
  max_methods: 4
71
67
  TooManyStatements:
72
68
  enabled: true
@@ -115,7 +111,6 @@ UnusedParameters:
115
111
  UtilityFunction:
116
112
  enabled: true
117
113
  exclude:
118
- - Substation::Processor::Outgoing#respond_with # defined in a module
119
- - Substation::Processor::Evaluator#on_success # inside a method object
120
- - Substation::Processor#success? # in a module to be included in a method object
114
+ - Substation::Chain::Incoming#result # defined in a module
115
+ - Substation::Chain::Outgoing#respond_with # defined in a module
121
116
  max_helper_calls: 0
data/lib/substation.rb CHANGED
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  require 'set'
4
2
  require 'forwardable'
5
3
 
@@ -34,11 +32,8 @@ require 'concord'
34
32
 
35
33
  module Substation
36
34
 
37
- # An empty frozen array useful for (default) parameters
38
- EMPTY_ARRAY = [].freeze
39
-
40
- # Error raised when trying to access an unknown processor
41
- UnknownProcessor = Class.new(StandardError)
35
+ # Represent an undefined argument
36
+ Undefined = Object.new.freeze
42
37
 
43
38
  end
44
39
 
@@ -50,8 +45,8 @@ require 'substation/chain'
50
45
  require 'substation/chain/dsl'
51
46
  require 'substation/processor'
52
47
  require 'substation/processor/evaluator'
48
+ require 'substation/processor/pivot'
53
49
  require 'substation/processor/wrapper'
54
- require 'substation/processor/transformer'
55
50
  require 'substation/environment'
56
51
  require 'substation/environment/dsl'
57
52
  require 'substation/dispatcher'
@@ -1,19 +1,20 @@
1
- # encoding: utf-8
2
-
3
1
  module Substation
4
2
 
5
3
  # Implements a chain of responsibility for an action
6
4
  #
7
5
  # An instance of this class will typically contain (in that order)
8
- # a few processors that process the incoming {Request} object, one
9
- # processor that calls an action ({Processor::Pivot}), and some processors
6
+ # a few handlers that process the incoming {Request} object, one
7
+ # handler that calls an action ({Chain::Pivot}), and some handlers
10
8
  # that process the outgoing {Response} object.
11
9
  #
12
- # @example chain processors (used in instance method examples)
10
+ # Both {Chain::Incoming} and {Chain::Outgoing} handlers must
11
+ # respond to `#call(response)` and `#result(response)`.
12
+ #
13
+ # @example chain handlers (used in instance method examples)
13
14
  #
14
15
  # module App
15
16
  #
16
- # class Processor
17
+ # class Handler
17
18
  #
18
19
  # def initialize(handler = nil)
19
20
  # @handler = handler
@@ -24,11 +25,11 @@ module Substation
24
25
  # attr_reader :handler
25
26
  #
26
27
  # class Incoming < self
27
- # include Substation::Processor::Incoming
28
+ # include Substation::Chain::Incoming
28
29
  # end
29
30
  #
30
31
  # class Outgoing < self
31
- # include Substation::Processor::Outgoing
32
+ # include Substation::Chain::Outgoing
32
33
  #
33
34
  # private
34
35
  #
@@ -38,13 +39,13 @@ module Substation
38
39
  # end
39
40
  # end
40
41
  #
41
- # class Validator < Processor::Incoming
42
+ # class Validator < Handler::Incoming
42
43
  # def call(request)
43
44
  # result = handler.call(request.input)
44
- # if result.success?
45
+ # if result.valid?
45
46
  # request.success(request.input)
46
47
  # else
47
- # request.error(result.output)
48
+ # request.error(result.violations)
48
49
  # end
49
50
  # end
50
51
  # end
@@ -57,7 +58,7 @@ module Substation
57
58
  # end
58
59
  # end
59
60
  #
60
- # class Presenter < Processor::Outgoing
61
+ # class Presenter < Handler::Outgoing
61
62
  # def call(response)
62
63
  # respond_with(response, handler.new(response.output))
63
64
  # end
@@ -66,97 +67,74 @@ module Substation
66
67
  #
67
68
  class Chain
68
69
 
69
- include Enumerable
70
- include Concord.new(:processors, :failure_chain)
71
- include Adamantium::Flat
72
-
73
- # Empty chain
74
- EMPTY = Class.new(self).new(EMPTY_ARRAY, EMPTY_ARRAY)
75
-
76
- # Wraps response data and an exception not caught from a handler
77
- class FailureData
78
- include Equalizer.new(:data)
70
+ # Supports chaining handlers processed before the {Pivot}
71
+ module Incoming
79
72
 
80
- # Return the data available when +exception+ was raised
81
- #
82
- # @return [Object]
73
+ # The request passed on to the next handler in a {Chain}
83
74
  #
84
- # @api private
85
- attr_reader :data
86
-
87
- # Return the exception instance
75
+ # @param [Response] response
76
+ # the response returned from the previous handler in a {Chain}
88
77
  #
89
- # @return [Class<StandardError>]
78
+ # @return [Request]
79
+ # the request passed on to the next handler in a {Chain}
90
80
  #
91
81
  # @api private
92
- attr_reader :exception
93
-
94
- # Initialize a new instance
95
- #
96
- # @param [Object] data
97
- # the data available when +exception+ was raised
98
- #
99
- # @param [Class<StandardError>] exception
100
- # the exception instance raised from a handler
101
- #
102
- # @return [undefined]
103
- #
104
- # @api private
105
- def initialize(data, exception)
106
- @data, @exception = data, exception
82
+ def result(response)
83
+ Request.new(response.env, response.output)
107
84
  end
85
+ end
86
+
87
+ # Supports chaining the {Pivot} or handlers processed after the {Pivot}
88
+ module Outgoing
108
89
 
109
- # Return the hash value
90
+ # The response passed on to the next handler in a {Chain}
110
91
  #
111
- # @return [Fixnum]
92
+ # @param [Response] response
93
+ # the response returned from the previous handler in a {Chain}
94
+ #
95
+ # @return [Response]
96
+ # the response passed on to the next handler in a {Chain}
112
97
  #
113
98
  # @api private
114
- def hash
115
- super ^ exception.class.hash
99
+ def result(response)
100
+ response
116
101
  end
117
102
 
118
103
  private
119
104
 
120
- # Tests wether +other+ is comparable using +comparator+
105
+ # Build a new {Response} based on +response+ and +output+
121
106
  #
122
- # @param [Symbol] comparator
123
- # the operation used for comparison
107
+ # @param [Response] response
108
+ # the original response
124
109
  #
125
- # @param [Object] other
126
- # the object to test
110
+ # @param [Object] output
111
+ # the data to be wrapped within the new {Response}
127
112
  #
128
- # @return [Boolean]
113
+ # @return [Response]
129
114
  #
130
115
  # @api private
131
- def cmp?(comparator, other)
132
- super && exception.class.send(comparator, other.exception.class)
116
+ def respond_with(response, output)
117
+ response.class.new(response.request, output)
133
118
  end
134
119
  end
135
120
 
136
- # Return a failure response
137
- #
138
- # @param [Request] request
139
- # the initial request passed into the chain
140
- #
141
- # @param [Object] data
142
- # the processed data available when the exception was raised
143
- #
144
- # @param [Class<StandardError>] exception
145
- # the exception instance that was raised
146
- #
147
- # @return [Response::Failure]
148
- #
149
- # @api private
150
- def self.failure_response(request, data, exception)
151
- Response::Failure.new(request, FailureData.new(data, exception))
152
- end
121
+ # Supports chaining the {Pivot} handler
122
+ Pivot = Outgoing
123
+
124
+ include Enumerable
125
+ include Concord.new(:handlers)
126
+ include Adamantium::Flat
127
+ include Pivot # allow nesting of chains
128
+
129
+ # Empty chain
130
+ EMPTY = Class.new(self).new([])
153
131
 
154
132
  # Call the chain
155
133
  #
156
- # Invokes all processors and returns either the first
134
+ # Invokes all handlers and returns either the first
157
135
  # {Response::Failure} that it encounters, or if all
158
136
  # goes well, the {Response::Success} returned from
159
- # the last processor.
137
+ # the last handler.
160
138
  #
161
139
  # @example
162
140
  #
@@ -184,21 +162,20 @@ module Substation
184
162
  # the request to handle
185
163
  #
186
164
  # @return [Response::Success]
187
- # the response returned from the last processor
165
+ # the response returned from the last handler
188
166
  #
189
167
  # @return [Response::Failure]
190
- # the response returned from the failing processor's failure chain
168
+ # the response returned from the failing handler
169
+ #
170
+ # @raise [Exception]
171
+ # any exception that isn't explicitly rescued in client code
191
172
  #
192
173
  # @api public
193
174
  def call(request)
194
- processors.reduce(request) { |result, processor|
195
- begin
196
- response = processor.call(result)
197
- return response unless processor.success?(response)
198
- processor.result(response)
199
- rescue => exception
200
- return on_failure(request, result, exception)
201
- end
175
+ handlers.inject(request) { |result, handler|
176
+ response = handler.call(result)
177
+ return response unless response.success?
178
+ handler.result(response)
202
179
  }
203
180
  end
204
181
 
@@ -217,29 +194,8 @@ module Substation
217
194
  # @api private
218
195
  def each(&block)
219
196
  return to_enum unless block
220
- processors.each(&block)
197
+ handlers.each(&block)
221
198
  self
222
199
  end
223
-
224
- private
225
-
226
- # Call the failure chain in case of an uncaught exception
227
- #
228
- # @param [Request] request
229
- # the initial request passed into the chain
230
- #
231
- # @param [Object] data
232
- # the processed data available when the exception was raised
233
- #
234
- # @param [Class<StandardError>] exception
235
- # the exception instance that was raised
236
- #
237
- # @return [Response::Failure]
238
- #
239
- # @api private
240
- def on_failure(request, data, exception)
241
- failure_chain.call(self.class.failure_response(request, data, exception))
242
- end
243
-
244
200
  end # class Chain
245
201
  end # module Substation
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Substation
4
2
  class Chain
5
3
 
@@ -55,6 +53,7 @@ module Substation
55
53
  }
56
54
  end
57
55
 
56
+
58
57
  # Define a new instance method on the +dsl+ class
59
58
  #
60
59
  # @param [Symbol] name
@@ -71,36 +70,46 @@ module Substation
71
70
  # @api private
72
71
  def define_dsl_method(name, processor, dsl)
73
72
  dsl.class_eval do
74
- define_method(name) { |*args| use(processor.new(name, *args)) }
73
+ define_method(name) do |*args, &block|
74
+ use(processor.new(env, *args, &block))
75
+ end
75
76
  end
76
77
  end
77
78
 
78
79
  end # class Builder
79
80
 
80
- # Build a new {Chain} based on +other+, a +failure_chain+ and a block
81
+ # The processors to be used within a {Chain}
81
82
  #
82
- # @param [#each<#call>] other
83
- # the processors to build on top of
83
+ # @return [Array<#call>]
84
+ #
85
+ # @api private
86
+ attr_reader :processors
87
+
88
+ # The processors to be used within a {Chain}
89
+ #
90
+ # @param [Environment] env
91
+ # the substation environment used to build chains
84
92
  #
85
- # @param [Chain] failure_chain
86
- # the failure chain to invoke in case of an uncaught exception
93
+ # @param [Chain] chain
94
+ # the chain to build on top of
87
95
  #
88
96
  # @param [Proc] block
89
97
  # a block to be instance_eval'ed
90
98
  #
91
- # @return [Chain]
99
+ # @return [Array<#call>]
92
100
  #
93
101
  # @api private
94
- def self.build(other, failure_chain, &block)
95
- Chain.new(new(other, &block).processors, failure_chain)
102
+ def self.processors(env, chain, &block)
103
+ new(env, chain, &block).processors
96
104
  end
97
105
 
98
- include Equalizer.new(:processors)
99
-
100
106
  # Initialize a new instance
101
107
  #
108
+ # @param [Environment] env
109
+ # the substation environment used to build chains
110
+ #
102
111
  # @param [#each<#call>] processors
103
- # the processors to build on top of
112
+ # an enumerable of processors to build on top of
104
113
  #
105
114
  # @param [Proc] block
106
115
  # a block to be instance_eval'ed
@@ -108,21 +117,12 @@ module Substation
108
117
  # @return [undefined]
109
118
  #
110
119
  # @api private
111
- def initialize(processors, &block)
112
- @processors = {}
120
+ def initialize(env, processors, &block)
121
+ @env, @processors = env, []
113
122
  chain(processors)
114
123
  instance_eval(&block) if block
115
124
  end
116
125
 
117
- # The processors to be used within a {Chain}
118
- #
119
- # @return [Array<#call>]
120
- #
121
- # @api private
122
- def processors
123
- @processors.values
124
- end
125
-
126
126
  # Use the given +processor+ within a chain
127
127
  #
128
128
  # @param [#call] processor
@@ -132,7 +132,7 @@ module Substation
132
132
  #
133
133
  # @api private
134
134
  def use(processor)
135
- @processors[processor.name] = processor
135
+ @processors << processor
136
136
  self
137
137
  end
138
138
 
@@ -145,45 +145,18 @@ module Substation
145
145
  #
146
146
  # @api private
147
147
  def chain(other)
148
- other.each { |processor| use(processor) }
149
- self
150
- end
151
-
152
- # Use +chain+ as the failure chain for the processor identified by +name+
153
- #
154
- # @param [Symbol] name
155
- # the processor's name
156
- #
157
- # @param [#call] chain
158
- # the failure chain to use for the processor identified by +name+
159
- #
160
- # @return [self]
161
- #
162
- # @api private
163
- def failure_chain(name, chain)
164
- @processors[name] = processor(name).with_failure_chain(chain)
148
+ other.each { |handler| use(handler) }
165
149
  self
166
150
  end
167
151
 
168
152
  private
169
153
 
170
- # Return the processor identified by +name+
171
- #
172
- # @param [Symbol] name
173
- # the processor's name
154
+ # The substation environment used to build chains
174
155
  #
175
- # @return [#call]
176
- # the processor identified by +name+
177
- #
178
- # @raise [UnknownProcessor]
179
- # if no processor identified by +name+ is registered
156
+ # @return [Environment]
180
157
  #
181
158
  # @api private
182
- def processor(name)
183
- @processors.fetch(name) {
184
- raise UnknownProcessor, "No processor named #{name.inspect} is registered"
185
- }
186
- end
159
+ attr_reader :env
187
160
 
188
161
  end # class DSL
189
162
  end # class Chain