Skip to content

Commit

Permalink
Support building a dispatcher with a simple DSL
Browse files Browse the repository at this point in the history
Building a dispatcher this way will raise
Substation::AlreadyRegisteredError if any given
name is already registered.

SUBWAY = Substation::Environment.build { ... }
CHAIN  = SUBWAY.chain { ... }
env    = Object.new

dispatcher = SUBWAY.dispatcher(env) do
  dispatch :create_person, CHAIN
end

OR (if no substation environment is available)

dispatcher = Substation::Dispatcher.build(env) do
  dispatch :create_person, CHAIN
end
  • Loading branch information
snusnu committed Jul 11, 2013
1 parent 02822d8 commit b88267a
Show file tree
Hide file tree
Showing 13 changed files with 210 additions and 27 deletions.
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: 105
total_score: 117
2 changes: 2 additions & 0 deletions config/reek.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ TooManyStatements:
exclude:
- Substation::Chain#call
- Substation::Processor::Evaluator#call
- Substation::Dispatcher::DSL#dispatch
max_statements: 3
UncommunicativeMethodName:
enabled: true
Expand Down Expand Up @@ -115,4 +116,5 @@ UtilityFunction:
- Substation::Processor::Evaluator#on_success # inside a method object
- Substation::Processor#success? # in a module to be included in a method object
- Substation::Environment#action # DSL method
- Substation::Environment#dispatcher # DSL method
max_helper_calls: 0
4 changes: 4 additions & 0 deletions lib/substation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ module Substation
# Raised when trying to dispatch to an unregistered action
UnknownActionError = Class.new(StandardError)

# Raised when a callable is already registered under the a given name
AlreadyRegisteredError = Class.new(StandardError)

end

require 'substation/request'
Expand All @@ -58,3 +61,4 @@ module Substation
require 'substation/environment'
require 'substation/environment/dsl'
require 'substation/dispatcher'
require 'substation/dispatcher/dsl'
2 changes: 1 addition & 1 deletion lib/substation/chain/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ def detect(name)
#
# @api private
def raise_unknown_processor(name)
raise UnknownProcessor, UNKNOWN_PROCESSOR_MSG % (name.inspect)
raise UnknownProcessor, UNKNOWN_PROCESSOR_MSG % name.inspect
end

end # class DSL
Expand Down
15 changes: 15 additions & 0 deletions lib/substation/dispatcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ module Substation
# application use case.
class Dispatcher

# Build a new instance
#
# @param [Object] env
# the application environment
#
# @param [Proc] block
# a block to be instance_eval'ed inside {DSL}
#
# @return [Dispatcher]
#
# @api private
def self.build(env, &block)
new(DSL.new(&block).dispatch_table, env)
end

include Concord.new(:actions, :env)
include Adamantium::Flat

Expand Down
74 changes: 74 additions & 0 deletions lib/substation/dispatcher/dsl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# encoding: utf-8

module Substation
class Dispatcher

# Class for supporting dispatch table construction
class DSL

ALREADY_REGISTERED_MSG = '%s is already registered'.freeze

include Equalizer.new(:dispatch_table)

# The dispatch table used to build a {Dispatcher} instance
#
# @return [Hash<Symbol, #call>]
#
# @api private
attr_reader :dispatch_table

# Initialize a new instance
#
# @param [Proc] block
# a block to be instance_eval'd
#
# @return [undefined]
#
# @api private
def initialize(&block)
@dispatch_table = {}
instance_eval(&block) if block
end

# Register the given +callable+ under +name+
#
# @param [#to_sym] name
# the name to use for dispatching
#
# @param [#call] callable
# the object to call when dispatching +name+
#
# @return [self]
#
# @raise [AlreadyRegisteredError]
# if +callable+ is already registered under +name+
#
# @api private
def dispatch(name, callable)
coerced_name = name.to_sym
raise_if_already_registered(coerced_name)
dispatch_table[coerced_name] = callable
self
end

private

# Raise if +name+ is already registered in {#dispatch_table}
#
# @param [Symbol] name
# the name to test
#
# @raise [AlreadyRegisteredError]
#
# @return [undefined]
#
# @api private
def raise_if_already_registered(name)
if dispatch_table.key?(name)
raise AlreadyRegisteredError, ALREADY_REGISTERED_MSG % name.inspect
end
end

end # class DSL
end # class Dispatcher
end # module Substation
18 changes: 18 additions & 0 deletions lib/substation/environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ def action(handler, observers = EMPTY_ARRAY)
Action.new(handler, observers)
end

# Build a new {Dispatcher} instance
#
# @see Dispatcher.build
#
# @param [Object] env
# the application environment
#
# @param [Proc] block
# a block to be instance_eval'd inside a {Dispatcher::DSL}
# instance
#
# @return [Dispatcher]
#
# @api private
def dispatcher(env, &block)
Dispatcher.build(env, &block)
end

protected

# The registry used by this {Environment}
Expand Down
31 changes: 8 additions & 23 deletions spec/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ module App

end

APP_ENV = Object.new

module Web

SANITIZATION_ERROR = Demo::ENV.chain { wrap Error::SanitizationError }
Expand Down Expand Up @@ -330,29 +332,12 @@ module HTML
wrap Views::Person
end

end # module HTML

end

ACTIONS = {
:HTML => {
:create_person => Web::HTML::CREATE_PERSON,
}
}.freeze

APP_ENV = Object.new

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

APP = new(Substation::Dispatcher.new(ACTIONS[:HTML], APP_ENV))

def call(name, input = nil)
dispatcher.call(name, input)
end
# The application
APP = Demo::ENV.dispatcher(APP_ENV) do
dispatch :create_person, CREATE_PERSON
end

def action_names
dispatcher.action_names
end
end # module HTML

end # module Web
end # class Demo
4 changes: 2 additions & 2 deletions spec/integration/app_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require 'app'

describe 'a typical substation application' do
subject { Demo::APP.call(name, input) }
subject { Demo::Web::HTML::APP.call(name, input) }

let(:request) { Substation::Request.new(name, env, input) }
let(:name) { :create_person }
Expand Down Expand Up @@ -51,7 +51,7 @@
let(:input) { mock }

it 'lists all the registered names' do
expect(Demo::APP.action_names).to eql(Set[ :create_person ])
expect(Demo::Web::HTML::APP.action_names).to eql(Set[ :create_person ])
end
end

Expand Down
22 changes: 22 additions & 0 deletions spec/unit/substation/dispatcher/class_methods/build_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# encoding: utf-8

require 'spec_helper'

describe Dispatcher, '.build' do

let(:env) { mock }

context 'when no block is given' do
subject { described_class.build(env) }

it { should eql(Dispatcher.new({}, env)) }
end

context 'when a block is given' do
subject { described_class.build(env, &block) }

let(:block) { ->(_) { dispatch :test, Chain::EMPTY } }

it { should eql(Dispatcher.new({ :test => Chain::EMPTY }, env)) }
end
end
26 changes: 26 additions & 0 deletions spec/unit/substation/dispatcher/dsl/dispatch_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# encoding: utf-8

require 'spec_helper'

describe Dispatcher::DSL, '#dispatch' do
subject { object.dispatch(name, callable) }

let(:object) { described_class.new }
let(:callable) { mock }
let(:name) { mock(:to_sym => coerced_name) }
let(:coerced_name) { mock }

context 'when name is not yet registered' do
it_behaves_like 'a command method'

its(:dispatch_table) { should eql(coerced_name => callable) }
end

context 'when name is already registered' do
let(:msg) { "#{coerced_name.inspect} is already registered" }

before { object.dispatch(name, callable) }

specify { expect { subject }.to raise_error(AlreadyRegisteredError, msg) }
end
end
12 changes: 12 additions & 0 deletions spec/unit/substation/dispatcher/dsl/dispatch_table_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# encoding: utf-8

require 'spec_helper'

describe Dispatcher::DSL, '#dispatch_table' do
subject { object.dispatch_table }

let(:object) { described_class.new(&block) }
let(:block) { ->(_) { dispatch :test, Chain::EMPTY } }

it { should eql({ :test => Chain::EMPTY }) }
end
25 changes: 25 additions & 0 deletions spec/unit/substation/environment/dispatcher_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# encoding: utf-8

require 'spec_helper'

describe Environment, '#dispatcher' do

let(:object) { described_class.new(registry, chain_dsl) }
let(:registry) { mock }
let(:chain_dsl) { mock }
let(:env) { mock }

context 'when no block is given' do
subject { object.dispatcher(env) }

it { should eql(Dispatcher.new({}, env)) }
end

context 'when a block is given' do
subject { object.dispatcher(env, &block) }

let(:block) { ->(_) { dispatch :test, Chain::EMPTY } }

it { should eql(Dispatcher.new({ :test => Chain::EMPTY }, env)) }
end
end

0 comments on commit b88267a

Please sign in to comment.