diff options
Diffstat (limited to 'activesupport/lib/active_support/dependencies.rb')
-rw-r--r-- | activesupport/lib/active_support/dependencies.rb | 190 |
1 files changed, 130 insertions, 60 deletions
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 2b80bd214f..dc10f78104 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -5,6 +5,7 @@ require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/module/introspection' require 'active_support/core_ext/module/anonymous' +require 'active_support/core_ext/module/deprecation' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/load_error' require 'active_support/core_ext/name_error' @@ -47,9 +48,6 @@ module ActiveSupport #:nodoc: mattr_accessor :autoloaded_constants self.autoloaded_constants = [] - mattr_accessor :references - self.references = {} - # An array of constant names that need to be unloaded on every request. Used # to allow arbitrary constants to be marked for unloading. mattr_accessor :explicitly_unloadable_constants @@ -63,58 +61,78 @@ module ActiveSupport #:nodoc: mattr_accessor :log_activity self.log_activity = false - class WatchStack < Array + # The WatchStack keeps a stack of the modules being watched as files are loaded. + # If a file in the process of being loaded (parent.rb) triggers the load of + # another file (child.rb) the stack will ensure that child.rb handles the new + # constants. + # + # If child.rb is being autoloaded, its constants will be added to + # autoloaded_constants. If it was being `require`d, they will be discarded. + # + # This is handled by walking back up the watch stack and adding the constants + # found by child.rb to the list of original constants in parent.rb + class WatchStack < Hash + # @watching is a stack of lists of constants being watched. For instance, + # if parent.rb is autoloaded, the stack will look like [[Object]]. If parent.rb + # then requires namespace/child.rb, the stack will look like [[Object], [Namespace]]. + def initialize - @mutex = Mutex.new + @watching = [] + super { |h,k| h[k] = [] } end - def self.locked(*methods) - methods.each { |m| class_eval "def #{m}(*) lock { super } end", __FILE__, __LINE__ } - end + # return a list of new constants found since the last call to watch_namespaces + def new_constants + constants = [] - locked :concat, :each, :delete_if, :<< + # Grab the list of namespaces that we're looking for new constants under + @watching.last.each do |namespace| + # Retrieve the constants that were present under the namespace when watch_namespaces + # was originally called + original_constants = self[namespace].last - def new_constants_for(frames) - constants = [] - frames.each do |mod_name, prior_constants| - mod = Inflector.constantize(mod_name) if Dependencies.qualified_const_defined?(mod_name) + mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace) next unless mod.is_a?(Module) - new_constants = mod.local_constant_names - prior_constants - - # If we are checking for constants under, say, :Object, nested under something - # else that is checking for constants also under :Object, make sure the - # parent knows that we have found, and taken care of, the constant. - # - # In particular, this means that since Kernel.require discards the constants - # it finds, parents will be notified that about those constants, and not - # consider them "new". As a result, they will not be added to the - # autoloaded_constants list. - each do |key, value| - value.concat(new_constants) if key == mod_name + # Get a list of the constants that were added + new_constants = mod.local_constant_names - original_constants + + # self[namespace] returns an Array of the constants that are being evaluated + # for that namespace. For instance, if parent.rb requires child.rb, the first + # element of self[Object] will be an Array of the constants that were present + # before parent.rb was required. The second element will be an Array of the + # constants that were present before child.rb was required. + self[namespace].each do |namespace_constants| + namespace_constants.concat(new_constants) end + # Normalize the list of new constants, and add them to the list we will return new_constants.each do |suffix| - constants << ([mod_name, suffix] - ["Object"]).join("::") + constants << ([namespace, suffix] - ["Object"]).join("::") end end constants + ensure + # A call to new_constants is always called after a call to watch_namespaces + pop_modules(@watching.pop) end # Add a set of modules to the watch stack, remembering the initial constants - def add_modules(modules) - list = modules.map do |desc| - name = Dependencies.to_constant_name(desc) - consts = Dependencies.qualified_const_defined?(name) ? - Inflector.constantize(name).local_constant_names : [] - [name, consts] + def watch_namespaces(namespaces) + watching = [] + namespaces.map do |namespace| + module_name = Dependencies.to_constant_name(namespace) + original_constants = Dependencies.qualified_const_defined?(module_name) ? + Inflector.constantize(module_name).local_constant_names : [] + + watching << module_name + self[module_name] << original_constants end - concat(list) - list + @watching << watching end - def lock - @mutex.synchronize { yield self } + def pop_modules(modules) + modules.each { |mod| self[mod].pop } end end @@ -146,7 +164,7 @@ module ActiveSupport #:nodoc: def const_missing(const_name, nesting = nil) klass_name = name.presence || "Object" - if !nesting + unless nesting # We'll assume that the nesting of Foo::Bar is ["Foo::Bar", "Foo"] # even though it might not be, such as in the case of # class Foo::Bar; Baz; end @@ -311,7 +329,7 @@ module ActiveSupport #:nodoc: if load? log "loading #{file_name}" - # Enable warnings iff this file has not been loaded before and + # Enable warnings if this file has not been loaded before and # warnings_on_first_load is set. load_args = ["#{file_name}.rb"] load_args << const_path unless const_path.nil? @@ -491,7 +509,12 @@ module ActiveSupport #:nodoc: end # Remove the constants that have been autoloaded, and those that have been - # marked for unloading. + # marked for unloading. Before each constant is removed a callback is sent + # to its class/module if it implements +before_remove_const+. + # + # The callback implementation should be restricted to cleaning up caches, etc. + # as the environment will be in an inconsistent state, e.g. other constants + # may have already been unloaded and not accessible. def remove_unloadable_constants! autoloaded_constants.each { |const| remove_constant const } autoloaded_constants.clear @@ -499,31 +522,76 @@ module ActiveSupport #:nodoc: explicitly_unloadable_constants.each { |const| remove_constant const } end - class Reference - @@constants = Hash.new { |h, k| h[k] = Inflector.constantize(k) } + class ClassCache + def initialize + @store = Hash.new { |h, k| h[k] = Inflector.constantize(k) } + end - attr_reader :name + def empty? + @store.empty? + end - def initialize(name) - @name = name.to_s - @@constants[@name] = name if name.respond_to?(:name) + def key?(key) + @store.key?(key) end - def get - @@constants[@name] + def []=(key, value) + return unless key.respond_to?(:name) + + raise(ArgumentError, 'anonymous classes cannot be cached') if key.name.blank? + + @store[key.name] = value end - def self.clear! - @@constants.clear + def [](key) + key = key.name if key.respond_to?(:name) + + @store[key] + end + alias :get :[] + + class Getter # :nodoc: + def initialize(name) + @name = name + end + + def get + Reference.get @name + end + deprecate :get + end + + def new(name) + self[name] = name + Getter.new(name) + end + deprecate :new + + def store(name) + self[name] = name + self + end + + def clear! + @store.clear end end + Reference = ClassCache.new + def ref(name) - references[name] ||= Reference.new(name) + Reference.new(name) + end + deprecate :ref + + # Store a reference to a class +klass+. + def reference(klass) + Reference.store klass end + # Get the reference for class named +name+. def constantize(name) - ref(name).get + Reference.get(name) end # Determine if the given constant has been automatically loaded. @@ -563,14 +631,15 @@ module ActiveSupport #:nodoc: # and will be removed immediately. def new_constants_in(*descs) log_call(*descs) - watch_frames = constant_watch_stack.add_modules(descs) + constant_watch_stack.watch_namespaces(descs) aborting = true + begin yield # Now yield to the code that is to define new constants. aborting = false ensure - new_constants = constant_watch_stack.new_constants_for(watch_frames) + new_constants = constant_watch_stack.new_constants log "New constants: #{new_constants * ', '}" return new_constants unless aborting @@ -580,9 +649,6 @@ module ActiveSupport #:nodoc: end return [] - ensure - # Remove the stack frames that we added. - watch_frames.each {|f| constant_watch_stack.delete(f) } if watch_frames.present? end class LoadingModule #:nodoc: @@ -618,6 +684,8 @@ module ActiveSupport #:nodoc: parent = Inflector.constantize(names * '::') log "removing constant #{const}" + constantized = constantize(const) + constantized.before_remove_const if constantized.respond_to?(:before_remove_const) parent.instance_eval { remove_const to_remove } return true @@ -625,7 +693,7 @@ module ActiveSupport #:nodoc: protected def log_call(*args) - if logger && log_activity + if log_activity? arg_str = args.collect { |arg| arg.inspect } * ', ' /in `([a-z_\?\!]+)'/ =~ caller(1).first selector = $1 || '<unknown>' @@ -634,9 +702,11 @@ module ActiveSupport #:nodoc: end def log(msg) - if logger && log_activity - logger.debug "Dependencies: #{msg}" - end + logger.debug "Dependencies: #{msg}" if log_activity? + end + + def log_activity? + logger && log_activity end end end |