Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Update dependencies to allow constants to be defined alongside their …

…siblings. (Applying changeset [5386] to RC.)

git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-pre-release@5387 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit 229e197ac7136add799f658682e5411ff5925651 1 parent 503c7c0
@seckar seckar authored
View
2  activesupport/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Update dependencies to allow constants to be defined alongside their siblings. A common case for this is AR model classes with STI; user.rb might define User, Administrator and Guest for example. [Nicholas Seckar]
+
* next_week respects DST changes. #6483 [marclove]
* Expose methods added to Enumerable in the documentation, such as group_by. Closes #6170. [sergeykojin@gmail.com, Marcel Molina Jr.]
View
84 activesupport/lib/active_support/dependencies.rb
@@ -47,6 +47,11 @@ module Dependencies #:nodoc:
mattr_accessor :log_activity
self.log_activity = false
+ # :nodoc:
+ # An internal stack used to record which constants are loaded by any block.
+ mattr_accessor :constant_watch_stack
+ self.constant_watch_stack = []
+
def load?
mechanism == :load
end
@@ -188,11 +193,13 @@ def autoload_module!(into, const_name, qualified_name, path_suffix)
def load_file(path, const_paths = loadable_constants_for_path(path))
log_call path, const_paths
const_paths = [const_paths].compact unless const_paths.is_a? Array
- undefined_before = const_paths.reject(&method(:qualified_const_defined?))
+ parent_paths = const_paths.collect { |const_path| /(.*)::[^:]+\Z/ =~ const_path ? $1 : :Object }
- result = load path
+ result = nil
+ newly_defined_paths = new_constants_in(*parent_paths) do
+ result = load_without_new_constant_marking path
+ end
- newly_defined_paths = undefined_before.select(&method(:qualified_const_defined?))
autoloaded_constants.concat newly_defined_paths
autoloaded_constants.uniq!
log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty?
@@ -290,6 +297,70 @@ def mark_for_unload(const_desc)
end
end
+ # Run the provided block and detect the new constants that were loaded during
+ # its execution. Constants may only be regarded as 'new' once -- so if the
+ # block calls +new_constants_in+ again, then the constants defined within the
+ # inner call will not be reported in this one.
+ def new_constants_in(*descs)
+ log_call(*descs)
+
+ # Build the watch frames. Each frame is a tuple of
+ # [module_name_as_string, constants_defined_elsewhere]
+ watch_frames = descs.collect do |desc|
+ if desc.is_a? Module
+ mod_name = desc.name
+ initial_constants = desc.constants
+ elsif desc.is_a?(String) || desc.is_a?(Symbol)
+ mod_name = desc.to_s
+
+ # Handle the case where the module has yet to be defined.
+ initial_constants = if qualified_const_defined?(mod_name)
+ mod_name.constantize.constants
+ else
+ []
+ end
+ else
+ raise Argument, "#{desc.inspect} does not describe a module!"
+ end
+
+ [mod_name, initial_constants]
+ end
+
+ constant_watch_stack.concat watch_frames
+
+ yield # Now yield to the code that is to define new constants.
+
+ # Find the new constants.
+ new_constants = watch_frames.collect do |mod_name, prior_constants|
+ # Module still doesn't exist? Treat it as if it has no constants.
+ next [] unless qualified_const_defined?(mod_name)
+
+ mod = mod_name.constantize
+ next [] unless mod.is_a? Module
+ new_constants = mod.constants - prior_constants
+
+ # Make sure no other frames takes credit for these constants.
+ constant_watch_stack.each do |frame_name, constants|
+ constants.concat new_constants if frame_name == mod_name
+ end
+
+ new_constants.collect do |suffix|
+ mod_name == "Object" ? suffix : "#{mod_name}::#{suffix}"
+ end
+ end.flatten
+
+ log "New constants: #{new_constants * ', '}"
+ return new_constants
+ ensure
+ # Remove the stack frames that we added.
+ if defined?(watch_frames) && ! watch_frames.empty?
+ frame_ids = watch_frames.collect(&:object_id)
+ constant_watch_stack.delete_if do |watch_frame|
+ frame_ids.include? watch_frame.object_id
+ end
+ end
+ end
+
class LoadingModule
# Old style environment.rb referenced this method directly. Please note, it doesn't
# actualy *do* anything any more.
@@ -389,15 +460,18 @@ def const_missing(class_id)
end
class Object #:nodoc:
+
+ alias_method :load_without_new_constant_marking, :load
+
def load(file, *extras)
- super(file, *extras)
+ Dependencies.new_constants_in(Object) { super(file, *extras) }
rescue Exception => exception # errors from loading file
exception.blame_file! file
raise
end
def require(file, *extras)
- super(file, *extras)
+ Dependencies.new_constants_in(Object) { super(file, *extras) }
rescue Exception => exception # errors from required file
exception.blame_file! file
raise
View
3  activesupport/test/autoloading_fixtures/class_folder/nested_class.rb
@@ -1,4 +1,7 @@
class ClassFolder
class NestedClass
end
+
+ class SiblingClass
+ end
end
View
2  activesupport/test/autoloading_fixtures/multiple_constant_file.rb
@@ -0,0 +1,2 @@
+MultipleConstantFile = 10
+SiblingConstant = MultipleConstantFile * 2
View
147 activesupport/test/dependencies_test.rb
@@ -483,4 +483,151 @@ def test_unloadable_should_return_change_flag
end
end
+ def test_new_contants_in_without_constants
+ assert_equal [], (Dependencies.new_constants_in(Object) { })
+ assert Dependencies.constant_watch_stack.empty?
+ end
+
+ def test_new_constants_in_with_a_single_constant
+ assert_equal(["Hello"], (Dependencies.new_constants_in(Object) do
+ Object.const_set :Hello, 10
+ end))
+ assert Dependencies.constant_watch_stack.empty?
+ ensure
+ Object.send :remove_const, :Hello rescue nil
+ end
+
+ def test_new_constants_in_with_nesting
+ outer = Dependencies.new_constants_in(Object) do
+ Object.const_set :OuterBefore, 10
+
+ inner = Dependencies.new_constants_in(Object) do
+ Object.const_set :Inner, 20
+ end
+ assert_equal ["Inner"], inner
+
+ Object.const_set :OuterAfter, 30
+ end
+
+ assert_equal ["OuterAfter", "OuterBefore"], outer.sort
+ assert Dependencies.constant_watch_stack.empty?
+ ensure
+ %w(OuterBefore Inner OuterAfter).each do |name|
+ Object.send :remove_const, name rescue nil
+ end
+ end
+
+ def test_new_constants_in_module
+ Object.const_set :M, Module.new
+
+ outer = Dependencies.new_constants_in(M) do
+ M.const_set :OuterBefore, 10
+
+ inner = Dependencies.new_constants_in(M) do
+ M.const_set :Inner, 20
+ end
+ assert_equal ["M::Inner"], inner
+
+ M.const_set :OuterAfter, 30
+ end
+ assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
+ assert Dependencies.constant_watch_stack.empty?
+ ensure
+ Object.send :remove_const, :M rescue nil
+ end
+
+ def test_new_constants_in_module_using_name
+ outer = Dependencies.new_constants_in(:M) do
+ Object.const_set :M, Module.new
+ M.const_set :OuterBefore, 10
+
+ inner = Dependencies.new_constants_in(:M) do
+ M.const_set :Inner, 20
+ end
+ assert_equal ["M::Inner"], inner
+
+ M.const_set :OuterAfter, 30
+ end
+ assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
+ assert Dependencies.constant_watch_stack.empty?
+ ensure
+ Object.send :remove_const, :M rescue nil
+ end
+
+ def test_file_with_multiple_constants_and_require_dependency
+ with_loading 'autoloading_fixtures' do
+ assert ! defined?(MultipleConstantFile)
+ assert ! defined?(SiblingConstant)
+
+ require_dependency 'multiple_constant_file'
+ assert defined?(MultipleConstantFile)
+ assert defined?(SiblingConstant)
+ assert Dependencies.autoloaded?(:MultipleConstantFile)
+ assert Dependencies.autoloaded?(:SiblingConstant)
+
+ Dependencies.clear
+
+ assert ! defined?(MultipleConstantFile)
+ assert ! defined?(SiblingConstant)
+ end
+ end
+
+ def test_file_with_multiple_constants_and_auto_loading
+ with_loading 'autoloading_fixtures' do
+ assert ! defined?(MultipleConstantFile)
+ assert ! defined?(SiblingConstant)
+
+ assert_equal 10, MultipleConstantFile
+
+ assert defined?(MultipleConstantFile)
+ assert defined?(SiblingConstant)
+ assert Dependencies.autoloaded?(:MultipleConstantFile)
+ assert Dependencies.autoloaded?(:SiblingConstant)
+
+ Dependencies.clear
+
+ assert ! defined?(MultipleConstantFile)
+ assert ! defined?(SiblingConstant)
+ end
+ end
+
+ def test_nested_file_with_multiple_constants_and_require_dependency
+ with_loading 'autoloading_fixtures' do
+ assert ! defined?(ClassFolder::NestedClass)
+ assert ! defined?(ClassFolder::SiblingClass)
+
+ require_dependency 'class_folder/nested_class'
+
+ assert defined?(ClassFolder::NestedClass)
+ assert defined?(ClassFolder::SiblingClass)
+ assert Dependencies.autoloaded?("ClassFolder::NestedClass")
+ assert Dependencies.autoloaded?("ClassFolder::SiblingClass")
+
+ Dependencies.clear
+
+ assert ! defined?(ClassFolder::NestedClass)
+ assert ! defined?(ClassFolder::SiblingClass)
+ end
+ end
+
+ def test_nested_file_with_multiple_constants_and_auto_loading
+ with_loading 'autoloading_fixtures' do
+ assert ! defined?(ClassFolder::NestedClass)
+ assert ! defined?(ClassFolder::SiblingClass)
+
+ assert_kind_of Class, ClassFolder::NestedClass
+
+ assert defined?(ClassFolder::NestedClass)
+ assert defined?(ClassFolder::SiblingClass)
+ assert Dependencies.autoloaded?("ClassFolder::NestedClass")
+ assert Dependencies.autoloaded?("ClassFolder::SiblingClass")
+
+ Dependencies.clear
+
+ assert ! defined?(ClassFolder::NestedClass)
+ assert ! defined?(ClassFolder::SiblingClass)
+ end
+ end
+
+
end
Please sign in to comment.
Something went wrong with that request. Please try again.