1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/railties/lib/rails/application/finisher.rb
Xavier Noria 0d523d8365 Drops support for classic mode
This starts a series of patches in which we drop classic mode. The final
result no longer has a const_missing callback, there is no hook/unhook,
and so on.

So, in this patch we remove the ability of configuring classic, but some
of the code that remains will be further refactored.
2021-03-08 05:30:11 +01:00

257 lines
9 KiB
Ruby

# frozen_string_literal: true
require "active_support/core_ext/string/inflections"
require "active_support/core_ext/array/conversions"
require "zeitwerk"
module Rails
class Application
module Finisher
include Initializable
initializer :add_generator_templates do
config.generators.templates.unshift(*paths["lib/templates"].existent)
end
initializer :ensure_autoload_once_paths_as_subset do
extra = ActiveSupport::Dependencies.autoload_once_paths -
ActiveSupport::Dependencies.autoload_paths
unless extra.empty?
abort <<-end_error
autoload_once_paths must be a subset of the autoload_paths.
Extra items in autoload_once_paths: #{extra * ','}
end_error
end
end
# This will become an error if/when we remove classic mode. The plan is
# autoloaders won't be configured up to this point in the finisher, so
# constants just won't be found, raising regular NameError exceptions.
initializer :warn_if_autoloaded, before: :let_zeitwerk_take_over do
next if config.cache_classes
next if ActiveSupport::Dependencies.autoloaded_constants.empty?
autoloaded = ActiveSupport::Dependencies.autoloaded_constants
constants = "constant".pluralize(autoloaded.size)
enum = autoloaded.to_sentence
have = autoloaded.size == 1 ? "has" : "have"
these = autoloaded.size == 1 ? "This" : "These"
example = autoloaded.first
example_klass = example.constantize.class
ActiveSupport::DescendantsTracker.clear
ActiveSupport::Dependencies.clear
unload_message = "#{these} autoloaded #{constants} #{have} been unloaded."
ActiveSupport::Deprecation.warn(<<~WARNING)
Initialization autoloaded the #{constants} #{enum}.
Being able to do this is deprecated. Autoloading during initialization is going
to be an error condition in future versions of Rails.
Reloading does not reboot the application, and therefore code executed during
initialization does not run again. So, if you reload #{example}, for example,
the expected changes won't be reflected in that stale #{example_klass} object.
#{unload_message}
In order to autoload safely at boot time, please wrap your code in a reloader
callback this way:
Rails.application.reloader.to_prepare do
# Autoload classes and modules needed at boot time here.
end
That block runs when the application boots, and every time there is a reload.
For historical reasons, it may run twice, so it has to be idempotent.
Check the "Autoloading and Reloading Constants" guide to learn more about how
Rails autoloads and reloads.
WARNING
end
initializer :let_zeitwerk_take_over do
require "active_support/dependencies/zeitwerk_integration"
ActiveSupport::Dependencies::ZeitwerkIntegration.take_over(enable_reloading: !config.cache_classes)
end
# Setup default session store if not already set in config/application.rb
initializer :setup_default_session_store, before: :build_middleware_stack do |app|
unless app.config.session_store?
app_name = app.class.name ? app.railtie_name.chomp("_application") : ""
app.config.session_store :cookie_store, key: "_#{app_name}_session"
end
end
initializer :build_middleware_stack do
build_middleware_stack
end
initializer :define_main_app_helper do |app|
app.routes.define_mounted_helper(:main_app)
end
initializer :add_to_prepare_blocks do |app|
config.to_prepare_blocks.each do |block|
app.reloader.to_prepare(&block)
end
end
# This needs to happen before eager load so it happens
# in exactly the same point regardless of config.eager_load
initializer :run_prepare_callbacks do |app|
app.reloader.prepare!
end
initializer :eager_load! do
if config.eager_load
ActiveSupport.run_load_hooks(:before_eager_load, self)
Zeitwerk::Loader.eager_load_all
config.eager_load_namespaces.each(&:eager_load!)
end
end
# All initialization is done, including eager loading in production
initializer :finisher_hook do
ActiveSupport.run_load_hooks(:after_initialize, self)
end
class MutexHook
def initialize(mutex = Mutex.new)
@mutex = mutex
end
def run
@mutex.lock
end
def complete(_state)
@mutex.unlock
end
end
module InterlockHook
def self.run
ActiveSupport::Dependencies.interlock.start_running
end
def self.complete(_state)
ActiveSupport::Dependencies.interlock.done_running
end
end
initializer :configure_executor_for_concurrency do |app|
if config.allow_concurrency == false
# User has explicitly opted out of concurrent request
# handling: presumably their code is not threadsafe
app.executor.register_hook(MutexHook.new, outer: true)
elsif config.allow_concurrency == :unsafe
# Do nothing, even if we know this is dangerous. This is the
# historical behavior for true.
else
# Default concurrency setting: enabled, but safe
unless config.cache_classes && config.eager_load
# Without cache_classes + eager_load, the load interlock
# is required for proper operation
app.executor.register_hook(InterlockHook, outer: true)
end
end
end
initializer :add_internal_routes do |app|
if Rails.env.development?
app.routes.prepend do
get "/rails/info/properties" => "rails/info#properties", internal: true
get "/rails/info/routes" => "rails/info#routes", internal: true
get "/rails/info" => "rails/info#index", internal: true
end
routes_reloader.run_after_load_paths = -> do
app.routes.append do
get "/" => "rails/welcome#index", internal: true
end
end
end
end
# Set routes reload after the finisher hook to ensure routes added in
# the hook are taken into account.
initializer :set_routes_reloader_hook do |app|
reloader = routes_reloader
reloader.eager_load = app.config.eager_load
reloader.execute
reloaders << reloader
app.reloader.to_run do
# We configure #execute rather than #execute_if_updated because if
# autoloaded constants are cleared we need to reload routes also in
# case any was used there, as in
#
# mount MailPreview => 'mail_view'
#
# This means routes are also reloaded if i18n is updated, which
# might not be necessary, but in order to be more precise we need
# some sort of reloaders dependency support, to be added.
require_unload_lock!
reloader.execute
end
end
# Set clearing dependencies after the finisher hook to ensure paths
# added in the hook are taken into account.
initializer :set_clear_dependencies_hook, group: :all do |app|
callback = lambda do
ActiveSupport::DescendantsTracker.clear
ActiveSupport::Dependencies.clear
end
if config.cache_classes
app.reloader.check = lambda { false }
elsif config.reload_classes_only_on_change
app.reloader.check = lambda do
app.reloaders.map(&:updated?).any?
end
else
app.reloader.check = lambda { true }
end
if config.cache_classes
# No reloader
elsif config.reload_classes_only_on_change
reloader = config.file_watcher.new(*watchable_args, &callback)
reloaders << reloader
# Prepend this callback to have autoloaded constants cleared before
# any other possible reloading, in case they need to autoload fresh
# constants.
app.reloader.to_run(prepend: true) do
# In addition to changes detected by the file watcher, if routes
# or i18n have been updated we also need to clear constants,
# that's why we run #execute rather than #execute_if_updated, this
# callback has to clear autoloaded constants after any update.
class_unload! do
reloader.execute
end
end
else
app.reloader.to_complete do
class_unload!(&callback)
end
end
end
# Disable dependency loading during request cycle
initializer :disable_dependency_loading do
if config.eager_load && config.cache_classes && !config.enable_dependency_loading
ActiveSupport::Dependencies.unhook!
end
end
end
end
end