# 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