Skip to content

Commit

Permalink
prevent Dependencies#remove_const from autoloading parents [fixes #8301]
Browse files Browse the repository at this point in the history
  • Loading branch information
fxn committed Nov 27, 2012
1 parent 5aade82 commit 46ebce6
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 32 deletions.
76 changes: 44 additions & 32 deletions activesupport/lib/active_support/dependencies.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -644,46 +644,58 @@ def remove_constant(const) #: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
# In an autoloaded user.rb like this # This method is robust to non-reachable constants.
#
# 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 # Non-reachable constants may be passed if some of the parents were
# far as Ruby is concerned--- because if the user removes the macro # autoloaded and already removed. It is easier to do a sanity check
# call from a class or module that were not autoloaded, as in the # here than require the caller to be clever. We check the parent
# example above with Object, accessing to that constant must err. # rather than the very const argument because we do not want to
unless parent.autoload?(to_remove) # trigger Kernel#autoloads, see the comment below.
begin parent_name = constants.join('::')
constantized = parent.const_get(to_remove, false) return unless qualified_const_defined?(parent_name)
rescue NameError parent = constantize(parent_name)
log "the constant #{const} is not reachable anymore, skipping" end
return
else
constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
end
end


log "removing constant #{const}"

# 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
Expand Down
10 changes: 10 additions & 0 deletions activesupport/test/dependencies_test.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -938,6 +938,16 @@ def test_remove_constant_does_not_trigger_loading_autoloads
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
Expand Down

0 comments on commit 46ebce6

Please sign in to comment.