require 'set'
require File.dirname(__FILE__) + '/core_ext/module/attribute_accessors'
require File.dirname(__FILE__) + '/core_ext/load_error'
require File.dirname(__FILE__) + '/core_ext/kernel'
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 = :load
def load?
mechanism == :load
end
def depend_on(file_name, swallow_load_errors = false)
require_or_load(file_name)
rescue LoadError
raise unless swallow_load_errors
end
def associate_with(file_name)
depend_on(file_name, true)
end
def clear
loaded.clear
end
def require_or_load(file_name)
file_name = $1 if file_name =~ /^(.*)\.rb$/
return if loaded.include?(file_name)
# Record that we've seen this file *before* loading it to avoid an
# infinite loop with mutual dependencies.
loaded << file_name
if load?
begin
# Enable warnings iff this file has not been loaded before and
# warnings_on_first_load is set.
if !warnings_on_first_load or history.include?(file_name)
load "#{file_name}.rb"
else
enable_warnings { load "#{file_name}.rb" }
end
rescue
loaded.delete file_name
raise
end
else
require file_name
end
# Record history *after* loading so first load gets warnings.
history << file_name
end
end
Object.send(:define_method, :require_or_load) { |file_name| Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load)
Object.send(:define_method, :require_dependency) { |file_name| Dependencies.depend_on(file_name) } unless Object.respond_to?(:require_dependency)
Object.send(:define_method, :require_association) { |file_name| Dependencies.associate_with(file_name) } unless Object.respond_to?(:require_association)
class Module #:nodoc:
# Rename the original handler so we can chain it to the new one
alias :rails_original_const_missing :const_missing
# Use const_missing to autoload associations so we don't have to
# require_association when using single-table inheritance.
def const_missing(class_id)
file_name = class_id.to_s.demodulize.underscore
file_path = as_load_path.empty? ? file_name : "#{as_load_path}/#{file_name}"
begin
require_dependency(file_path)
brief_name = self == Object ? '' : "#{name}::"
raise NameError.new("uninitialized constant #{brief_name}#{class_id}") unless const_defined?(class_id)
return const_get(class_id)
rescue MissingSourceFile => e
# Re-raise the error if it does not concern the file we were trying to load.
raise unless e.is_missing? file_path
# Look for a directory in the load path that we ought to load.
if $LOAD_PATH.any? { |base| File.directory? "#{base}/#{file_path}" }
mod = Module.new
const_set class_id, mod # Create the new module
return mod
end
# Attempt to access the name from the parent, unless we don't have a valid
# parent, or the constant is already defined in the parent. If the latter
# is the case, then we are being queried via self::class_id, and we should
# avoid returning the constant from the parent if possible.
if parent && parent != self && ! parents.any? { |p| p.const_defined?(class_id) }
suppress(NameError) do
return parent.send(:const_missing, class_id)
end
end
raise NameError.new("uninitialized constant #{class_id}").copy_blame!(e)
end
end
end
class Class
def const_missing(class_id)
if [Object, Kernel].include?(self) || parent == self
super
else
parent.send :const_missing, class_id
end
end
end
class Object #:nodoc:
def load(file, *extras)
super(file, *extras)
rescue Object => exception
exception.blame_file! file
raise
end
def require(file, *extras)
super(file, *extras)
rescue Object => exception
exception.blame_file! file
raise
end
end
# Add file-blaming to exceptions
class Exception #: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 occured while loading the following files:\n #{blamed_files.join "\n "}"
end
def copy_blame!(exc)
@blamed_files = exc.blamed_files.clone
self
end
end