diff options
| -rw-r--r-- | actionpack/test/dispatch/reloader_test.rb | 9 | ||||
| -rw-r--r-- | activerecord/lib/active_record/railtie.rb | 5 | ||||
| -rw-r--r-- | activesupport/lib/active_support/file_update_checker.rb | 43 | ||||
| -rw-r--r-- | activesupport/lib/active_support/i18n_railtie.rb | 3 | ||||
| -rw-r--r-- | activesupport/test/file_update_checker_test.rb | 13 | ||||
| -rw-r--r-- | railties/CHANGELOG.md | 7 | ||||
| -rw-r--r-- | railties/guides/source/configuring.textile | 2 | ||||
| -rw-r--r-- | railties/lib/rails/application.rb | 40 | ||||
| -rw-r--r-- | railties/lib/rails/application/configuration.rb | 49 | ||||
| -rw-r--r-- | railties/lib/rails/application/finisher.rb | 14 | ||||
| -rw-r--r-- | railties/lib/rails/application/routes_reloader.rb | 12 | ||||
| -rw-r--r-- | railties/lib/rails/engine.rb | 2 | ||||
| -rw-r--r-- | railties/lib/rails/railtie/configuration.rb | 12 | ||||
| -rw-r--r-- | railties/test/application/console_test.rb | 3 | ||||
| -rw-r--r-- | railties/test/application/loading_test.rb | 1 | 
15 files changed, 157 insertions, 58 deletions
diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb index bd24256427..3411bd14ea 100644 --- a/actionpack/test/dispatch/reloader_test.rb +++ b/actionpack/test/dispatch/reloader_test.rb @@ -129,6 +129,15 @@ class ReloaderTest < Test::Unit::TestCase      assert cleaned    end +  def test_prepend_prepare_callback +    i = 10 +    Reloader.to_prepare { i += 1 } +    Reloader.to_prepare(:prepend => true) { i = 0 } + +    Reloader.prepare! +    assert_equal 1, i +  end +    def test_cleanup_callbacks_are_called_on_exceptions      cleaned = false      Reloader.to_cleanup { cleaned  = true } diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index c2e31579a4..71772529d2 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -94,6 +94,11 @@ module ActiveRecord        end      end +    initializer "active_record.add_watchable_files" do |app| +      files = ["#{app.root}/db/schema.rb", "#{app.root}/db/structure.sql"] +      config.watchable_files.concat files.select { |f| File.exist?(f) } +    end +      config.after_initialize do        ActiveSupport.on_load(:active_record) do          instantiate_observers diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 77bc5388d6..4137bbf6a0 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -39,30 +39,53 @@ module ActiveSupport        @paths = paths        @glob  = compile_glob(@paths.extract_options!)        @block = block +      @updated_at = nil        @last_update_at = calculate ? updated_at : nil      end -    def updated_at -      all = [] -      all.concat @paths -      all.concat Dir[@glob] if @glob -      all.map { |path| File.mtime(path) }.max +    # Check if any of the entries were updated. If so, the updated_at +    # value is cached until flush! is called. +    def updated? +      current_updated_at = updated_at +      if @last_update_at != current_updated_at +        @updated_at = updated_at +        true +      else +        false +      end      end +    # Flush the cache so updated? is calculated again +    def flush! +      @updated_at = nil +    end + +    # Execute the block given if updated. This call +    # always flush the cache.      def execute_if_updated -      current_update_at = self.updated_at -      if @last_update_at != current_update_at -        @last_update_at = current_update_at +      if updated? +        @last_update_at = updated_at          @block.call          true        else          false        end +    ensure +      flush!      end      private -    def compile_glob(hash) +    def updated_at #:nodoc: +      @updated_at || begin +        all = [] +        all.concat @paths +        all.concat Dir[@glob] if @glob +        all.map { |path| File.mtime(path) }.max +      end +    end + +    def compile_glob(hash) #:nodoc:        return if hash.empty?        globs = []        hash.each do |key, value| @@ -71,7 +94,7 @@ module ActiveSupport        "{#{globs.join(",")}}"      end -    def compile_ext(array) +    def compile_ext(array) #:nodoc:        array = Array.wrap(array)        return if array.empty?        ".{#{array.join(",")}}" diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index 4c59fe9ac9..a989ff8f57 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -17,7 +17,8 @@ module I18n      # point, no path was added to the reloader, I18n.reload! is not triggered      # on to_prepare callbacks. This will only happen on the config.after_initialize      # callback below. -    initializer "i18n.callbacks" do +    initializer "i18n.callbacks" do |app| +      app.reloaders << I18n::Railtie.reloader        ActionDispatch::Reloader.to_prepare do          I18n::Railtie.reloader.execute_if_updated        end diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb index a5a9b7a682..52c1f3260d 100644 --- a/activesupport/test/file_update_checker_test.rb +++ b/activesupport/test/file_update_checker_test.rb @@ -54,6 +54,19 @@ class FileUpdateCheckerWithEnumerableTest < Test::Unit::TestCase      assert_equal 1, i    end +  def test_should_cache_updated_result_until_flushed +    i = 0 +    checker = ActiveSupport::FileUpdateChecker.new(FILES, true){ i += 1 } +    assert !checker.updated? + +    sleep(1) +    FileUtils.touch(FILES) + +    assert checker.updated? +    assert checker.execute_if_updated +    assert !checker.updated? +  end +    def test_should_invoke_the_block_if_a_watched_dir_changed_its_glob      i = 0      checker = ActiveSupport::FileUpdateChecker.new([{"tmp_watcher" => [:txt]}], true){ i += 1 } diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 2841996b56..a88f443517 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,9 +1,8 @@  ## Rails 3.2.0 (unreleased) ## -*   New applications get a flag -    `config.active_record.auto_explain_threshold_in_seconds` in the environments -    configuration files. With a value of 0.5 in development.rb, and commented -    out in production.rb. No mention in test.rb. *fxn* +*   Speed up development by only reloading classes if dependencies files changed. This can be turned off by setting `config.reload_classes_only_on_change` to false. *José Valim* + +*   New applications get a flag `config.active_record.auto_explain_threshold_in_seconds` in the environments configuration files. With a value of 0.5 in development.rb, and commented out in production.rb. No mention in test.rb. *fxn*  *   Add DebugExceptions middleware which contains features extracted from ShowExceptions middleware *José Valim* diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index 8e65dbccbb..8cf88cf71f 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -98,6 +98,8 @@ NOTE. The +config.asset_path+ configuration is ignored if the asset pipeline is  * +config.preload_frameworks+ enables or disables preloading all frameworks at startup. Enabled by +config.threadsafe!+. Defaults to +nil+, so is disabled. +* +config.reload_classes_only_on_change+ enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. +  * +config.reload_plugins+ enables or disables plugin reloading. Defaults to false.  * +config.secret_token+ used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get +config.secret_token+ initialized to a random key in +config/initializers/secret_token.rb+. diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index acbfd7078b..0b8eac8a8b 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -71,12 +71,14 @@ module Rails      attr_accessor :assets, :sandbox      alias_method :sandbox?, :sandbox +    attr_reader :reloaders      delegate :default_url_options, :default_url_options=, :to => :routes      def initialize        super        @initialized = false +      @reloaders   = []      end      # This method is called just after an application inherits from Rails::Application, @@ -119,6 +121,7 @@ module Rails        reloader = routes_reloader        hook = lambda { reloader.execute_if_updated }        hook.call +      self.reloaders << reloader        ActionDispatch::Reloader.to_prepare(&hook)      end @@ -126,10 +129,35 @@ module Rails      # A plugin may override this if they desire to provide a more exquisite app reloading.      # :api: plugin      def set_dependencies_hook -      ActionDispatch::Reloader.to_cleanup do +      callback = lambda do          ActiveSupport::DescendantsTracker.clear          ActiveSupport::Dependencies.clear        end + +      if config.reload_classes_only_on_change +        reloader = ActiveSupport::FileUpdateChecker.new(watchable_args, true, &callback) +        self.reloaders << reloader +        # We need to set a to_prepare callback regardless of the reloader result, i.e. +        # models should be reloaded if any of the reloaders (i18n, routes) were updated. +        ActionDispatch::Reloader.to_prepare(:prepend => true, &callback) +      else +        ActionDispatch::Reloader.to_cleanup(&callback) +      end +    end + +    # Returns an array of file paths appended with a hash of directories-extensions +    # suitable for ActiveSupport::FileUpdateChecker API. +    def watchable_args +      files = [] +      files.concat config.watchable_files + +      dirs = {} +      dirs.merge! config.watchable_dirs +      ActiveSupport::Dependencies.autoload_paths.each do |path| +        dirs[path.to_s] = [:rb] +      end + +      files << dirs      end      # Initialize the application passing the given group. By default, the @@ -223,6 +251,10 @@ module Rails      alias :build_middleware_stack :app +    def reload_dependencies? +      config.reload_classes_only_on_change != true || reloaders.map(&:updated?).any? +    end +      def default_middleware_stack        ActionDispatch::MiddlewareStack.new.tap do |middleware|          if rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache @@ -252,7 +284,11 @@ module Rails            middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header          end -        middleware.use ::ActionDispatch::Reloader unless config.cache_classes +        unless config.cache_classes +          app = self +          middleware.use ::ActionDispatch::Reloader, lambda { app.reload_dependencies? } +        end +          middleware.use ::ActionDispatch::Callbacks          middleware.use ::ActionDispatch::Cookies diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 4b2afe3a28..39d66ecc31 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -7,11 +7,11 @@ module Rails      class Configuration < ::Rails::Engine::Configuration        attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets,                      :cache_classes, :cache_store, :consider_all_requests_local, -                    :dependency_loading, :filter_parameters, -                    :force_ssl, :helpers_paths, :logger, :log_tags, :preload_frameworks, -                    :relative_url_root, :reload_plugins, :secret_token, :serve_static_assets, -                    :ssl_options, :static_cache_control, :session_options, -                    :time_zone, :whiny_nils, :railties_order, :all_initializers +                    :dependency_loading, :filter_parameters, :force_ssl, :helpers_paths, +                    :initializers_paths, :logger, :log_tags, :preload_frameworks, +                    :railties_order, :relative_url_root, :reload_plugins, :secret_token, +                    :serve_static_assets, :ssl_options, :static_cache_control, :session_options, +                    :time_zone, :reload_classes_only_on_change, :whiny_nils        attr_writer :log_level        attr_reader :encoding @@ -19,25 +19,26 @@ module Rails        def initialize(*)          super          self.encoding = "utf-8" -        @allow_concurrency           = false -        @consider_all_requests_local = false -        @filter_parameters           = [] -        @helpers_paths               = [] -        @dependency_loading          = true -        @serve_static_assets         = true -        @static_cache_control        = nil -        @force_ssl                   = false -        @ssl_options                 = {} -        @session_store               = :cookie_store -        @session_options             = {} -        @time_zone                   = "UTC" -        @log_level                   = nil -        @middleware                  = app_middleware -        @generators                  = app_generators -        @cache_store                 = [ :file_store, "#{root}/tmp/cache/" ] -        @railties_order              = [:all] -        @all_initializers            = [] -        @relative_url_root           = ENV["RAILS_RELATIVE_URL_ROOT"] +        @allow_concurrency             = false +        @consider_all_requests_local   = false +        @filter_parameters             = [] +        @helpers_paths                 = [] +        @dependency_loading            = true +        @serve_static_assets           = true +        @static_cache_control          = nil +        @force_ssl                     = false +        @ssl_options                   = {} +        @session_store                 = :cookie_store +        @session_options               = {} +        @time_zone                     = "UTC" +        @log_level                     = nil +        @middleware                    = app_middleware +        @generators                    = app_generators +        @cache_store                   = [ :file_store, "#{root}/tmp/cache/" ] +        @railties_order                = [:all] +        @initializers_paths            = [] +        @relative_url_root             = ENV["RAILS_RELATIVE_URL_ROOT"] +        @reload_classes_only_on_change = true          @assets = ActiveSupport::OrderedOptions.new          @assets.enabled                  = false diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 17e7aa0f28..e000f6ef3a 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -5,7 +5,7 @@ module Rails        $rails_rake_task = nil        initializer :load_config_initializers do -        config.all_initializers.each { |init| load(init) } +        config.initializers_paths.each { |init| load(init) }        end        initializer :add_generator_templates do @@ -65,17 +65,17 @@ module Rails        end        # Set app reload just after the finisher hook to ensure -      # paths added in the hook are still loaded. -      initializer :set_dependencies_hook, :group => :all do |app| -        app.set_dependencies_hook -      end - -      # Set app reload just after the finisher hook to ensure        # routes added in the hook are still loaded.        initializer :set_routes_reloader_hook do |app|          app.set_routes_reloader_hook        end +      # Set app reload just after the finisher hook to ensure +      # paths added in the hook are still loaded. +      initializer :set_dependencies_hook, :group => :all do |app| +        app.set_dependencies_hook +      end +        # Disable dependency loading during request cycle        initializer :disable_dependency_loading do          if config.cache_classes && !config.dependency_loading diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb index c1f435a3ee..460b84dfd9 100644 --- a/railties/lib/rails/application/routes_reloader.rb +++ b/railties/lib/rails/application/routes_reloader.rb @@ -1,21 +1,17 @@ +require "active_support/core_ext/module/delegation" +  module Rails    class Application      class RoutesReloader        attr_reader :route_sets +      delegate :paths, :execute_if_updated, :updated?, :to => :@updater +        def initialize(updater=ActiveSupport::FileUpdateChecker)          @updater    = updater.new([]) { reload! }          @route_sets = []        end -      def paths -        @updater.paths -      end - -      def execute_if_updated -        @updater.execute_if_updated -      end -        def reload!          clear!          load_paths diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 8ebe1f48a5..86efc7332c 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -584,7 +584,7 @@ module Rails      end      initializer :append_config_initializers do |app| -      app.config.all_initializers.concat config.paths["config/initializers"].existent.sort +      app.config.initializers_paths.concat config.paths["config/initializers"].existent.sort      end      initializer :engines_blank_point do diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index f888684117..cf9e4ad500 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -7,6 +7,18 @@ module Rails          @@options ||= {}        end +      # Add files that should be watched for change. +      def watchable_files +        @@watchable_files ||= [] +      end + +      # Add directories that should be watched for change. +      # The key of the hashes should be directories and the values should +      # be an array of extensions to match in each directory. +      def watchable_dirs +        @@watchable_dirs ||= {} +      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 diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index 2073c780bf..6f9d8d57b1 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -61,7 +61,8 @@ class ConsoleTest < Test::Unit::TestCase      load_environment      assert User.new.respond_to?(:name) -    assert !User.new.respond_to?(:age) + +    sleep(1)      app_file "app/models/user.rb", <<-MODEL        class User diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index 47c6fd5c6e..c4908915dc 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -66,6 +66,7 @@ class LoadingTest < Test::Unit::TestCase    def test_descendants_are_cleaned_on_each_request_without_cache_classes      add_to_config <<-RUBY        config.cache_classes = false +      config.reload_classes_only_on_change = false      RUBY      app_file "app/models/post.rb", <<-MODEL  | 
