diff options
Diffstat (limited to 'activesupport')
-rw-r--r-- | activesupport/CHANGELOG | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/dependencies.rb | 84 | ||||
-rw-r--r-- | activesupport/test/autoloading_fixtures/class_folder/nested_class.rb | 3 | ||||
-rw-r--r-- | activesupport/test/autoloading_fixtures/multiple_constant_file.rb | 2 | ||||
-rw-r--r-- | activesupport/test/dependencies_test.rb | 147 |
5 files changed, 233 insertions, 5 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 8b7d421473..f383e325e9 100644 --- a/activesupport/CHANGELOG +++ b/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.] diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index d124c657e5..270496ab15 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/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 @@ 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 diff --git a/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb b/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb index 08bed842cf..77ea230d2c 100644 --- a/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb +++ b/activesupport/test/autoloading_fixtures/class_folder/nested_class.rb @@ -1,4 +1,7 @@ class ClassFolder class NestedClass end + + class SiblingClass + end end diff --git a/activesupport/test/autoloading_fixtures/multiple_constant_file.rb b/activesupport/test/autoloading_fixtures/multiple_constant_file.rb new file mode 100644 index 0000000000..a9ff4eb89c --- /dev/null +++ b/activesupport/test/autoloading_fixtures/multiple_constant_file.rb @@ -0,0 +1,2 @@ +MultipleConstantFile = 10 +SiblingConstant = MultipleConstantFile * 2 diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 4065625e70..17c58eca46 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -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 |