Skip to content

Commit

Permalink
add pattern matching to before/after filters.
Browse files Browse the repository at this point in the history
Filter now optionally take a pattern, causing them to be evaluated only if the request
path matches that pattern:

  before("/protected/*") { authenticate! }
  after("/create/:slug") { session[:last_slug] = slug }
  • Loading branch information
rkh committed Apr 27, 2010
1 parent d3a401e commit 7575b96
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 22 deletions.
11 changes: 11 additions & 0 deletions README.rdoc
Expand Up @@ -331,6 +331,17 @@ set in before filters and routes are accessible by after filters:
puts response.status puts response.status
end end


Filters optionally taking a pattern, causing them to be evaluated only if the request
path matches that pattern:

before '/protected/*' do
authenticate!
end

after '/create/:slug' do |slug|
session[:last_slug] = slug
end

== Halting == Halting


To immediately stop a request within a filter or route use: To immediately stop a request within a filter or route use:
Expand Down
61 changes: 39 additions & 22 deletions lib/sinatra/base.rb
Expand Up @@ -455,16 +455,10 @@ def forward
end end


private private
# Run before filters defined on the class and all superclasses. # Run filters defined on the class and all superclasses.
def before_filter!(base=self.class) def filter!(type, base = self.class)
before_filter!(base.superclass) if base.superclass.respond_to?(:before_filters) filter! type, base.superclass if base.superclass.respond_to?(:filters)
base.before_filters.each { |block| instance_eval(&block) } base.filters[type].each { |block| instance_eval(&block) }
end

# Run after filters defined on the class and all superclasses.
def after_filter!(base=self.class)
after_filter!(base.superclass) if base.superclass.respond_to?(:after_filters)
base.after_filters.each { |block| instance_eval(&block) }
end end


# Run routes defined on the class and all superclasses. # Run routes defined on the class and all superclasses.
Expand Down Expand Up @@ -597,14 +591,14 @@ def invoke(&block)
# Dispatch a request with error handling. # Dispatch a request with error handling.
def dispatch! def dispatch!
static! if settings.static? && (request.get? || request.head?) static! if settings.static? && (request.get? || request.head?)
before_filter! filter! :before
route! route!
rescue NotFound => boom rescue NotFound => boom
handle_not_found!(boom) handle_not_found!(boom)
rescue ::Exception => boom rescue ::Exception => boom
handle_exception!(boom) handle_exception!(boom)
ensure ensure
after_filter! unless env['sinatra.static_file'] filter! :after unless env['sinatra.static_file']
end end


def handle_not_found!(boom) def handle_not_found!(boom)
Expand Down Expand Up @@ -654,13 +648,12 @@ def dump_errors!(boom)
end end


class << self class << self
attr_reader :routes, :before_filters, :after_filters, :templates, :errors attr_reader :routes, :filters, :templates, :errors


def reset! def reset!
@conditions = [] @conditions = []
@routes = {} @routes = {}
@before_filters = [] @filters = {:before => [], :after => []}
@after_filters = []
@errors = {} @errors = {}
@middleware = [] @middleware = []
@prototype = nil @prototype = nil
Expand All @@ -673,6 +666,14 @@ def reset!
end end
end end


def before_filters
@filters[:before]
end

def after_filters
@filters[:after]
end

# Extension modules registered on this class and all superclasses. # Extension modules registered on this class and all superclasses.
def extensions def extensions
if superclass.respond_to?(:extensions) if superclass.respond_to?(:extensions)
Expand Down Expand Up @@ -781,15 +782,25 @@ def mime_type(type, value=nil)
# Define a before filter; runs before all requests within the same # Define a before filter; runs before all requests within the same
# context as route handlers and may access/modify the request and # context as route handlers and may access/modify the request and
# response. # response.
def before(&block) def before(path = nil, &block)
@before_filters << block add_filter(:before, path, &block)
end end


# Define an after filter; runs after all requests within the same # Define an after filter; runs after all requests within the same
# context as route handlers and may access/modify the request and # context as route handlers and may access/modify the request and
# response. # response.
def after(&block) def after(path = nil, &block)
@after_filters << block add_filter(:after, path, &block)
end

# add a filter
def add_filter(type, path = nil, &block)
return filters[type] << block unless path
unbound_method, pattern = compile!(type, path, &block)
add_filter(type) do
next unless match = pattern.match(request.path_info)
unbound_method.bind(self).call(*match.captures.to_a)
end
end end


# Add a route condition. The route is considered non-matching when the # Add a route condition. The route is considered non-matching when the
Expand Down Expand Up @@ -853,11 +864,9 @@ def route(verb, path, options={}, &block)


options.each {|option, args| send(option, *args)} options.each {|option, args| send(option, *args)}


pattern, keys = compile(path) unbound_method, pattern, keys = compile!(verb, path, &block)
conditions, @conditions = @conditions, [] conditions, @conditions = @conditions, []


define_method "#{verb} #{path}", &block
unbound_method = instance_method("#{verb} #{path}")
block = block =
if block.arity != 0 if block.arity != 0
proc { unbound_method.bind(self).call(*@block_params) } proc { unbound_method.bind(self).call(*@block_params) }
Expand All @@ -875,6 +884,14 @@ def invoke_hook(name, *args)
extensions.each { |e| e.send(name, *args) if e.respond_to?(name) } extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
end end


def compile!(verb, path, &block)
method_name = "#{verb} #{path}"
define_method(method_name, &block)
unbound_method = instance_method method_name
remove_method method_name
[unbound_method, *compile(path)]
end

def compile(path) def compile(path)
keys = [] keys = []
if path.respond_to? :to_str if path.respond_to? :to_str
Expand Down
46 changes: 46 additions & 0 deletions test/filter_test.rb
Expand Up @@ -121,6 +121,29 @@ class BeforeFilterTest < Test::Unit::TestCase
assert_equal File.read(__FILE__), body assert_equal File.read(__FILE__), body
assert !ran_filter assert !ran_filter
end end

it 'takes an optional route pattern' do
ran_filter = false
mock_app do
before("/b*") { ran_filter = true }
get('/foo') { }
get('/bar') { }
end
get '/foo'
assert !ran_filter
get '/bar'
assert ran_filter
end

it 'generates block arguments from route pattern' do
subpath = nil
mock_app do
before("/foo/:sub") { |s| subpath = s }
get('/foo/*') { }
end
get '/foo/bar'
assert_equal subpath, 'bar'
end
end end


class AfterFilterTest < Test::Unit::TestCase class AfterFilterTest < Test::Unit::TestCase
Expand Down Expand Up @@ -218,4 +241,27 @@ class AfterFilterTest < Test::Unit::TestCase
assert_equal File.read(__FILE__), body assert_equal File.read(__FILE__), body
assert !ran_filter assert !ran_filter
end end

it 'takes an optional route pattern' do
ran_filter = false
mock_app do
after("/b*") { ran_filter = true }
get('/foo') { }
get('/bar') { }
end
get '/foo'
assert !ran_filter
get '/bar'
assert ran_filter
end

it 'generates block arguments from route pattern' do
subpath = nil
mock_app do
after("/foo/:sub") { |s| subpath = s }
get('/foo/*') { }
end
get '/foo/bar'
assert_equal subpath, 'bar'
end
end end

0 comments on commit 7575b96

Please sign in to comment.