Skip to content

Commit

Permalink
Simplify and power up failure chain (re)definition
Browse files Browse the repository at this point in the history
  • Loading branch information
snusnu committed Jul 8, 2013
1 parent e5d3d84 commit 3f88c64
Show file tree
Hide file tree
Showing 33 changed files with 317 additions and 310 deletions.
67 changes: 42 additions & 25 deletions Changelog.md
Expand Up @@ -9,37 +9,54 @@

* [feature] Make the dispatched name available in `Request#name`.

* [feature] Support definining failure chains for `Substation::Processor::Evaluator::*` processors.
* [feature] Support (re)definining failure chains for `Substation::Processor::Fallible` processors.

env = Substation::Environment.build do
register :validate, Substation::Processor::Evaluator::Data
register :call, Substation::Processor::Evaluator::Pivot
end

class Error
attr_reader :data
def initialize(data)
@data = data
module Demo
ENV = Substation::Environment.build do
register :validate, Substation::Processor::Evaluator::Data
register :call, Substation::Processor::Evaluator::Pivot
end

ValidationError = Class.new(self)
InternalError = Class.new(self)
end
class Error
attr_reader :data
def initialize(data)
@data = data
end

chain = env.chain do
validate(Vanguard::Validator) { wrap Error::ValidationError }
call(Some::Action) { wrap Error::InternalError }
end
ValidationError = Class.new(self)
InternalError = Class.new(self)
end

module App
VALIDATION_ERROR = Demo::ENV.chain { wrap Error::ValidationError }
INTERNAL_ERROR = Demo::ENV.chain { wrap Error::InternalError }

env = Object.new
name = :some_name
invalid_request = Substation::Request.new(name, env, :invalid)
response = chain.call(invalid_request)
SOME_ACTION = Demo::ENV.chain do
validate Vanguard::Validator, VALIDATION_ERROR
call Some::Action, INTERNAL_ERROR
end
end

response.data.instance_of?(Errors::ValidationError)
# => true
response.data.data
# => the actual vanguard violation set
module Web
VALIDATION_ERROR = Demo::ENV.chain(App::VALIDATION_ERROR) do
render Renderer::ValidationError
end

INTERNAL_ERROR = Demo::ENV.chain(App::INTERNAL_ERROR) do
render Renderer::InternalError
end

# in case of success, returns an instance of Views::Person
# in case of validation failure, renders using Renderer::ValidationError
# in case of internal error, renders using Renderer::InternalError
SOME_ACTION = Demo::ENV.chain(App::SOME_ACTION) do
failure_chain :validate, VALIDATION_ERROR
failure_chain :call, INTERNAL_ERROR
wrap Presenters::Person
wrap Views::ShowPerson
end
end
end

[Compare v0.0.8..master](https://github.com/snusnu/substation/compare/v0.0.8...master)

Expand Down
2 changes: 1 addition & 1 deletion config/flay.yml
@@ -1,3 +1,3 @@
---
threshold: 6
total_score: 100
total_score: 107
2 changes: 1 addition & 1 deletion config/flog.yml
@@ -1,2 +1,2 @@
---
threshold: 19.5
threshold: 13.3
3 changes: 3 additions & 0 deletions lib/substation.rb
Expand Up @@ -35,6 +35,9 @@ module Substation
# An empty frozen array useful for (default) parameters
EMPTY_ARRAY = [].freeze

# Error raised when trying to access an unknown processor
UnknownProcessor = Class.new(StandardError)

end

require 'substation/utils'
Expand Down
91 changes: 52 additions & 39 deletions lib/substation/chain/dsl.rb
Expand Up @@ -70,10 +70,7 @@ def compile_dsl
# @api private
def define_dsl_method(name, processor, dsl)
dsl.class_eval do
define_method(name) do |*args, &block|
failure_chain = self.class.build(processor[:block], block)
use(processor[:class].new(failure_chain, *args))
end
define_method(name) { |*args| use(processor.new(name, *args)) }
end
end

Expand Down Expand Up @@ -101,41 +98,6 @@ def self.processors(chain, &block)
new(chain, &block).processors
end

# The substation environment used to build chains
#
# @return [Environment]
#
# @api private
# attr_reader :env

# Build a new chain based on all +blocks+
#
# @param [Proc] *blocks
# any number of blocks to instance_eval inside a new instance
#
# @return [Chain]
#
# @api private
def self.build(*blocks)
Chain.new(coerce(*blocks).processors)
end

# Coerce an array of blocks into a new instance
#
# @param [Proc] *blocks
# any number of blocks to instance_eval inside the new instance
#
# @return [DSL]
#
# @api private
def self.coerce(*blocks)
blocks.compact.inject(new(EMPTY_ARRAY)) { |dsl, block|
dsl.instance_eval(&block)
}
end

private_class_method :coerce

# Initialize a new instance
#
# @param [#each<#call>] processors
Expand Down Expand Up @@ -179,6 +141,57 @@ def chain(other)
self
end

# Use +chain+ as the failure chain for the processor identified by +name+
#
# @param [Symbol] name
# the processor's name
#
# @param [#call] chain
# the failure chain to use for the processor identified by +name+
#
# @return [self]
#
# @api private
def failure_chain(name, chain)
replace_processor(processor(name), chain)
self
end

private

# Return the processor identified by +name+
#
# @param [Symbol] name
# the processor's name
#
# @return [#call]
# the processor identified by +name+
#
# @return [nil]
# if no processor identified by +name+ is registered
#
# @api private
def processor(name)
processor = @processors.detect { |processor| processor.name == name }

This comment has been minimized.

Copy link
@dkubb

dkubb Jul 9, 2013

What about:

def processor(name)
  @processors.detect { |processor| processor.name == name } or
    raise UnknownProcessor, "No processor named #{name.inspect} is registered"
end
unless processor
raise UnknownProcessor, "No processor named #{name.inspect} is registered"
end
processor
end

# Replace +processor+'s failure chain with +chain+
#
# @param [#call] processor
# @param [#call] chain
#
# @return [undefined]
#
# @api private
def replace_processor(processor, chain)
index = @processors.index(processor)
@processors.delete_at(index)
@processors.insert(index, processor.with_failure_chain(chain))

This comment has been minimized.

Copy link
@dkubb

dkubb Jul 8, 2013

What about:

def replace_processor(processor, chain)
  index = @processors.index(processor)
  @processors[index] = processor.with_failure_chain(chain)
end

This comment has been minimized.

Copy link
@snusnu

snusnu Jul 9, 2013

Author Owner

@dkubb thx for your comments! i really needed those ;) see d2bd081

end
end # class DSL
end # class Chain
end # module Substation
4 changes: 2 additions & 2 deletions lib/substation/environment/dsl.rb
Expand Up @@ -51,8 +51,8 @@ def initialize(&block)
# @return [self]
#
# @api private
def register(name, processor, &block)
@registry[name.to_sym] = { :class => processor, :block => block }
def register(name, processor)
@registry[name.to_sym] = processor
self
end

Expand Down
28 changes: 27 additions & 1 deletion lib/substation/processor.rb
Expand Up @@ -3,7 +3,14 @@ module Substation
# Namespace for chain processors
module Processor

include Concord.new(:failure_chain, :handler)
include Concord.new(:name, :handler)

# This processor's name
#
# @return [Symbol]
#
# @api private
attr_reader :name

# Test wether chain processing should continue
#
Expand Down Expand Up @@ -31,8 +38,26 @@ def result(response)
response
end

module Fallible
include Concord.new(:name, :handler, :failure_chain)

# Return a new processor with +chain+ as failure_chain
#
# @param [#call] chain
# the failure chain to use for the new processor
#
# @return [#call]
#
# @api private
def with_failure_chain(chain)
self.class.new(name, handler, chain)
end
end

module Incoming
include Processor
include Fallible


# The request passed on to the next processor in a {Chain}
#
Expand All @@ -50,6 +75,7 @@ def result(_response)

module Pivot
include Processor
include Fallible
end

module Outgoing
Expand Down
43 changes: 22 additions & 21 deletions spec/spec_helper.rb
Expand Up @@ -2,6 +2,25 @@

require 'concord' # makes spec setup easier

if ENV['COVERAGE'] == 'true'
require 'simplecov'
require 'coveralls'

SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
SimpleCov::Formatter::HTMLFormatter,
Coveralls::SimpleCov::Formatter
]

SimpleCov.start do
command_name 'spec:unit'
add_filter 'config'
add_filter 'spec'
minimum_coverage 100
end
end

require 'substation'

module Spec

def self.response_data
Expand All @@ -28,7 +47,8 @@ def self.call(request)
end

class Processor
include Concord::Public.new(:failure_chain, :handler)
include Substation::Processor::Fallible
attr_reader :name
end

class Presenter
Expand Down Expand Up @@ -84,29 +104,10 @@ def call(request)

FAKE_HANDLER = Object.new
FAKE_ENV = Object.new
FAKE_PROCESSOR = Processor.new(FAKE_ENV, FAKE_HANDLER)
FAKE_PROCESSOR = Processor.new(:test, FAKE_HANDLER, [])

end

if ENV['COVERAGE'] == 'true'
require 'simplecov'
require 'coveralls'

SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
SimpleCov::Formatter::HTMLFormatter,
Coveralls::SimpleCov::Formatter
]

SimpleCov.start do
command_name 'spec:unit'
add_filter 'config'
add_filter 'spec'
minimum_coverage 100
end
end

require 'substation'

include Substation

RSpec.configure do |config|
Expand Down

0 comments on commit 3f88c64

Please sign in to comment.