add pattern matching to before/after filters.

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") { |slug| session[:last_slug] = slug }

Signed-off-by: Simon Rozet <simon@rozet.name>
This commit is contained in:
Konstantin Haase 2010-04-27 23:11:43 +02:00 committed by Simon Rozet
parent d3a401e66c
commit da047d3d4c
3 changed files with 96 additions and 22 deletions

View File

@ -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:

View File

@ -455,16 +455,10 @@ module Sinatra
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.
@ -597,14 +591,14 @@ module Sinatra
# 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)
@ -654,13 +648,12 @@ module Sinatra
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
@ -673,6 +666,14 @@ module Sinatra
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)
@ -781,15 +782,25 @@ module Sinatra
# 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
@ -853,11 +864,9 @@ module Sinatra
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) }
@ -875,6 +884,14 @@ module Sinatra
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

View File

@ -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
@ -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