Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce duplication with Processor::API::Responder #7

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/flay.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
---
threshold: 9
total_score: 125
total_score: 126
2 changes: 1 addition & 1 deletion config/flog.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
threshold: 13.3
threshold: 14.6
3 changes: 3 additions & 0 deletions config/reek.yml
Original file line number Diff line number Diff line change
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 All @@ -48,6 +49,8 @@ NestedIterators:
enabled: true
exclude:
- Substation::Chain::DSL::Builder#define_dsl_method
- Substation::Processor::API::Responder#define_abstract_helper_method
- Substation::Processor::API::Responder#define_api_method
max_allowed_nesting: 1
ignore_iterators: []
NilCheck:
Expand Down
2 changes: 1 addition & 1 deletion lib/substation.rb
Original file line number Diff line number Diff line change
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
83 changes: 83 additions & 0 deletions lib/substation/processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,89 @@ 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|
klass = base_response.const_get(class_name)
define_api_method(host, method_name, klass)
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 [Class] klass
# the class constructing the response
#
# @return [undefined]
#
# @api private
def define_api_method(host, method_name, klass)
host.class_eval do
define_method(method_name) do |output|
respond_with(klass, output)
end
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.class_eval do
define_method(:respond_with) do |_klass, _output|
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to use abstract_type to remove some of this.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkubb not really, as i also include that module into non-abstract classes which provide an implementation of #respond_with (Substation::Request)

msg = "#{self.class}##{__method__} must be implemented"
raise NotImplementedError, msg
end
end
end
end # module Responder

end
end # module Processor
end # module Substation
27 changes: 2 additions & 25 deletions lib/substation/processor/evaluator.rb
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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

described_class::API_METHOD_MAPPING.keys.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

described_class::API_METHOD_MAPPING.keys.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