From 4e50ddbc79e5bc37f361b429775c82e687e907ac Mon Sep 17 00:00:00 2001 From: Jimmy Schementi Date: Sun, 21 Dec 2008 18:36:14 -0800 Subject: [PATCH] Adds after filters Originally by jschementi (http://bit.ly/1RTt2H) Updated for Sinatra 1.0 by rtomayko --- README.rdoc | 10 ++++- lib/sinatra/base.rb | 54 +++++++++++++++++---------- test/filter_test.rb | 90 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 131 insertions(+), 23 deletions(-) diff --git a/README.rdoc b/README.rdoc index d50fc436..5a37196d 100644 --- a/README.rdoc +++ b/README.rdoc @@ -297,9 +297,17 @@ filters are accessible by routes and templates: params[:splat] #=> 'bar/baz' end +After filter are evaluated after each request within the context of the +require and can also modify the request and response. Instance variables +set in before filters and routes are accessible by after filters: + + after do + puts response.status + end + == Halting -To immediately stop a request during a before filter or route use: +To immediately stop a request within a filter or route use: halt diff --git a/lib/sinatra/base.rb b/lib/sinatra/base.rb index a0520c21..010c522b 100644 --- a/lib/sinatra/base.rb +++ b/lib/sinatra/base.rb @@ -65,7 +65,7 @@ module Sinatra def code ; 404 ; end end - # Methods available to routes, before filters, and views. + # Methods available to routes, before/after filters, and views. module Helpers # Set or retrieve the response status code. def status(value=nil) @@ -432,9 +432,15 @@ module Sinatra private # Run before filters defined on the class and all superclasses. - def filter!(base=self.class) - filter!(base.superclass) if base.superclass.respond_to?(:filters) - base.filters.each { |block| instance_eval(&block) } + 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) } end # Run routes defined on the class and all superclasses. @@ -564,12 +570,14 @@ module Sinatra # Dispatch a request with error handling. def dispatch! static! if settings.static? && (request.get? || request.head?) - filter! + before_filter! route! rescue NotFound => boom handle_not_found!(boom) rescue ::Exception => boom handle_exception!(boom) + ensure + after_filter! end def handle_not_found!(boom) @@ -623,16 +631,17 @@ module Sinatra end class << self - attr_reader :routes, :filters, :templates, :errors + attr_reader :routes, :before_filters, :after_filters, :templates, :errors def reset! - @conditions = [] - @routes = {} - @filters = [] - @errors = {} - @middleware = [] - @prototype = nil - @extensions = [] + @conditions = [] + @routes = {} + @before_filters = [] + @after_filters = [] + @errors = {} + @middleware = [] + @prototype = nil + @extensions = [] if superclass.respond_to?(:templates) @templates = Hash.new { |hash,key| superclass.templates[key] } @@ -748,11 +757,18 @@ module Sinatra Rack::Mime::MIME_TYPES[type] = value end - # Define a before filter. Filters are run before all requests - # within the same context as route handlers and may access/modify the - # request and response. + # 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) - @filters << block + @before_filters << 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 end # Add a route condition. The route is considered non-matching when the @@ -1098,8 +1114,8 @@ module Sinatra end end - delegate :get, :put, :post, :delete, :head, :template, :layout, :before, - :error, :not_found, :configure, :set, :mime_type, + delegate :get, :put, :post, :delete, :head, :template, :layout, + :before, :after, :error, :not_found, :configure, :set, :mime_type, :enable, :disable, :use, :development?, :test?, :production?, :use_in_file_templates!, :helpers end diff --git a/test/filter_test.rb b/test/filter_test.rb index dc13f585..33f36b6d 100644 --- a/test/filter_test.rb +++ b/test/filter_test.rb @@ -1,6 +1,6 @@ require File.dirname(__FILE__) + '/helper' -class FilterTest < Test::Unit::TestCase +class BeforeFilterTest < Test::Unit::TestCase it "executes filters in the order defined" do count = 0 mock_app do @@ -21,7 +21,7 @@ class FilterTest < Test::Unit::TestCase assert_equal 'Hello World', body end - it "allows filters to modify the request" do + it "can modify the request" do mock_app { get('/foo') { 'foo' } get('/bar') { 'bar' } @@ -44,7 +44,7 @@ class FilterTest < Test::Unit::TestCase assert_equal 'bar', body end - it "allows redirects in filters" do + it "allows redirects" do mock_app { before { redirect '/bar' } get('/foo') do @@ -109,3 +109,87 @@ class FilterTest < Test::Unit::TestCase assert_equal 'hello from superclass', body end end + +class AfterFilterTest < Test::Unit::TestCase + it "executes filters in the order defined" do + invoked = 0 + mock_app do + before { invoked = 2 } + get('/') { invoked += 2 } + after { invoked *= 2 } + end + + get '/' + assert ok? + + assert_equal 8, invoked + end + + it "executes filters in the order defined" do + count = 0 + mock_app do + get('/') { 'Hello World' } + after { + assert_equal 0, count + count = 1 + } + after { + assert_equal 1, count + count = 2 + } + end + + get '/' + assert ok? + assert_equal 2, count + assert_equal 'Hello World', body + end + + it "allows redirects" do + mock_app { + get('/foo') { 'ORLY' } + after { redirect '/bar' } + } + + get '/foo' + assert redirect? + assert_equal '/bar', response['Location'] + assert_equal '', body + end + + it "does not modify the response with its return value" do + mock_app { + get('/foo') { 'cool' } + after { 'Hello World!' } + } + + get '/foo' + assert ok? + assert_equal 'cool', body + end + + it "does modify the response with halt" do + mock_app { + get '/foo' do + "should not be returned" + end + after { halt 302, 'Hi' } + } + + get '/foo' + assert_equal 302, response.status + assert_equal 'Hi', body + end + + it "runs filters defined in superclasses" do + count = 2 + base = Class.new(Sinatra::Base) + base.after { count *= 2 } + mock_app(base) { + get('/foo') { count += 2 } + } + + get '/foo' + assert_equal 8, count + end +end