mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
prevent Dependencies#remove_const from autoloading parents [fixes #8301]
This commit is contained in:
parent
5aade8245a
commit
46ebce6b49
2 changed files with 54 additions and 32 deletions
|
@ -644,46 +644,58 @@ module ActiveSupport #:nodoc:
|
||||||
normalized = const.to_s.sub(/\A::/, '')
|
normalized = const.to_s.sub(/\A::/, '')
|
||||||
normalized.sub!(/\A(Object::)+/, '')
|
normalized.sub!(/\A(Object::)+/, '')
|
||||||
|
|
||||||
constants = normalized.split('::')
|
constants = normalized.split('::')
|
||||||
to_remove = constants.pop
|
to_remove = constants.pop
|
||||||
parent_name = constants.empty? ? 'Object' : constants.join('::')
|
|
||||||
|
|
||||||
if parent = safe_constantize(parent_name)
|
if constants.empty?
|
||||||
log "removing constant #{const}"
|
parent = Object
|
||||||
|
else
|
||||||
|
# This method is robust to non-reachable constants.
|
||||||
|
#
|
||||||
|
# Non-reachable constants may be passed if some of the parents were
|
||||||
|
# autoloaded and already removed. It is easier to do a sanity check
|
||||||
|
# here than require the caller to be clever. We check the parent
|
||||||
|
# rather than the very const argument because we do not want to
|
||||||
|
# trigger Kernel#autoloads, see the comment below.
|
||||||
|
parent_name = constants.join('::')
|
||||||
|
return unless qualified_const_defined?(parent_name)
|
||||||
|
parent = constantize(parent_name)
|
||||||
|
end
|
||||||
|
|
||||||
# In an autoloaded user.rb like this
|
log "removing constant #{const}"
|
||||||
#
|
|
||||||
# autoload :Foo, 'foo'
|
|
||||||
#
|
|
||||||
# class User < ActiveRecord::Base
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# we correctly register "Foo" as being autoloaded. But if the app does
|
|
||||||
# not use the "Foo" constant we need to be careful not to trigger
|
|
||||||
# loading "foo.rb" ourselves. While #const_defined? and #const_get? do
|
|
||||||
# require the file, #autoload? and #remove_const don't.
|
|
||||||
#
|
|
||||||
# We are going to remove the constant nonetheless ---which exists as
|
|
||||||
# far as Ruby is concerned--- because if the user removes the macro
|
|
||||||
# call from a class or module that were not autoloaded, as in the
|
|
||||||
# example above with Object, accessing to that constant must err.
|
|
||||||
unless parent.autoload?(to_remove)
|
|
||||||
begin
|
|
||||||
constantized = parent.const_get(to_remove, false)
|
|
||||||
rescue NameError
|
|
||||||
log "the constant #{const} is not reachable anymore, skipping"
|
|
||||||
return
|
|
||||||
else
|
|
||||||
constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
# In an autoloaded user.rb like this
|
||||||
|
#
|
||||||
|
# autoload :Foo, 'foo'
|
||||||
|
#
|
||||||
|
# class User < ActiveRecord::Base
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# we correctly register "Foo" as being autoloaded. But if the app does
|
||||||
|
# not use the "Foo" constant we need to be careful not to trigger
|
||||||
|
# loading "foo.rb" ourselves. While #const_defined? and #const_get? do
|
||||||
|
# require the file, #autoload? and #remove_const don't.
|
||||||
|
#
|
||||||
|
# We are going to remove the constant nonetheless ---which exists as
|
||||||
|
# far as Ruby is concerned--- because if the user removes the macro
|
||||||
|
# call from a class or module that were not autoloaded, as in the
|
||||||
|
# example above with Object, accessing to that constant must err.
|
||||||
|
unless parent.autoload?(to_remove)
|
||||||
begin
|
begin
|
||||||
parent.instance_eval { remove_const to_remove }
|
constantized = parent.const_get(to_remove, false)
|
||||||
rescue NameError
|
rescue NameError
|
||||||
log "the constant #{const} is not reachable anymore, skipping"
|
log "the constant #{const} is not reachable anymore, skipping"
|
||||||
|
return
|
||||||
|
else
|
||||||
|
constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
parent.instance_eval { remove_const to_remove }
|
||||||
|
rescue NameError
|
||||||
|
log "the constant #{const} is not reachable anymore, skipping"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
|
@ -938,6 +938,16 @@ class DependenciesTest < ActiveSupport::TestCase
|
||||||
assert !defined?(ShouldNotBeAutoloaded)
|
assert !defined?(ShouldNotBeAutoloaded)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_remove_constant_does_not_autoload_already_removed_parents_as_a_side_effect
|
||||||
|
with_autoloading_fixtures do
|
||||||
|
::A
|
||||||
|
::A::B
|
||||||
|
ActiveSupport::Dependencies.remove_constant('A')
|
||||||
|
ActiveSupport::Dependencies.remove_constant('A::B')
|
||||||
|
assert !defined?(A)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_load_once_constants_should_not_be_unloaded
|
def test_load_once_constants_should_not_be_unloaded
|
||||||
with_autoloading_fixtures do
|
with_autoloading_fixtures do
|
||||||
ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths
|
ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths
|
||||||
|
|
Loading…
Reference in a new issue