Skip to content

Commit

Permalink
Update dependencies to allow constants to be defined alongside their …
Browse files Browse the repository at this point in the history
…siblings.

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@5386 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information
seckar committed Nov 1, 2006
1 parent 70840d4 commit 5cc682d
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 5 deletions.
2 changes: 2 additions & 0 deletions activesupport/CHANGELOG
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,7 @@
*SVN* *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] * 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.] * Expose methods added to Enumerable in the documentation, such as group_by. Closes #6170. [sergeykojin@gmail.com, Marcel Molina Jr.]
Expand Down
84 changes: 79 additions & 5 deletions activesupport/lib/active_support/dependencies.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ module Dependencies #:nodoc:
mattr_accessor :log_activity mattr_accessor :log_activity
self.log_activity = false 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? def load?
mechanism == :load mechanism == :load
end end
Expand Down Expand Up @@ -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)) def load_file(path, const_paths = loadable_constants_for_path(path))
log_call path, const_paths log_call path, const_paths
const_paths = [const_paths].compact unless const_paths.is_a? Array 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.concat newly_defined_paths
autoloaded_constants.uniq! autoloaded_constants.uniq!
log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty? log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty?
Expand Down Expand Up @@ -290,6 +297,70 @@ def mark_for_unload(const_desc)
end end
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 class LoadingModule
# Old style environment.rb referenced this method directly. Please note, it doesn't # Old style environment.rb referenced this method directly. Please note, it doesn't
# actualy *do* anything any more. # actualy *do* anything any more.
Expand Down Expand Up @@ -389,15 +460,18 @@ def const_missing(class_id)
end end


class Object #:nodoc: class Object #:nodoc:

alias_method :load_without_new_constant_marking, :load

def load(file, *extras) def load(file, *extras)
super(file, *extras) Dependencies.new_constants_in(Object) { super(file, *extras) }
rescue Exception => exception # errors from loading file rescue Exception => exception # errors from loading file
exception.blame_file! file exception.blame_file! file
raise raise
end end


def require(file, *extras) def require(file, *extras)
super(file, *extras) Dependencies.new_constants_in(Object) { super(file, *extras) }
rescue Exception => exception # errors from required file rescue Exception => exception # errors from required file
exception.blame_file! file exception.blame_file! file
raise raise
Expand Down
Original file line number Original file line Diff line number Diff line change
@@ -1,4 +1,7 @@
class ClassFolder class ClassFolder
class NestedClass class NestedClass
end end

class SiblingClass
end
end end
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,2 @@
MultipleConstantFile = 10
SiblingConstant = MultipleConstantFile * 2
147 changes: 147 additions & 0 deletions activesupport/test/dependencies_test.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -483,4 +483,151 @@ def test_unloadable_should_return_change_flag
end end
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 end

0 comments on commit 5cc682d

Please sign in to comment.