From da047d3d4c06cbc53b15e5382cad13b8559c6582 Mon Sep 17 00:00:00 2001 From: Konstantin Haase Date: Tue, 27 Apr 2010 23:11:43 +0200 Subject: [PATCH] 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 --- README.rdoc | 11 ++++++++ lib/sinatra/base.rb | 61 +++++++++++++++++++++++++++++---------------- test/filter_test.rb | 46 ++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 22 deletions(-) diff --git a/README.rdoc b/README.rdoc index 278cf41d..e3932f6f 100644 --- a/README.rdoc +++ b/README.rdoc @@ -331,6 +331,17 @@ set in before filters and routes are accessible by after filters: puts response.status 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 To immediately stop a request within a filter or route use: diff --git a/lib/sinatra/base.rb b/lib/sinatra/base.rb index f28376f9..f4a3400d 100644 --- a/lib/sinatra/base.rb +++ b/lib/sinatra/base.rb @@ -455,16 +455,10 @@ module Sinatra end private - # Run before filters defined on the class and all superclasses. - def before_filter!(base=self.class) - before_filter!(base.superclass) if base.superclass.respond_to?(:before_filters) - base.before_filters.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) } + # Run filters defined on the class and all superclasses. + def filter!(type, base = self.class) + filter! type, base.superclass if base.superclass.respond_to?(:filters) + base.filters[type].each { |block| instance_eval(&block) } end # Run routes defined on the class and all superclasses. @@ -597,14 +591,14 @@ module Sinatra # Dispatch a request with error handling. def dispatch! static! if settings.static? && (request.get? || request.head?) - before_filter! + filter! :before route! rescue NotFound => boom handle_not_found!(boom) rescue ::Exception => boom handle_exception!(boom) ensure - after_filter! unless env['sinatra.static_file'] + filter! :after unless env['sinatra.static_file'] end def handle_not_found!(boom) @@ -654,13 +648,12 @@ module Sinatra end class << self - attr_reader :routes, :before_filters, :after_filters, :templates, :errors + attr_reader :routes, :filters, :templates, :errors def reset! @conditions = [] @routes = {} - @before_filters = [] - @after_filters = [] + @filters = {:before => [], :after => []} @errors = {} @middleware = [] @prototype = nil @@ -673,6 +666,14 @@ module Sinatra end end + def before_filters + @filters[:before] + end + + def after_filters + @filters[:after] + end + # Extension modules registered on this class and all superclasses. def extensions if superclass.respond_to?(:extensions) @@ -781,15 +782,25 @@ module Sinatra # Define a before filter; runs before all requests within the same # context as route handlers and may access/modify the request and # response. - def before(&block) - @before_filters << block + def before(path = nil, &block) + add_filter(:before, path, &block) end # Define an after filter; runs after all requests within the same # context as route handlers and may access/modify the request and # response. - def after(&block) - @after_filters << block + def after(path = nil, &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 # 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)} - pattern, keys = compile(path) + unbound_method, pattern, keys = compile!(verb, path, &block) conditions, @conditions = @conditions, [] - define_method "#{verb} #{path}", &block - unbound_method = instance_method("#{verb} #{path}") block = if block.arity != 0 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) } 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) keys = [] if path.respond_to? :to_str diff --git a/test/filter_test.rb b/test/filter_test.rb index b4bbda1a..4689913c 100644 --- a/test/filter_test.rb +++ b/test/filter_test.rb @@ -121,6 +121,29 @@ class BeforeFilterTest < Test::Unit::TestCase assert_equal File.read(__FILE__), body assert !ran_filter 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 class AfterFilterTest < Test::Unit::TestCase @@ -218,4 +241,27 @@ class AfterFilterTest < Test::Unit::TestCase assert_equal File.read(__FILE__), body assert !ran_filter 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