From 9cfeefb637b603ce41d3019c8baa95ea984620d7 Mon Sep 17 00:00:00 2001 From: wycats Date: Sat, 15 May 2010 06:08:55 -0700 Subject: Reorganized initializers a bit to enable better hooks for common cases without the need for Railtie. Specifically, the following hooks were added: * before_configuration: this hook is run immediately after the Application class comes into existence, but before the user has added any configuration. This is the appropriate place to set configuration for your plugin * before_initialize: This is run after all of the user's configuration has completed, but before any initializers have begun (in other words, it runs right after config/environments/{development,production,test}.rb) * after_initialize: This is run after all of the initializers have run. It is an appropriate place for forking in a preforking setup Each of these hooks may be used via ActiveSupport.on_load(name) { }. In all these cases, the context inside the block will be the Application object. This means that for simple cases, you can use these hooks without needing to create a Railtie. --- activerecord/lib/active_record/railtie.rb | 17 +++--- .../lib/active_support/lazy_load_hooks.rb | 18 ++++-- activesupport/test/lazy_load_hooks_test.rb | 67 ++++++++++++++++++++++ railties/guides/source/initialization.textile | 2 - railties/lib/rails/application.rb | 10 ++-- railties/lib/rails/application/bootstrap.rb | 8 +-- railties/lib/rails/application/configuration.rb | 2 +- railties/lib/rails/application/finisher.rb | 6 +- railties/lib/rails/engine.rb | 15 +++-- railties/lib/rails/railtie/configuration.rb | 48 ++++++++++++++-- .../application/initializers/initializers_test.rb | 13 ----- 11 files changed, 154 insertions(+), 52 deletions(-) create mode 100644 activesupport/test/lazy_load_hooks_test.rb diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 898df0a67a..a32fb7d399 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -15,6 +15,12 @@ module ActiveRecord config.generators.orm :active_record, :migration => true, :timestamps => true + config.app_middleware.insert_after "::ActionDispatch::Callbacks", + "ActiveRecord::QueryCache" + + config.app_middleware.insert_after "::ActionDispatch::Callbacks", + "ActiveRecord::ConnectionAdapters::ConnectionManagement" + rake_tasks do load "active_record/railties/databases.rake" end @@ -58,16 +64,9 @@ module ActiveRecord end end - # Setup database middleware after initializers have run - initializer "active_record.initialize_database_middleware", :after => "action_controller.set_configs" do |app| - middleware = app.config.middleware - middleware.insert_after "::ActionDispatch::Callbacks", ActiveRecord::QueryCache - middleware.insert_after "::ActionDispatch::Callbacks", ActiveRecord::ConnectionAdapters::ConnectionManagement - end - initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app| - ActiveSupport.on_load(:active_record) do - unless app.config.cache_classes + unless app.config.cache_classes + ActiveSupport.on_load(:active_record) do ActionDispatch::Callbacks.after do ActiveRecord::Base.reset_subclasses ActiveRecord::Base.clear_reloadable_connections! diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb index 642a4c105c..3664431a28 100644 --- a/activesupport/lib/active_support/lazy_load_hooks.rb +++ b/activesupport/lib/active_support/lazy_load_hooks.rb @@ -2,16 +2,26 @@ module ActiveSupport @load_hooks = Hash.new {|h,k| h[k] = [] } @loaded = {} - def self.on_load(name, &block) + def self.on_load(name, options = {}, &block) if base = @loaded[name] - base.instance_eval(&block) + execute_hook(base, options, block) else - @load_hooks[name] << block + @load_hooks[name] << [block, options] + end + end + + def self.execute_hook(base, options, block) + if options[:yield] + block.call(base) + else + base.instance_eval(&block) end end def self.run_load_hooks(name, base = Object) - @load_hooks[name].each { |hook| base.instance_eval(&hook) } @loaded[name] = base + @load_hooks[name].each do |hook, options| + execute_hook(base, options, hook) + end end end \ No newline at end of file diff --git a/activesupport/test/lazy_load_hooks_test.rb b/activesupport/test/lazy_load_hooks_test.rb new file mode 100644 index 0000000000..58ccc14324 --- /dev/null +++ b/activesupport/test/lazy_load_hooks_test.rb @@ -0,0 +1,67 @@ +require 'abstract_unit' + +class LazyLoadHooksTest < ActiveSupport::TestCase + def test_basic_hook + i = 0 + ActiveSupport.on_load(:basic_hook) { i += 1 } + ActiveSupport.run_load_hooks(:basic_hook) + assert_equal 1, i + end + + def test_hook_registered_after_run + i = 0 + ActiveSupport.run_load_hooks(:registered_after) + assert_equal 0, i + ActiveSupport.on_load(:registered_after) { i += 1 } + assert_equal 1, i + end + + def test_hook_receives_a_context + i = 0 + ActiveSupport.on_load(:contextual) { i += incr } + assert_equal 0, i + ActiveSupport.run_load_hooks(:contextual, FakeContext.new(2)) + assert_equal 2, i + end + + def test_hook_receives_a_context_afterward + i = 0 + ActiveSupport.run_load_hooks(:contextual_after, FakeContext.new(2)) + assert_equal 0, i + ActiveSupport.on_load(:contextual_after) { i += incr } + assert_equal 2, i + end + + def test_hook_with_yield_true + i = 0 + ActiveSupport.on_load(:contextual_yield, :yield => true) do |obj| + i += obj.incr + incr_amt + end + assert_equal 0, i + ActiveSupport.run_load_hooks(:contextual_yield, FakeContext.new(2)) + assert_equal 7, i + end + + def test_hook_with_yield_true_afterward + i = 0 + ActiveSupport.run_load_hooks(:contextual_yield_after, FakeContext.new(2)) + assert_equal 0, i + ActiveSupport.on_load(:contextual_yield_after, :yield => true) do |obj| + i += obj.incr + incr_amt + end + assert_equal 7, i + end + +private + + def incr_amt + 5 + end + + class FakeContext + attr_reader :incr + def initialize(incr) + @incr = incr + end + end +end \ No newline at end of file diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 9ce27fa331..96d6998e1c 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -2379,7 +2379,6 @@ Now that we've referenced that class, it will be required for us. You'll notice * initialize_subscriber * set_clear_dependencies_hook * initialize_dependency_mechanism -* bootstrap_load_path These are all defined using the +initializer+ method: @@ -2930,7 +2929,6 @@ With +@@autoloads+ being * initialize_subscriber * set_clear_dependencies_hook * initialize_dependency_mechanism -* bootstrap_load_path h4. Active Support Initializers diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index d39f9a2ae9..9e18dccf69 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -12,7 +12,7 @@ module Rails # points to it. # # In other words, Rails::Application is Singleton and whenever you are accessing - # Rails::Application.config or YourApplication::Application.config, you are actually + # Rails::Application.config or YourApplication::Application.config, you are actually # accessing YourApplication::Application.instance.config. # # == Initialization @@ -40,7 +40,7 @@ module Rails # # The Application is also responsible for building the middleware stack and setting up # both application and engines metals. - # + # class Application < Engine autoload :Bootstrap, 'rails/application/bootstrap' autoload :Configurable, 'rails/application/configurable' @@ -69,6 +69,8 @@ module Rails raise "You cannot have more than one Rails::Application" if Rails.application super Rails.application = base.instance + + ActiveSupport.run_load_hooks(:before_configuration, base.instance) end def respond_to?(*args) @@ -82,7 +84,7 @@ module Rails end end - delegate :metal_loader, :to => :config + delegate :middleware, :metal_loader, :to => :config def require_environment! environment = paths.config.environment.to_a.first @@ -125,7 +127,7 @@ module Rails end def app - @app ||= middleware.build(routes) + @app ||= config.middleware.build(routes) end def call(env) diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb index 022e1a91d8..e62eed8a87 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -10,7 +10,8 @@ module Rails require environment if environment end - initializer :load_all_active_support do + initializer :load_active_support do + require 'active_support/dependencies' require "active_support/all" unless config.active_support.bare end @@ -18,7 +19,6 @@ module Rails # Used by Passenger to ensure everything's loaded before forking and # to avoid autoload race conditions in JRuby. initializer :preload_frameworks do - require 'active_support/dependencies' ActiveSupport::Autoload.eager_autoload! if config.preload_frameworks end @@ -66,8 +66,8 @@ module Rails ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load end - initializer :bootstrap_load_path do - # This is just an initializer used as hook so all load paths are loaded together + initializer :bootstrap_hook do |app| + ActiveSupport.run_load_hooks(:before_initialize, app) end end end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 1ad77fdfec..cd77f1adaf 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -33,7 +33,7 @@ module Rails end def middleware - @middleware ||= default_middleware_stack + @middleware ||= app_middleware.merge_into(default_middleware_stack) end def metal_loader diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 94507bb387..03bc270c81 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -35,10 +35,8 @@ module Rails app end - initializer :after_initialize do - config.after_initialize_blocks.each do |block| - block.call(self) - end + initializer :finisher_hook do |app| + ActiveSupport.run_load_hooks(:after_initialize, app) end # Disable dependency loading during request cycle diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index ab0ead65a9..652bd40ee4 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -45,7 +45,7 @@ module Rails # app.middleware.use MyEngine::Middleware # end # end - # + # # == Paths # # Since Rails 3.0, both your Application and Engines do not have hardcoded paths. @@ -125,7 +125,7 @@ module Rails end end - delegate :middleware, :paths, :root, :to => :config + delegate :paths, :root, :to => :config def load_tasks super @@ -133,7 +133,7 @@ module Rails end # Add configured load paths to ruby load paths and remove duplicates. - initializer :set_load_path, :before => :bootstrap_load_path do + initializer :set_load_path, :before => :bootstrap_hook do config.load_paths.reverse_each do |path| $LOAD_PATH.unshift(path) if File.directory?(path) end @@ -142,7 +142,10 @@ module Rails # Set the paths from which Rails will automatically load source files, # and the load_once paths. - initializer :set_autoload_paths, :before => :bootstrap_load_path do |app| + # + # This needs to be an initializer, since it needs to run once + # per engine and get the engine as a block parameter + initializer :set_autoload_paths, :before => :bootstrap_hook do |app| ActiveSupport::Dependencies.load_paths.unshift(*config.load_paths) if reloadable?(app) @@ -200,7 +203,9 @@ module Rails end end - initializer :load_app_classes do |app| + # This needs to be an initializer, since it needs to run once + # per engine and get the engine as a block parameter + initializer :load_app_classes, :before => :finisher_hook do |app| next if $rails_rake_task if app.config.cache_classes diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index 16eccaccc4..f57d82a3d8 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -3,10 +3,50 @@ require 'rails/configuration' module Rails class Railtie class Configuration + class MiddlewareStackProxy + def initialize + @operations = [] + end + + def insert_before(*args, &block) + @operations << [:insert_before, args, block] + end + + alias insert insert_before + + def insert_after(*args, &block) + @operations << [:insert_after, args, block] + end + + def swap(*args, &block) + @operations << [:swap, args, block] + end + + def use(*args, &block) + @operations << [:use, args, block] + end + + def merge_into(other) + @operations.each do |operation, args, block| + other.send(operation, *args, &block) + end + other + end + end + def initialize @@options ||= {} end + # This allows you to modify the application's middlewares from Engines. + # + # All operations you run on the app_middleware will be replayed on the + # application once it is defined and the default_middlewares are + # created + def app_middleware + @@app_middleware ||= MiddlewareStackProxy.new + end + # Holds generators configuration: # # config.generators do |g| @@ -28,12 +68,8 @@ module Rails end end - def after_initialize_blocks - @@after_initialize_blocks ||= [] - end - - def after_initialize(&blk) - after_initialize_blocks << blk if blk + def after_initialize(&block) + ActiveSupport.on_load(:after_initialize, :yield => true, &block) end def to_prepare_blocks diff --git a/railties/test/application/initializers/initializers_test.rb b/railties/test/application/initializers/initializers_test.rb index 2e6a707175..eca42dada6 100644 --- a/railties/test/application/initializers/initializers_test.rb +++ b/railties/test/application/initializers/initializers_test.rb @@ -28,19 +28,6 @@ module ApplicationTests assert_equal "congratulations", $test_after_initialize_block2 end - test "after_initialize block works correctly when no block is passed" do - add_to_config <<-RUBY - config.root = "#{app_path}" - config.after_initialize { $test_after_initialize_block1 = "success" } - config.after_initialize # don't pass a block, this is what we're testing! - config.after_initialize { $test_after_initialize_block2 = "congratulations" } - RUBY - require "#{app_path}/config/environment" - - assert_equal "success", $test_after_initialize_block1 - assert_equal "congratulations", $test_after_initialize_block2 - end - test "after_initialize runs after frameworks have been initialized" do $activerecord_configurations = nil add_to_config <<-RUBY -- cgit v1.2.3