Created
March 16, 2010 23:58
-
-
Save patmaddox/334690 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Functional test just to demonstrate that everything is hooked up | |
describe UsersController, "POST create", :type => :controller do | |
before(:each) do | |
UsersController.dispatcher = DefaultDispatcher | |
end | |
context "successful" do | |
it "should create a user" do | |
lambda { | |
post :create, :user => {:name => "Pat", :email => "[email protected]"} | |
}.should change { User.count }.by(1) | |
end | |
end | |
context "failure" do | |
it "should not create a user" do | |
lambda { | |
post :create, :user => {} | |
}.should_not change { User.count } | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Hey check it out, a really simple sexy controller unit test! | |
describe UsersController, "POST create" do | |
before(:each) do | |
@command = mock('CreateUser command', :success? => true) | |
Commands::CreateUser.stub(:new).and_return @command | |
@handler = mock('handler', :dispatch => true) | |
UsersController.dispatcher = @handler | |
end | |
it "should dispatch a CreateUser command" do | |
@handler.should_receive(:dispatch).with(@command) | |
post :create, :user => {} | |
end | |
it "should pass the params when building the CreateUser command" do | |
Commands::CreateUser.should_receive(:new).with('param1' => 'foo') | |
post :create, :user => {:param1 => 'foo'} | |
end | |
it "should HTTP 200 when the command is successful" do | |
@command.stub(:success?).and_return true | |
post :create, :user => {} | |
response.should be_success | |
end | |
it "should HTTP 400 when the command is unsuccessful" do | |
@command.stub(:success?).and_return false | |
post :create, :user => {} | |
response.code.should == '400' | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# A controller now acts as an adapter to the app's commands | |
class UsersController < ApplicationController | |
class << self | |
attr_accessor :dispatcher | |
end | |
def create | |
command = Commands::CreateUser.new params['user'] | |
dispatch command | |
if command.success? | |
render :text => "yay!", :status => 200 | |
else | |
render :text => "nay :(", :status => 400 | |
end | |
end | |
def dispatch(command) | |
self.class.dispatcher.dispatch command | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
describe Commands::CreateUser do | |
it "should be valid with a name and email" do | |
Commands::CreateUser.new(:email => '[email protected]', :name => 'Pat').should be_valid | |
end | |
it "should be invalid with a missing name" do | |
Commands::CreateUser.new(:email => '[email protected]', :name => '').should_not be_valid | |
end | |
it "should be invalid with a missing email" do | |
Commands::CreateUser.new(:email => '', :name => 'Pat').should_not be_valid | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Doesn't follow the Command pattern to the t (no execute method). Commands only know | |
# about validity. The actual execution is done by command handlers | |
module Commands | |
class CreateUser | |
attr_writer :success | |
attr_reader :name, :email | |
def initialize(options) | |
@name = options[:name] | |
@email = options[:email] | |
end | |
def valid? | |
[@name, @email].all?(&:present?) | |
end | |
def success? | |
@success | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
describe EventHandlers::CreateUserRecord do | |
it "should create a user with the name and email given by the command" do | |
command = stub('command', :name => 'Pat', :email => '[email protected]') | |
User.should_receive(:create!).with(:name => 'Pat', :email => '[email protected]') | |
EventHandlers::CreateUserRecord.new.handle command | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module EventHandlers | |
class CreateUserRecord | |
def handle(command) | |
User.create! :name => command.name, :email => command.email | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
describe CommandDispatcher do | |
class FunkyCommand | |
attr_writer :success | |
def initialize(valid=true) | |
@valid = valid | |
end | |
def valid? | |
@valid | |
end | |
def success? | |
@success | |
end | |
end | |
it "should allow handlers to be registered as anonymous blocks" do | |
dispatcher = CommandDispatcher.new | |
called = false | |
dispatcher.register(FunkyCommand) {|command| called = true } | |
dispatcher.dispatch FunkyCommand.new | |
called.should be_true | |
end | |
it "should only call the handlers registered for a dispatched command type" do | |
dispatcher = CommandDispatcher.new | |
called = false | |
dispatcher.register(FunkyCommand) {|command| called = true } | |
dispatcher.dispatch Object.new | |
called.should be_false | |
end | |
it "should not call the handler if the command is invalid" do | |
dispatcher = CommandDispatcher.new | |
called = false | |
dispatcher.register(FunkyCommand) {|command| called = true } | |
dispatcher.dispatch FunkyCommand.new(false) | |
called.should be_false | |
end | |
it "should allow a handler to be registered as a class" do | |
dispatcher = CommandDispatcher.new | |
called = false | |
handler_class = Class.new do | |
define_method(:handle) {|command| called = true } | |
end | |
dispatcher.register FunkyCommand, handler_class | |
dispatcher.dispatch FunkyCommand.new | |
called.should be_true | |
end | |
it "sets the command's success flag to true if the handler passes" do | |
dispatcher = CommandDispatcher.new | |
dispatcher.register(FunkyCommand) {|command| } | |
command = FunkyCommand.new | |
dispatcher.dispatch command | |
command.should be_success | |
end | |
it "sets the command's success flag to false if the handler errors" do | |
dispatcher = CommandDispatcher.new | |
dispatcher.register(FunkyCommand) {|command| raise "chickens" } | |
command = FunkyCommand.new | |
dispatcher.dispatch command | |
command.should_not be_success | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class CommandDispatcher | |
def register(command_class, handler_class=nil, &block) | |
@command_class = command_class | |
@block = handler_class ? lambda {|command| handler_class.new.handle(command) } : block | |
end | |
def dispatch(command) | |
if @command_class === command && command.valid? | |
begin | |
@block.call(command) | |
command.success = true | |
rescue | |
command.success = false | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class UserRegistrationApp | |
def initialize(dispatcher) | |
dispatcher.register Commands::CreateUser, EventHandlers::CreateUserRecord | |
end | |
end | |
# and then in environments/config.rb... | |
DefaultDispatcher = CommandDispatcher.new | |
UserRegistrationApp.new DefaultDispatcher | |
=begin | |
So here's what I like about this setup...first, it's wicked decoupled. The application's | |
interface is defined in terms of commands that the client can perform. The application's | |
behavior is defined by linking up commands to command handlers. The app can be run entirely | |
headless. In order to use a different UI, you just write a new adapter that converts client | |
interactions into commands, then dispatch the command. | |
Whole thing is very proof of concept, obviously. At this point it really only implements | |
the left side of the hexagonal architecture. | |
=end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment