require 'set'
require 'thread'
require 'pathname'
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/qualified_const'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/name_error'
require 'active_support/core_ext/string/starts_ends_with'
require 'active_support/inflector'
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
extend self
# Should we turn on Ruby warnings on the first load of dependent files?
mattr_accessor :warnings_on_first_load
self.warnings_on_first_load = false
# All files ever loaded.
mattr_accessor :history
self.history = Set.new
# All files currently loaded.
mattr_accessor :loaded
self.loaded = Set.new
# Should we load files or require them?
mattr_accessor :mechanism
self.mechanism = ENV['NO_RELOAD'] ? :require : :load
# The set of directories from which we may automatically load files. Files
# under these directories will be reloaded on each request in development mode,
# unless the directory also appears in autoload_once_paths.
mattr_accessor :autoload_paths
self.autoload_paths = []
# The set of directories from which automatically loaded constants are loaded
# only once. All directories in this set must also be present in +autoload_paths+.
mattr_accessor :autoload_once_paths
self.autoload_once_paths = []
# An array of qualified constant names that have been loaded. Adding a name to
# this array will cause it to be unloaded the next time Dependencies are cleared.
mattr_accessor :autoloaded_constants
self.autoloaded_constants = []
# 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
self.explicitly_unloadable_constants = []
# The logger is used for generating information on the action run-time (including benchmarking) if available.
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
mattr_accessor :logger
# Set to true to enable logging of const_missing and file loads
mattr_accessor :log_activity
self.log_activity = false
# 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
include Enumerable
# @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
@watching = []
@stack = Hash.new { |h,k| h[k] = [] }
end
def each(&block)
@stack.each(&block)
end
def watching?
!@watching.empty?
end
# return a list of new constants found since the last call to watch_namespaces
def new_constants
constants = []
# 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 = @stack[namespace].last
mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace)
next unless mod.is_a?(Module)
# 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.
@stack[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 << ([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 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
@stack[module_name] << original_constants
end
@watching << watching
end
private
def pop_modules(modules)
modules.each { |mod| @stack[mod].pop }
end
end
# An internal stack used to record which constants are loaded by any block.
mattr_accessor :constant_watch_stack
self.constant_watch_stack = WatchStack.new
# Module includes this module
module ModuleConstMissing #:nodoc:
def self.append_features(base)
base.class_eval do
# Emulate #exclude via an ivar
return if defined?(@_const_missing) && @_const_missing
@_const_missing = instance_method(:const_missing)
remove_method(:const_missing)
end
super
end
def self.exclude_from(base)
base.class_eval do
define_method :const_missing, @_const_missing
@_const_missing = nil
end
end
# Use const_missing to autoload associations so we don't have to
# require_association when using single-table inheritance.
def const_missing(const_name, nesting = nil)
klass_name = name.presence || "Object"
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
nesting = []
klass_name.to_s.scan(/::|$/) { nesting.unshift $` }
end
# If there are multiple levels of nesting to search under, the top
# level is the one we want to report as the lookup fail.
error = nil
nesting.each do |namespace|
begin
return Dependencies.load_missing_constant Inflector.constantize(namespace), const_name
rescue NoMethodError then raise
rescue NameError => e
error ||= e
end
end
# Raise the first error for this set. If this const_missing came from an
# earlier const_missing, this will result in the real error bubbling
# all the way up
raise error
end
def unloadable(const_desc = self)
super(const_desc)
end
end
# Object includes this module
module Loadable #:nodoc:
def self.exclude_from(base)
base.class_eval { define_method(:load, Kernel.instance_method(:load)) }
end
def require_or_load(file_name)
Dependencies.require_or_load(file_name)
end
def require_dependency(file_name, message = "No such file to load -- %s")
unless file_name.is_a?(String)
raise ArgumentError, "the file name must be a String -- you passed #{file_name.inspect}"
end
Dependencies.depend_on(file_name, false, message)
end
def require_association(file_name)
Dependencies.associate_with(file_name)
end
def load_dependency(file)
if Dependencies.load? && ActiveSupport::Dependencies.constant_watch_stack.watching?
Dependencies.new_constants_in(Object) { yield }
else
yield
end
rescue Exception => exception # errors from loading file
exception.blame_file! file
raise
end
def load(file, wrap = false)
result = false
load_dependency(file) { result = super }
result
end
def require(file)
result = false
load_dependency(file) { result = super }
result
end
# Mark the given constant as unloadable. Unloadable constants are removed each
# time dependencies are cleared.
#
# Note that marking a constant for unloading need only be done once. Setup
# or init scripts may list each unloadable constant that may need unloading;
# each constant will be removed for every subsequent clear, as opposed to for
# the first clear.
#
# The provided constant descriptor may be a (non-anonymous) module or class,
# or a qualified constant name as a string or symbol.
#
# Returns true if the constant was not previously marked for unloading, false
# otherwise.
def unloadable(const_desc)
Dependencies.mark_for_unload const_desc
end
end
# Exception file-blaming
module Blamable #:nodoc:
def blame_file!(file)
(@blamed_files ||= []).unshift file
end
def blamed_files
@blamed_files ||= []
end
def describe_blame
return nil if blamed_files.empty?
"This error occurred while loading the following files:\n #{blamed_files.join "\n "}"
end
def copy_blame!(exc)
@blamed_files = exc.blamed_files.clone
self
end
end
def hook!
Object.class_eval { include Loadable }
Module.class_eval { include ModuleConstMissing }
Exception.class_eval { include Blamable }
true
end
def unhook!
ModuleConstMissing.exclude_from(Module)
Loadable.exclude_from(Object)
true
end
def load?
mechanism == :load
end
def depend_on(file_name, swallow_load_errors = false, message = "No such file to load -- %s.rb")
path = search_for_file(file_name)
require_or_load(path || file_name)
rescue LoadError => load_error
unless swallow_load_errors
if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1]
raise LoadError.new(message % file_name).copy_blame!(load_error)
end
raise
end
end
def associate_with(file_name)
depend_on(file_name, true)
end
def clear
log_call
loaded.clear
remove_unloadable_constants!
end
def require_or_load(file_name, const_path = nil)
log_call file_name, const_path
file_name = $1 if file_name =~ /^(.*)\.rb$/
expanded = File.expand_path(file_name)
return if loaded.include?(expanded)
# Record that we've seen this file *before* loading it to avoid an
# infinite loop with mutual dependencies.
loaded << expanded
begin
if load?
log "loading #{file_name}"
Loading ...