Update dependencies to allow constants to be defined alongside their siblings.

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@5386 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
Nicholas Seckar
2006-11-01 23:21:13 +00:00
parent 70840d4b7f
commit 5cc682da07
5 changed files with 233 additions and 5 deletions

View File

@@ -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 File

@@ -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 @@ module Dependencies #:nodoc:
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 @@ module Dependencies #:nodoc:
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 @@ class Class
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 File

@@ -1,4 +1,7 @@
class ClassFolder
class NestedClass
end
class SiblingClass
end
end

View File

@@ -0,0 +1,2 @@
MultipleConstantFile = 10
SiblingConstant = MultipleConstantFile * 2

View File

@@ -483,4 +483,151 @@ class DependenciesTest < Test::Unit::TestCase
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