mirror of
https://github.com/github/rails.git
synced 2026-01-30 08:48:06 -05:00
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:
@@ -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.]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
class ClassFolder
|
||||
class NestedClass
|
||||
end
|
||||
|
||||
class SiblingClass
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
MultipleConstantFile = 10
|
||||
SiblingConstant = MultipleConstantFile * 2
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user