diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index dbc81dd47574d772df58baafd26f451ee6e166d9..d67c4d0c3af0461523e985563b6d83572c92b547 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -20,6 +20,9 @@ module ActiveSupport #:nodoc: module Dependencies #:nodoc: extend self + UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name) + private_constant :UNBOUND_METHOD_MODULE_NAME + mattr_accessor :interlock, default: Interlock.new # :doc: @@ -659,7 +662,7 @@ def safe_constantize(name) # Determine if the given constant has been automatically loaded. def autoloaded?(desc) - return false if desc.is_a?(Module) && desc.anonymous? + return false if desc.is_a?(Module) && real_mod_name(desc).nil? name = to_constant_name desc return false unless qualified_const_defined?(name) autoloaded_constants.include?(name) @@ -715,7 +718,7 @@ def to_constant_name(desc) #:nodoc: when String then desc.sub(/^::/, "") when Symbol then desc.to_s when Module - desc.name || + real_mod_name(desc) || raise(ArgumentError, "Anonymous modules have no name to be referenced by") else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" end @@ -789,6 +792,14 @@ def remove_constant(const) #:nodoc: def log(message) logger.debug("autoloading: #{message}") if logger && verbose end + + private + + # Returns the original name of a class or module even if `name` has been + # overridden. + def real_mod_name(mod) + UNBOUND_METHOD_MODULE_NAME.bind(mod).call + end end end diff --git a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb index fd39ad6e55325b63c7f1c34d817fa06b55126cb4..c87fadcef8222d9f1482735082a08e4a5a902886 100644 --- a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb +++ b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb @@ -28,7 +28,7 @@ def autoloaded_constants end def autoloaded?(object) - cpath = object.is_a?(Module) ? object.name : object.to_s + cpath = object.is_a?(Module) ? real_mod_name(object) : object.to_s Rails.autoloaders.main.unloadable_cpath?(cpath) end diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 003a0dbccb40df47b3dda1f51afa5bacfb57fcde..6bad69f7f224cc4e369463b4cc7522988dcf80ca 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -592,6 +592,13 @@ def test_autoloaded? nil_name = Module.new def nil_name.name() nil end assert_not ActiveSupport::Dependencies.autoloaded?(nil_name) + + invalid_constant_name = Module.new do + def self.name + "primary::SchemaMigration" + end + end + assert_not ActiveSupport::Dependencies.autoloaded?(invalid_constant_name) end ensure remove_constants(:ModuleFolder) diff --git a/railties/test/application/zeitwerk_integration_test.rb b/railties/test/application/zeitwerk_integration_test.rb index 9146222f7311d267fe58a461e647e3c69de35052..ff8c06b479aeaf48c619de6bf8c5e5ba77b48e82 100644 --- a/railties/test/application/zeitwerk_integration_test.rb +++ b/railties/test/application/zeitwerk_integration_test.rb @@ -98,6 +98,15 @@ class RESTfulController < ApplicationController assert_nil deps.safe_constantize("Admin") end + test "autoloaded? and overridden class names" do + invalid_constant_name = Module.new do + def self.name + "primary::SchemaMigration" + end + end + assert_not deps.autoloaded?(invalid_constant_name) + end + test "unloadable constants (main)" do app_file "app/models/user.rb", "class User; end" app_file "app/models/post.rb", "class Post; end"