Skip to content

Commit

Permalink
Merge pull request rack#182 from rkh/builder
Browse files Browse the repository at this point in the history
improve Rack::Builder
  • Loading branch information
josh committed May 31, 2011
2 parents 9b915c8 + e88aa4e commit 404f2f1
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 15 deletions.
38 changes: 23 additions & 15 deletions lib/rack/builder.rb
Expand Up @@ -46,13 +46,13 @@ def self.parse_file(config, opts = Server::Options.new)
return app, options
end

def initialize(&block)
@ins = []
def initialize(default_app = nil,&block)
@use, @map, @run = [], nil, default_app
instance_eval(&block) if block_given?
end

def self.app(&block)
self.new(&block).to_app
def self.app(default_app = nil, &block)
self.new(default_app, &block).to_app
end

# Specifies a middleware to use in a stack.
Expand All @@ -75,7 +75,11 @@ def self.app(&block)
# The +call+ method in this example sets an additional environment key which then can be
# referenced in the application if required.
def use(middleware, *args, &block)
@ins << lambda { |app| middleware.new(app, *args, &block) }
if @map
mapping, @map = @map, nil
@use << proc { |app| generate_map app, mapping }
end
@use << proc { |app| middleware.new(app, *args, &block) }
end

# Takes an argument that is an object that responds to #call and returns a Rack response.
Expand All @@ -93,7 +97,7 @@ def use(middleware, *args, &block)
#
# run Heartbeat
def run(app)
@ins << app #lambda { |nothing| app }
@run = app
end

# Creates a route within the application.
Expand All @@ -116,22 +120,26 @@ def run(app)
# This example includes a piece of middleware which will run before requests hit +Heartbeat+.
#
def map(path, &block)
if @ins.last.kind_of? Hash
@ins.last[path] = self.class.new(&block).to_app
else
@ins << {}
map(path, &block)
end
@map ||= {}
@map[path] = block
end

def to_app
@ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last
inner_app = @ins.last
@ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) }
app = @map ? generate_map(@run, @map) : @run
fail "missing run or map statement" unless app
@use.reverse.inject(app) { |a,e| e[a] }
end

def call(env)
to_app.call(env)
end

private

def generate_map(default_app, mapping)
mapped = default_app ? {'/' => default_app} : {}
mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b) }
URLMap.new(mapped)
end
end
end
37 changes: 37 additions & 0 deletions test/spec_builder.rb
Expand Up @@ -99,6 +99,26 @@ def self.env
Rack::MockRequest.new(app).get("/").should.be.server_error
end

it "can mix map and run for endpoints" do
app = Rack::Builder.app do
map('/sub') { run lambda { |inner_env| [200, {}, ['sub']] }}
run lambda { |inner_env| [200, {}, ['root']] }
end

Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
Rack::MockRequest.new(app).get("/sub").body.to_s.should.equal 'sub'
end

it "accepts middleware-only map blocks" do
app = Rack::Builder.app do
map('/foo') { use Rack::ShowExceptions }
run lambda { |env| raise "bzzzt" }
end

proc { Rack::MockRequest.new(app).get("/") }.should.raise(RuntimeError)
Rack::MockRequest.new(app).get("/foo").should.be.server_error
end

should "initialize apps once" do
app = Rack::Builder.new do
class AppClass
Expand All @@ -120,6 +140,23 @@ def call(env)
Rack::MockRequest.new(app).get("/").should.be.server_error
end

it "allows use after run" do
app = Rack::Builder.app do
run lambda { |env| raise "bzzzt" }
use Rack::ShowExceptions
end

Rack::MockRequest.new(app).get("/").should.be.server_error
Rack::MockRequest.new(app).get("/").should.be.server_error
Rack::MockRequest.new(app).get("/").should.be.server_error
end

it 'complains about a missing run' do
proc do
Rack::Builder.app { use Rack::ShowExceptions }
end.should.raise(RuntimeError)
end

describe "parse_file" do
def config_file(name)
File.join(File.dirname(__FILE__), 'builder', name)
Expand Down

0 comments on commit 404f2f1

Please sign in to comment.