From cbc3afb8786a9e6caa486fa2c97b17348c9eff51 Mon Sep 17 00:00:00 2001 From: Nicholas Seckar Date: Sun, 6 Aug 2006 02:51:53 +0000 Subject: Add Dispatcher.to_prepare and config.to_prepare to provide a pre-request hook. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4686 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- railties/CHANGELOG | 2 ++ railties/lib/dispatcher.rb | 47 +++++++++++++++++++++++++++++++++++--- railties/lib/initializer.rb | 7 ++++++ railties/test/dispatcher_test.rb | 49 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 63f3197fcd..74ce8c0a3a 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Add Dispatcher.to_prepare and config.to_prepare to provide a pre-request hook. [Nicholas Seckar] + * Tweak the Rails load order so observers are loaded after plugins, and reloaded in development mode. Closed #5279. [Rick Olson] * Added that you can change the web server port in config/lighttpd.conf from script/server --port/-p #5465 [mats@imediatec.co.uk] diff --git a/railties/lib/dispatcher.rb b/railties/lib/dispatcher.rb index 32f286434f..a125018446 100644 --- a/railties/lib/dispatcher.rb +++ b/railties/lib/dispatcher.rb @@ -25,6 +25,7 @@ # to the appropriate controller and action. It also takes care of resetting # the environment (when Dependencies.load? is true) after each request. class Dispatcher + class << self # Dispatch the given CGI request, using the given session options, and @@ -54,11 +55,36 @@ class Dispatcher # to restart the server (WEBrick, FastCGI, etc.). def reset_application! Dependencies.clear - ActiveRecord::Base.reset + ActiveRecord::Base.reset if defined?(ActiveRecord) Class.remove_class(*Reloadable.reloadable_classes) end + + + # Add a preparation callback. Preparation callbacks are run before every + # request in development mode, and before the first request in production + # mode. + # + # An optional identifier may be supplied for the callback. If provided, + # to_prepare may be called again with the same identifier to replace the + # existing callback. Passing an identifier is a suggested practice if the + # code adding a preparation block may be reloaded. + def to_prepare(identifier = nil, &block) + unless identifier.nil? + callback = preparation_callbacks.detect { |ident, _| ident == identifier } + if callback # Already registered: update the existing callback + callback[-1] = block + return + end + end + preparation_callbacks << [identifier, block] + nil + end private + + attr_accessor :preparation_callbacks, :preparation_callbacks_run + alias_method :preparation_callbacks_run?, :preparation_callbacks_run + # CGI.new plus exception handling. CGI#read_multipart raises EOFError # if body.empty? or body.size != Content-Length and raises ArgumentError # if Content-Length is non-integer. @@ -67,10 +93,15 @@ class Dispatcher end def prepare_application - ActionController::Routing::Routes.reload if Dependencies.load? + if Dependencies.load? + ActionController::Routing::Routes.reload + self.preparation_callbacks_run = false + end + prepare_breakpoint require_dependency('application.rb') unless Object.const_defined?(:ApplicationController) - ActiveRecord::Base.verify_active_connections! + ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord) + run_preparation_callbacks end def reset_after_dispatch @@ -87,6 +118,12 @@ class Dispatcher nil end + def run_preparation_callbacks + return if preparation_callbacks_run? + preparation_callbacks.each { |_, callback| callback.call } + self.preparation_callbacks_run = true + end + # If the block raises, send status code as a last-ditch response. def failsafe_response(output, status, exception = nil) yield @@ -117,4 +154,8 @@ class Dispatcher end end end + + self.preparation_callbacks = [] + self.preparation_callbacks_run = false + end diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb index de58051f97..d18a5ecc4d 100644 --- a/railties/lib/initializer.rb +++ b/railties/lib/initializer.rb @@ -294,6 +294,13 @@ module Rails configuration.after_initialize_block.call if configuration.after_initialize_block end + # Add a preparation callback that will run before every request in development + # mode, or before the first request in production. + # + # See Dispatcher#to_prepare. + def to_prepare(&callback) + Dispatcher.to_prepare(&callback) + end protected # Return a list of plugin paths within base_path. A plugin path is diff --git a/railties/test/dispatcher_test.rb b/railties/test/dispatcher_test.rb index 41d08e224e..8fb19b173a 100644 --- a/railties/test/dispatcher_test.rb +++ b/railties/test/dispatcher_test.rb @@ -24,6 +24,8 @@ class DispatcherTest < Test::Unit::TestCase def setup @output = StringIO.new ENV['REQUEST_METHOD'] = "GET" + Dispatcher.send(:preparation_callbacks).clear + Dispatcher.send(:preparation_callbacks_run=, false) end def teardown @@ -84,6 +86,53 @@ class DispatcherTest < Test::Unit::TestCase ensure $stdin = old_stdin end + + def test_preparation_callbacks + Object.const_set :ApplicationController, nil + old_mechanism = Dependencies.mechanism + + a = b = c = nil + Dispatcher.to_prepare { a = b = c = 1 } + Dispatcher.to_prepare { b = c = 2 } + Dispatcher.to_prepare { c = 3 } + + Dispatcher.send :prepare_application + + assert_equal 1, a + assert_equal 2, b + assert_equal 3, c + + # When mechanism is :load, perform the callbacks each request: + Dependencies.mechanism = :load + a = b = c = nil + Dispatcher.send :prepare_application + assert_equal 1, a + assert_equal 2, b + assert_equal 3, c + + # But when not :load, make sure they are only run once + a = b = c = nil + Dependencies.mechanism = :not_load + Dispatcher.send :prepare_application + assert_equal nil, a || b || c + ensure + Dependencies.mechanism = old_mechanism + Object.send :remove_const, :ApplicationController + end + + def test_to_prepare_with_identifier_replaces + Object.const_set :ApplicationController, nil + + a = b = nil + Dispatcher.to_prepare(:unique_id) { a = b = 1 } + Dispatcher.to_prepare(:unique_id) { a = 2 } + + Dispatcher.send :prepare_application + assert_equal 2, a + assert_equal nil, b + ensure + Object.send :remove_const, :ApplicationController + end private def dispatch -- cgit v1.2.3