Permalink
Browse files

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>
  • Loading branch information...
rkh authored and sr committed Apr 27, 2010
1 parent d3a401e commit da047d3d4c06cbc53b15e5382cad13b8559c6582
Showing with 96 additions and 22 deletions.
  1. +11 −0 README.rdoc
  2. +39 −22 lib/sinatra/base.rb
  3. +46 −0 test/filter_test.rb
View
@@ -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:
View
@@ -455,16 +455,10 @@ def forward
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 @@ def invoke(&block)
# 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 @@ def dump_errors!(boom)
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 @@ def reset!
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 @@ def mime_type(type, value=nil)
# 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 @@ def route(verb, path, options={}, &block)
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 @@ def invoke_hook(name, *args)
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
View
@@ -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

5 comments on commit da047d3

That's awesome, thanks!

Owner

rkh replied May 4, 2010

Yay, thanks sr!

Owner

rkh replied May 4, 2010

Deleting comments does not work, yay!

Contributor

DAddYE replied May 4, 2010

Congrats!

This looks awesome!

Please sign in to comment.