substation 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/.travis.yml +4 -0
  2. data/Changelog.md +92 -2
  3. data/Gemfile.devtools +19 -17
  4. data/README.md +116 -46
  5. data/config/flay.yml +2 -2
  6. data/config/mutant.yml +1 -0
  7. data/config/reek.yml +11 -5
  8. data/config/rubocop.yml +35 -0
  9. data/lib/substation.rb +10 -1
  10. data/lib/substation/chain.rb +108 -64
  11. data/lib/substation/chain/dsl.rb +62 -37
  12. data/lib/substation/dispatcher.rb +3 -1
  13. data/lib/substation/environment.rb +9 -6
  14. data/lib/substation/environment/dsl.rb +4 -3
  15. data/lib/substation/observer.rb +2 -0
  16. data/lib/substation/processor.rb +106 -7
  17. data/lib/substation/processor/evaluator.rb +98 -7
  18. data/lib/substation/processor/transformer.rb +26 -0
  19. data/lib/substation/processor/wrapper.rb +5 -3
  20. data/lib/substation/request.rb +12 -1
  21. data/lib/substation/response.rb +13 -0
  22. data/lib/substation/utils.rb +3 -1
  23. data/lib/substation/version.rb +3 -1
  24. data/spec/integration/substation/dispatcher/call_spec.rb +12 -12
  25. data/spec/spec_helper.rb +39 -32
  26. data/spec/unit/substation/chain/call_spec.rb +205 -29
  27. data/spec/unit/substation/chain/class_methods/failure_response_spec.rb +16 -0
  28. data/spec/unit/substation/chain/dsl/builder/dsl_spec.rb +7 -4
  29. data/spec/unit/substation/chain/dsl/class_methods/build_spec.rb +24 -0
  30. data/spec/unit/substation/chain/dsl/failure_chain_spec.rb +35 -0
  31. data/spec/unit/substation/chain/dsl/processors_spec.rb +8 -6
  32. data/spec/unit/substation/chain/dsl/use_spec.rb +1 -1
  33. data/spec/unit/substation/chain/each_spec.rb +5 -9
  34. data/spec/unit/substation/chain/failure_data/equalizer_spec.rb +46 -0
  35. data/spec/unit/substation/chain/failure_data/hash_spec.rb +13 -0
  36. data/spec/unit/substation/dispatcher/action/call_spec.rb +2 -1
  37. data/spec/unit/substation/dispatcher/action/class_methods/coerce_spec.rb +7 -5
  38. data/spec/unit/substation/dispatcher/call_spec.rb +2 -2
  39. data/spec/unit/substation/dispatcher/class_methods/coerce_spec.rb +6 -6
  40. data/spec/unit/substation/environment/chain_spec.rb +22 -27
  41. data/spec/unit/substation/environment/class_methods/build_spec.rb +11 -4
  42. data/spec/unit/substation/environment/dsl/class_methods/registry_spec.rb +5 -3
  43. data/spec/unit/substation/environment/dsl/register_spec.rb +8 -3
  44. data/spec/unit/substation/environment/dsl/registry_spec.rb +5 -3
  45. data/spec/unit/substation/environment/equalizer_spec.rb +25 -0
  46. data/spec/unit/substation/observer/chain/call_spec.rb +2 -0
  47. data/spec/unit/substation/observer/class_methods/coerce_spec.rb +2 -0
  48. data/spec/unit/substation/observer/null/call_spec.rb +2 -0
  49. data/spec/unit/substation/processor/evaluator/call_spec.rb +20 -10
  50. data/spec/unit/substation/processor/evaluator/class_methods/new_spec.rb +9 -0
  51. data/spec/unit/substation/processor/evaluator/data/call_spec.rb +34 -0
  52. data/spec/unit/substation/processor/evaluator/pivot/call_spec.rb +34 -0
  53. data/spec/unit/substation/processor/evaluator/request/call_spec.rb +34 -0
  54. data/spec/unit/substation/processor/fallible/name_spec.rb +15 -0
  55. data/spec/unit/substation/processor/fallible/with_failure_chain_spec.rb +18 -0
  56. data/spec/unit/substation/processor/incoming/result_spec.rb +25 -0
  57. data/spec/unit/substation/processor/outgoing/call_spec.rb +28 -0
  58. data/spec/unit/substation/processor/outgoing/name_spec.rb +14 -0
  59. data/spec/unit/substation/processor/outgoing/success_predicate_spec.rb +15 -0
  60. data/spec/unit/substation/{chain/outgoing → processor}/result_spec.rb +4 -3
  61. data/spec/unit/substation/processor/success_predicate_spec.rb +22 -0
  62. data/spec/unit/substation/processor/transformer/call_spec.rb +21 -0
  63. data/spec/unit/substation/processor/wrapper/call_spec.rb +9 -7
  64. data/spec/unit/substation/request/env_spec.rb +3 -2
  65. data/spec/unit/substation/request/error_spec.rb +2 -1
  66. data/spec/unit/substation/request/input_spec.rb +3 -2
  67. data/spec/unit/substation/request/name_spec.rb +15 -0
  68. data/spec/unit/substation/request/success_spec.rb +2 -1
  69. data/spec/unit/substation/response/env_spec.rb +3 -2
  70. data/spec/unit/substation/response/failure/success_predicate_spec.rb +2 -1
  71. data/spec/unit/substation/response/input_spec.rb +3 -2
  72. data/spec/unit/substation/response/output_spec.rb +2 -1
  73. data/spec/unit/substation/response/request_spec.rb +3 -2
  74. data/spec/unit/substation/response/success/success_predicate_spec.rb +2 -1
  75. data/spec/unit/substation/response/to_request_spec.rb +19 -0
  76. data/spec/unit/substation/utils/class_methods/coerce_callable_spec.rb +14 -12
  77. data/spec/unit/substation/utils/class_methods/const_get_spec.rb +6 -6
  78. data/spec/unit/substation/utils/class_methods/symbolize_keys_spec.rb +4 -4
  79. metadata +25 -9
  80. data/lib/substation/processor/pivot.rb +0 -25
  81. data/spec/unit/substation/chain/class_methods/build_spec.rb +0 -31
  82. data/spec/unit/substation/chain/dsl/class_methods/processors_spec.rb +0 -23
  83. data/spec/unit/substation/chain/incoming/result_spec.rb +0 -21
  84. data/spec/unit/substation/chain/outgoing/call_spec.rb +0 -25
  85. data/spec/unit/substation/processor/pivot/call_spec.rb +0 -16
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Substation
2
4
 
3
5
  # Encapsulates all registered actions and their observers
@@ -249,7 +251,7 @@ module Substation
249
251
  #
250
252
  # @api public
251
253
  def call(name, input)
252
- fetch(name).call(Request.new(env, input))
254
+ fetch(name).call(Request.new(name, env, input))
253
255
  end
254
256
 
255
257
  # The names of all registered actions
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Substation
2
4
 
3
5
  # The environment holding all registered {Chain} processors
@@ -15,7 +17,9 @@ module Substation
15
17
  #
16
18
  # @api private
17
19
  def self.build(&block)
18
- new(DSL.registry(&block))
20
+ registry = DSL.registry(&block)
21
+ chain_dsl = Chain::DSL::Builder.call(registry)
22
+ new(registry, chain_dsl)
19
23
  end
20
24
 
21
25
  # Initialize a new instance
@@ -26,9 +30,8 @@ module Substation
26
30
  # @return [undefined]
27
31
  #
28
32
  # @api private
29
- def initialize(registry)
30
- @registry = registry
31
- @chain_dsl = Chain::DSL::Builder.call(@registry)
33
+ def initialize(registry, chain_dsl)
34
+ @registry, @chain_dsl = registry, chain_dsl
32
35
  end
33
36
 
34
37
  # Build a new {Chain} instance
@@ -42,8 +45,8 @@ module Substation
42
45
  # @return [Chain]
43
46
  #
44
47
  # @api private
45
- def chain(other = Chain::EMPTY, &block)
46
- Chain.build(@chain_dsl, other, &block)
48
+ def chain(other = Chain::EMPTY, failure_chain = Chain::EMPTY, &block)
49
+ @chain_dsl.build(other, failure_chain, &block)
47
50
  end
48
51
 
49
52
  protected
@@ -1,7 +1,9 @@
1
+ # encoding: utf-8
2
+
1
3
  module Substation
2
4
  class Environment
3
5
 
4
- # The DSL class used to define register processors
6
+ # The DSL class used to register processors
5
7
  class DSL
6
8
 
7
9
  # The registry of processors
@@ -11,7 +13,6 @@ module Substation
11
13
  # @api private
12
14
  attr_reader :registry
13
15
 
14
-
15
16
  # The registry of processors
16
17
  #
17
18
  # @param [Proc] block
@@ -49,7 +50,7 @@ module Substation
49
50
  #
50
51
  # @api private
51
52
  def register(name, processor)
52
- @registry[name] = processor
53
+ @registry[name.to_sym] = processor
53
54
  self
54
55
  end
55
56
 
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Substation
2
4
 
3
5
  # Abstract observer base class
@@ -1,23 +1,122 @@
1
+ # encoding: utf-8
2
+
1
3
  module Substation
2
4
 
3
5
  # Namespace for chain processors
4
6
  module Processor
5
7
 
6
- include AbstractType
7
- include Adamantium::Flat
8
+ # Test wether chain processing should continue
9
+ #
10
+ # @param [Response] response
11
+ # the response returned from invoking the processor
12
+ #
13
+ # @return [true] for a successful response
14
+ # @return [false] otherwise
15
+ #
16
+ # @api private
17
+ def success?(response)
18
+ response.success?
19
+ end
20
+
21
+ # The response passed on to the next processor in a {Chain}
22
+ #
23
+ # @param [Response] response
24
+ # the response returned from invoking the processor
25
+ #
26
+ # @return [Response]
27
+ # the response passed on to the next processor in a {Chain}
28
+ #
29
+ # @api private
30
+ def result(response)
31
+ response
32
+ end
33
+
34
+ module Fallible
35
+ include Concord.new(:name, :handler, :failure_chain)
36
+
37
+ # This processor's name
38
+ #
39
+ # @return [Symbol]
40
+ #
41
+ # @api private
42
+ attr_reader :name
8
43
 
9
- abstract_method :call
10
- abstract_method :result
44
+ # Return a new processor with +chain+ as failure_chain
45
+ #
46
+ # @param [#call] chain
47
+ # the failure chain to use for the new processor
48
+ #
49
+ # @return [#call]
50
+ #
51
+ # @api private
52
+ def with_failure_chain(chain)
53
+ self.class.new(name, handler, chain)
54
+ end
55
+ end
11
56
 
12
57
  module Incoming
13
58
  include Processor
14
- include Chain::Incoming
59
+ include Fallible
60
+
61
+ # The request passed on to the next processor in a {Chain}
62
+ #
63
+ # @param [Response] _response
64
+ # the response returned from invoking this processor
65
+ #
66
+ # @return [Request]
67
+ # the request passed on to the next processor in a {Chain}
68
+ #
69
+ # @api private
70
+ def result(_response)
71
+ super.to_request
72
+ end
73
+ end
74
+
75
+ module Pivot
76
+ include Processor
77
+ include Fallible
15
78
  end
16
79
 
17
80
  module Outgoing
81
+ include Concord.new(:name, :handler)
18
82
  include Processor
19
- include Chain::Outgoing
20
- end
21
83
 
84
+ # This processor's name
85
+ #
86
+ # @return [Symbol]
87
+ #
88
+ # @api private
89
+ attr_reader :name
90
+
91
+ # Test wether chain processing should continue
92
+ #
93
+ # @param [Response] _response
94
+ # the response returned from invoking the processor
95
+ #
96
+ # @return [false]
97
+ #
98
+ # @api private
99
+ def success?(_response)
100
+ true
101
+ end
102
+
103
+ private
104
+
105
+ # Build a new {Response} based on +response+ and +output+
106
+ #
107
+ # @param [Response] response
108
+ # the original response
109
+ #
110
+ # @param [Object] output
111
+ # the data to be wrapped within the new {Response}
112
+ #
113
+ # @return [Response]
114
+ #
115
+ # @api private
116
+ def respond_with(response, output)
117
+ response.class.new(response.request, output)
118
+ end
119
+
120
+ end # module Outgoing
22
121
  end # module Processor
23
122
  end # module Substation
@@ -1,11 +1,60 @@
1
+ # encoding: utf-8
2
+
1
3
  module Substation
2
4
  module Processor
3
5
 
4
- # A processor to evaluate a chain's request input data
6
+ # Abstract processor to evaluate a request coming into a chain
5
7
  class Evaluator
6
8
 
7
- include Incoming
8
- include Concord.new(:handler)
9
+ # Processor to evaluate request input data
10
+ class Data < self
11
+ include Incoming
12
+
13
+ private
14
+
15
+ # Invoke the handler
16
+ #
17
+ # @param [Request] request
18
+ # the request to evaluate
19
+ #
20
+ # @return [Response]
21
+ #
22
+ # @api private
23
+ def invoke(request)
24
+ handler.call(request.input)
25
+ end
26
+
27
+ end
28
+
29
+ # Processor to evaluate an incoming request
30
+ class Request < self
31
+ include Incoming
32
+ end
33
+
34
+ # Processor to evaluate a pivot chain handler
35
+ class Pivot < self
36
+ include Processor::Pivot
37
+
38
+ private
39
+
40
+ # Return a successful response
41
+ #
42
+ # @param [Request] _request
43
+ # the evaluated request
44
+ #
45
+ # @param [#output] result
46
+ # the evaluation result
47
+ #
48
+ # @return [Response::Success]
49
+ #
50
+ # @api private
51
+ def on_success(_request, response)
52
+ response
53
+ end
54
+ end
55
+
56
+ include Adamantium::Flat
57
+ include AbstractType
9
58
 
10
59
  # Evaluate a chain's request input data
11
60
  #
@@ -16,15 +65,57 @@ module Substation
16
65
  #
17
66
  # @api private
18
67
  def call(request)
19
- result = handler.call(request.input)
20
- output = result.output
68
+ result = invoke(request)
21
69
  if result.success?
22
- request.success(output)
70
+ on_success(request, result)
23
71
  else
24
- request.error(output)
72
+ on_failure(request, result)
25
73
  end
26
74
  end
27
75
 
76
+ private
77
+
78
+ # Invoke the handler
79
+ #
80
+ # @param [Request] request
81
+ # the request to evaluate
82
+ #
83
+ # @return [Response]
84
+ #
85
+ # @api private
86
+ def invoke(request)
87
+ handler.call(request)
88
+ end
89
+
90
+ # Return a successful response
91
+ #
92
+ # @param [Request] request
93
+ # the evaluated request
94
+ #
95
+ # @param [#output] result
96
+ # the evaluation result
97
+ #
98
+ # @return [Response::Success]
99
+ #
100
+ # @api private
101
+ def on_success(request, result)
102
+ request.success(result.output)
103
+ end
104
+
105
+ # Return a failure response by invoking a failure chain
106
+ #
107
+ # @param [Request] request
108
+ # the evaluated request
109
+ #
110
+ # @param [#output] result
111
+ # the evaluation result
112
+ #
113
+ # @return [Response::Failure]
114
+ #
115
+ # @api private
116
+ def on_failure(request, result)
117
+ failure_chain.call(request.error(result.output))
118
+ end
28
119
  end # class Evaluator
29
120
  end # module Processor
30
121
  end # module Substation
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ module Substation
4
+ module Processor
5
+
6
+ # A processor that transforms output data into something else
7
+ class Transformer
8
+
9
+ include Processor::Outgoing
10
+ include Adamantium::Flat
11
+
12
+ # Transform response data into something else
13
+ #
14
+ # @param [Response] response
15
+ # the response to process
16
+ #
17
+ # @return [Response]
18
+ #
19
+ # @api private
20
+ def call(response)
21
+ respond_with(response, handler.call(response))
22
+ end
23
+
24
+ end # class Wrapper
25
+ end # module Processor
26
+ end # module Substation
@@ -1,11 +1,13 @@
1
+ # encoding: utf-8
2
+
1
3
  module Substation
2
4
  module Processor
3
5
 
4
6
  # A processor that wraps output data in a new handler instance
5
7
  class Wrapper
6
8
 
7
- include Outgoing
8
- include Concord.new(:handler)
9
+ include Processor::Outgoing
10
+ include Adamantium::Flat
9
11
 
10
12
  # Wrap response data in an instance of {#handler}
11
13
  #
@@ -16,7 +18,7 @@ module Substation
16
18
  #
17
19
  # @api private
18
20
  def call(response)
19
- respond_with(response, handler.new(response.output))
21
+ respond_with(response, handler.new(response.data))
20
22
  end
21
23
 
22
24
  end # class Wrapper
@@ -1,11 +1,20 @@
1
+ # encoding: utf-8
2
+
1
3
  module Substation
2
4
 
3
5
  # Encapsulates the application environment and an input model instance
4
6
  class Request
5
7
 
6
- include Concord.new(:env, :input)
8
+ include Concord.new(:name, :env, :input)
7
9
  include Adamantium::Flat
8
10
 
11
+ # The name of the request
12
+ #
13
+ # @return [Symbol]
14
+ #
15
+ # @api private
16
+ attr_reader :name
17
+
9
18
  # The application environment
10
19
  #
11
20
  # @example
@@ -36,6 +45,8 @@ module Substation
36
45
  # @api public
37
46
  attr_reader :input
38
47
 
48
+ alias_method :data, :input
49
+
39
50
  # Create a new successful response
40
51
  #
41
52
  # @example
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Substation
2
4
 
3
5
  # Base class for action responses
@@ -91,6 +93,8 @@ module Substation
91
93
  # @api public
92
94
  attr_reader :output
93
95
 
96
+ alias_method :data, :output
97
+
94
98
  # Initialize a new instance
95
99
  #
96
100
  # @param [Request] request
@@ -127,6 +131,15 @@ module Substation
127
131
  # @api public
128
132
  abstract_method :success?
129
133
 
134
+ # Return a {Request} instance built upon this response
135
+ #
136
+ # @return [Request]
137
+ #
138
+ # @api private
139
+ def to_request
140
+ Request.new(request.name, env, output)
141
+ end
142
+
130
143
  # An errorneous {Response}
131
144
  class Failure < self
132
145