mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
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. Extensions like Active Record should add their respective files like db/schema.rb and db/structure.sql to `config.watchable_files` if they want their changes to affect classes reloading. Thanks to https://github.com/paneq/active_reload and Pastorino for the inspiration. <3
This commit is contained in:
parent
62cda03fa8
commit
fa1d9a884c
15 changed files with 161 additions and 62 deletions
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
end
|
||||
|
||||
def execute_if_updated
|
||||
current_update_at = self.updated_at
|
||||
if @last_update_at != current_update_at
|
||||
@last_update_at = current_update_at
|
||||
@block.call
|
||||
# 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
|
||||
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(",")}}"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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*
|
||||
|
||||
|
|
|
@ -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+.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -64,18 +64,18 @@ module Rails
|
|||
ActiveSupport.run_load_hooks(:after_initialize, self)
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue