lib/active_support/dependencies.rb in activesupport-1.2.5 vs lib/active_support/dependencies.rb in activesupport-1.3.0
- old
+ new
@@ -1,209 +1,131 @@
-require File.dirname(__FILE__) + '/module_attribute_accessors'
+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
- @@loaded = [ ]
+ # 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
- @@mechanism = :load
+ # 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)
- unless loaded.include?(file_name)
- loaded << file_name
- begin
- require_or_load(file_name)
- rescue LoadError
- raise unless swallow_load_errors
- end
- 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
- self.loaded = [ ]
+ loaded.clear
end
-
+
def require_or_load(file_name)
- file_name = "#{file_name}.rb" unless ! load? || file_name[-3..-1] == '.rb'
- load? ? load(file_name) : require(file_name)
- end
-
- def remove_subclasses_for(*classes)
- Object.remove_subclasses_of(*classes)
- end
-
- # LoadingModules implement namespace-safe dynamic loading.
- # They support automatic loading via const_missing, allowing contained items to be automatically
- # loaded when required. No extra syntax is required, as expressions such as Controller::Admin::UserController
- # load the relavent files automatically.
- #
- # Ruby-style modules are supported, as a folder named 'submodule' will load 'submodule.rb' when available.
- class LoadingModule < Module #:nodoc:
- attr_reader :path
- attr_reader :root
-
- class << self
- def root(*load_paths)
- RootLoadingModule.new(*load_paths)
- end
- end
-
- def initialize(root, path=[])
- @path = path.clone.freeze
- @root = root
- end
-
- def root?() self.root == self end
- def load_paths() self.root.load_paths end
-
- # Load missing constants if possible.
- def const_missing(name)
- const_load!(name) ? const_get(name) : super(name)
- end
-
- # Load the controller class or a parent module.
- def const_load!(name, file_name = nil)
- file_name ||= 'application' if root? && name.to_s == 'ApplicationController'
- path = self.path + [file_name || name]
+ file_name = $1 if file_name =~ /^(.*)\.rb$/
+ return if loaded.include?(file_name)
- load_paths.each do |load_path|
- fs_path = load_path.filesystem_path(path)
- next unless fs_path
+ # Record that we've seen this file *before* loading it to avoid an
+ # infinite loop with mutual dependencies.
+ loaded << file_name
- case
- when File.directory?(fs_path)
- new_module = LoadingModule.new(self.root, self.path + [name])
- self.const_set name, new_module
- if self.root?
- if Object.const_defined?(name)
- msg = "Cannot load module #{name}: Object::#{name} is set to #{Object.const_get(name).inspect}"
- raise NameError, msg
- end
- Object.const_set(name, new_module)
- end
- break
- when File.file?(fs_path)
- loaded_file = self.root.load_file!(fs_path)
-
- # Import the loaded constant from Object provided we are the root node.
- self.const_set(name, Object.const_get(name)) if self.root? && Object.const_defined?(name)
-
- # Throw an error if we load the file but we don't find the Object we expect
- if loaded_file and not self.const_defined?(name)
- msg = "Already loaded file '#{fs_path}' but '#{name.to_s}' was not set, perhaps you need to rename '#{fs_path}'?"
- raise LoadError, msg
- end
- break
+ 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
-
- self.const_defined?(name)
+ else
+ require file_name
end
-
- # Is this name present or loadable?
- # This method is used by Routes to find valid controllers.
- def const_available?(name)
- self.const_defined?(name) || load_paths.any? {|lp| lp.filesystem_path(path + [name])}
- end
- end
-
- class RootLoadingModule < LoadingModule #:nodoc:
- attr_reader :load_paths
- def initialize(*paths)
- @load_paths = paths.flatten.collect {|p| p.kind_of?(ConstantLoadPath) ? p : ConstantLoadPath.new(p)}
- end
-
- def root() self end
-
- def path() [] end
-
- # Load the source file at the given file path
- def load_file!(file_path)
- require_dependency(file_path)
- end
-
- # Erase all items in this module
- def clear!
- constants.each do |name|
- Object.send(:remove_const, name) if Object.const_defined?(name) && Object.const_get(name).object_id == self.const_get(name).object_id
- self.send(:remove_const, name)
- end
- end
+ # Record history *after* loading so first load gets warnings.
+ history << file_name
end
-
- # This object defines a path from which Constants can be loaded.
- class ConstantLoadPath #:nodoc:
- # Create a new load path with the filesystem path
- def initialize(root) @root = root end
-
- # Return nil if the path does not exist, or the path to a directory
- # if the path leads to a module, or the path to a file if it leads to an object.
- def filesystem_path(path, allow_module=true)
- fs_path = [@root]
- fs_path += path[0..-2].map {|name| const_name_to_module_name name}
-
- if allow_module
- result = File.join(fs_path, const_name_to_module_name(path.last))
- return result if File.directory? result # Return the module path if one exists
- end
-
- result = File.join(fs_path, const_name_to_file_name(path.last))
-
- File.file?(result) ? result : nil
- end
-
- def const_name_to_file_name(name)
- name.to_s.underscore + '.rb'
- end
-
- def const_name_to_module_name(name)
- name.to_s.underscore
- end
- 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)
- if Object.const_defined?(:Controllers) and Object::Controllers.const_available?(class_id)
- return Object::Controllers.const_get(class_id)
- end
-
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_name)
- raise NameError.new("uninitialized constant #{class_id}") unless Object.const_defined?(class_id)
- return Object.const_get(class_id)
+ 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
- # Convert the exception to a NameError only if the file we are looking for is the missing one.
- raise unless e.is_missing? file_name
+ # 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
@@ -235,6 +157,6 @@
def copy_blame!(exc)
@blamed_files = exc.blamed_files.clone
self
end
-end
+end
\ No newline at end of file