aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/dependencies.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support/dependencies.rb')
-rw-r--r--activesupport/lib/active_support/dependencies.rb190
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