Skip to content

Commit

Permalink
Reduce duplication with Processor::API::Responder
Browse files Browse the repository at this point in the history
  • Loading branch information
snusnu committed Jul 11, 2013
1 parent b96ed1e commit 6a3eaf3
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 68 deletions.
1 change: 1 addition & 0 deletions config/reek.yml
Expand Up @@ -39,6 +39,7 @@ LongParameterList:
- Substation::Chain::DSL::Builder#define_dsl_method
- Substation::Chain#self.failure_response
- Substation::Chain#on_failure
- Substation::Processor::API::Responder#define_api_method
max_params: 2
LongYieldList:
enabled: true
Expand Down
2 changes: 1 addition & 1 deletion lib/substation.rb
Expand Up @@ -52,8 +52,8 @@ module Substation
require 'substation/processor/evaluator'
require 'substation/processor/wrapper'
require 'substation/processor/transformer'
require 'substation/request'
require 'substation/response'
require 'substation/request'
require 'substation/action'
require 'substation/chain'
require 'substation/chain/dsl'
Expand Down
79 changes: 79 additions & 0 deletions lib/substation/processor.rb
Expand Up @@ -147,6 +147,85 @@ def success?
false
end
end

# Helps returning an api compatible result from custom Evaluator handlers
class Responder < Module

include Concord.new(:base_response)
include Adamantium::Flat

# Mapping of method names to class names
API_METHOD_MAPPING = {
:success => :Success,
:error => :Failure
}.freeze

# Hook called when module is included
#
# @param [Class|Module] host
# the object the module is being included in
#
# @return [undefined]
#
# @api private
def included(host)
define_api_methods(host)
define_abstract_helper_method(host)
end

private

# Defines public responder methods on host
#
# @param [Class, Module] host
# the object hosting this module
#
# @return [undefined]
#
# @api private
def define_api_methods(host)
API_METHOD_MAPPING.each do |method_name, class_name|
define_api_method(host, method_name, class_name)
end
end

# Defines a public responder method on host
#
# @param [Class, Module] host
# the object hosting this module
#
# @param [Symbol] method_name
# the responder method's name
#
# @param [Symbol] class_name
# the name of the class constructing the response
#
# @return [undefined]
#
# @api private
def define_api_method(host, method_name, class_name)
base = base_response # support the closure
host.define_method(method_name) do |output|
respond_with(base.const_get(class_name), output)
end
end

# Defines private abstract responder helper method on host
#
# @param [Class, Module] host
# the object hosting this module
#
# @return [undefined]
#
# @api private
def define_abstract_helper_method(host)
host.define_method(:respond_with) do |_klass, _output|
msg = "#{self.class}##{__method__} must be implemented"
raise NotImplementedError, msg
end
end
end # module Responder

end
end # module Processor
end # module Substation
27 changes: 2 additions & 25 deletions lib/substation/processor/evaluator.rb
Expand Up @@ -22,32 +22,9 @@ class Failure < self
end # class Failure
end # class Result

# Helps returning an api compatible result from custom Evaluator handlers
# Helps returning an api compatible result from custom {Data} handlers
module Handler

# Return a successful result
#
# @param [Object] output
# the data associated with the result
#
# @return [Result::Success]
#
# @api private
def success(output)
respond_with(Result::Success, output)
end

# Return an errorneous result
#
# @param [Object] output
# the data associated with the result
#
# @return [Result::Failure]
#
# @api private
def error(output)
respond_with(Result::Failure, output)
end
include API::Responder.new(Result)

private

Expand Down
43 changes: 1 addition & 42 deletions lib/substation/request.rb
Expand Up @@ -6,6 +6,7 @@ module Substation
class Request

include Concord.new(:name, :env, :input)
include Processor::API::Responder.new(Response)
include Adamantium::Flat

# The name of the request
Expand Down Expand Up @@ -47,48 +48,6 @@ class Request

alias_method :data, :input

# Create a new successful response
#
# @example
#
# class SomeUseCase
# def self.call(request)
# data = perform_use_case
# request.success(data)
# end
# end
#
# @param [Object] output
# the data associated with the response
#
# @return [Response::Success]
#
# @api public
def success(output)
respond_with(Response::Success, output)
end

# Create a new failure response
#
# @example
#
# class SomeUseCase
# def self.call(request)
# error = perform_use_case
# request.error(error)
# end
# end
#
# @param [Object] output
# the data associated with the response
#
# @return [Response::Failure]
#
# @api public
def error(output)
respond_with(Response::Failure, output)
end

private

# Instantiate an instance of +klass+ and pass +output+
Expand Down
47 changes: 47 additions & 0 deletions spec/unit/substation/processor/api/responder/included_spec.rb
@@ -0,0 +1,47 @@
# encoding: utf-8

require 'spec_helper'

describe Processor::API::Responder, '#included' do
subject { klass.new }

let(:base) { Processor::Evaluator::Result }
let(:success) { base::Success.new(output) }
let(:error) { base::Failure.new(output) }
let(:output) { mock }

context 'when private #respond_with is not implemented' do
let(:klass) do
Class.new do
include Processor::API::Responder.new(Processor::Evaluator::Result)
end
end

[ :success, :error ].each do |method_name|
it "raises NotImplementedError when calling ##{method_name}" do
msg = "#{subject.class}#respond_with must be implemented"
expect {
subject.public_send(method_name, output)
}.to raise_error(NotImplementedError, msg)
end
end
end

context 'when private #respond_with is implemented' do
let(:klass) do
Class.new do
include Processor::API::Responder.new(Processor::Evaluator::Result)
private
def respond_with(klass, output)
klass.new(output)
end
end
end

[ :success, :error ].each do |method_name|
it "defines public ##{method_name} on its host" do
expect(subject.public_send(method_name, output)).to eql(send(method_name))
end
end
end
end

0 comments on commit 6a3eaf3

Please sign in to comment.