aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib
diff options
context:
space:
mode:
authorNicholas Seckar <nseckar@gmail.com>2006-08-08 21:21:04 +0000
committerNicholas Seckar <nseckar@gmail.com>2006-08-08 21:21:04 +0000
commit74165eb6acaca98d4da13409c4e5b5ecc9d260f7 (patch)
tree024158d20563f34cca52a00b42aaa6c922777559 /activesupport/lib
parent94a1309194fa5962e33d395ede14e94b237c54f5 (diff)
downloadrails-74165eb6acaca98d4da13409c4e5b5ecc9d260f7.tar.gz
rails-74165eb6acaca98d4da13409c4e5b5ecc9d260f7.tar.bz2
rails-74165eb6acaca98d4da13409c4e5b5ecc9d260f7.zip
New dependencies implementation
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4728 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activesupport/lib')
-rw-r--r--activesupport/lib/active_support/dependencies.rb194
-rw-r--r--activesupport/lib/active_support/deprecation.rb24
-rw-r--r--activesupport/lib/active_support/reloadable.rb41
3 files changed, 206 insertions, 53 deletions
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index a58989173e..e845408137 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -22,12 +22,20 @@ module Dependencies #:nodoc:
mattr_accessor :mechanism
self.mechanism = :load
+ # The set of directories from which we may autoload files
+ mattr_accessor :autoload_paths
+ self.autoload_paths = []
+
+ mattr_accessor :autoloaded_constants
+ self.autoloaded_constants = []
+
def load?
mechanism == :load
end
def depend_on(file_name, swallow_load_errors = false)
- require_or_load(file_name)
+ path = search_for_autoload_file(file_name)
+ require_or_load(path || file_name)
rescue LoadError
raise unless swallow_load_errors
end
@@ -38,9 +46,10 @@ module Dependencies #:nodoc:
def clear
loaded.clear
+ remove_autoloaded_constants!
end
- def require_or_load(file_name)
+ def require_or_load(file_name, const_path = nil)
file_name = $1 if file_name =~ /^(.*)\.rb$/
return if loaded.include?(file_name)
@@ -52,10 +61,13 @@ module Dependencies #:nodoc:
begin
# Enable warnings iff 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?
+
if !warnings_on_first_load or history.include?(file_name)
- load "#{file_name}.rb"
+ load_file(*load_args)
else
- enable_warnings { load "#{file_name}.rb" }
+ enable_warnings { load_file(*load_args) }
end
rescue
loaded.delete file_name
@@ -69,9 +81,127 @@ module Dependencies #:nodoc:
history << file_name
end
- # Return the a constant path for the provided parent and constant name
- def constant_path_for(mod, name)
- ([Object, Kernel].include? mod) ? name.to_s : "#{mod}::#{name}"
+ # Is the provided constant path defined?
+ def qualified_const_defined?(path)
+ raise NameError, "#{path.inspect} is not a valid constant name!" unless
+ /^(::)?([A-Z]\w*)(::[A-Z]\w*)*$/ =~ path
+ Object.module_eval("defined?(#{path})", __FILE__, __LINE__)
+ end
+
+ # Given +path+ return an array of constant paths which would cause Dependencies
+ # to attempt to load +path+.
+ def autoloadable_constants_for_path(path)
+ path = $1 if path =~ /\A(.*)\.rb\Z/
+ expanded_path = File.expand_path(path)
+ autoload_paths.collect do |root|
+ expanded_root = File.expand_path root
+ next unless expanded_path.starts_with? expanded_root
+
+ nesting = expanded_path[(expanded_root.size)..-1]
+ nesting = nesting[1..-1] if nesting && nesting[0] == ?/
+ next if nesting.blank?
+
+ nesting.camelize
+ end.compact.uniq
+ end
+
+ # Search for a file in the autoload_paths matching the provided suffix.
+ def search_for_autoload_file(path_suffix)
+ path_suffix = path_suffix + '.rb' unless path_suffix.ends_with? '.rb'
+ autoload_paths.each do |root|
+ path = File.join(root, path_suffix)
+ return path if File.file? path
+ end
+ nil # Gee, I sure wish we had first_match ;-)
+ end
+
+ # Does the provided path_suffix correspond to an autoloadable module?
+ def autoloadable_module?(path_suffix)
+ autoload_paths.any? do |autoload_path|
+ File.directory? File.join(autoload_path, path_suffix)
+ end
+ end
+
+ # Load the file at the provided path. +const_paths+ is a set of qualified
+ # constant names. When loading the file, Dependencies will watch for the
+ # addition of these constants. Each that is defined will be marked as
+ # autoloaded, and will be removed when Dependencies.clear is next called.
+ #
+ # If the second parameter is left off, then Dependencies will construct a set
+ # of names that the file at +path+ may define. See
+ # +autoloadable_constants_for_path+ for more details.
+ def load_file(path, const_paths = autoloadable_constants_for_path(path))
+ const_paths = [const_paths].compact unless const_paths.is_a? Array
+ undefined_before = const_paths.reject(&method(:qualified_const_defined?))
+
+ load path
+
+ autoloaded_constants.concat const_paths.select(&method(:qualified_const_defined?))
+ autoloaded_constants.uniq!
+ end
+
+ # Return the constant path for the provided parent and constant name.
+ def qualified_name_for(mod, name)
+ mod_name = to_constant_name mod
+ (%w(Object Kernel).include? mod_name) ? name.to_s : "#{mod_name}::#{name}"
+ end
+
+ # Load the constant named +const_name+ which is missing from +from_mod+. If
+ # it is not possible to laod the constant into from_mod, try its parent module
+ # using const_missing.
+ def load_missing_constant(from_mod, const_name)
+ qualified_name = qualified_name_for from_mod, const_name
+ path_suffix = qualified_name.underscore
+ name_error = NameError.new("uninitialized constant #{qualified_name}")
+
+ file_path = search_for_autoload_file(path_suffix)
+ if file_path # We found a matching file to load
+ require_or_load file_path, qualified_name
+ raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless from_mod.const_defined?(const_name)
+ return from_mod.const_get(const_name)
+ elsif autoloadable_module? path_suffix # Create modules for directories
+ mod = Module.new
+ from_mod.const_set const_name, mod
+ autoloaded_constants << qualified_name
+ return mod
+ elsif (parent = from_mod.parent) && parent != from_mod &&
+ ! from_mod.parents.any? { |p| p.const_defined?(const_name) }
+ # If our parents do not have a constant named +const_name+ then we are free
+ # to attempt to load upwards. If they do have such a constant, then this
+ # const_missing must be due to from_mod::const_name, which should not
+ # return constants from from_mod's parents.
+ begin
+ return parent.const_missing(const_name)
+ rescue NameError => e
+ raise unless e.missing_name? qualified_name_for(parent, const_name)
+ raise name_error
+ end
+ else
+ raise name_error
+ end
+ end
+
+ # Remove the constants that have been autoloaded.
+ def remove_autoloaded_constants!
+ until autoloaded_constants.empty?
+ const = autoloaded_constants.shift
+ next unless qualified_const_defined? const
+ names = const.split('::')
+ if names.size == 1 || names.first.empty? # It's under Object
+ parent = Object
+ else
+ parent = (names[0..-2] * '::').constantize
+ end
+ parent.send :remove_const, names.last
+ true
+ end
+ end
+
+ # Determine if the given constant has been automatically loaded.
+ def autoloaded?(desc)
+ name = to_constant_name desc
+ return false unless qualified_const_defined? name
+ return autoloaded_constants.include?(name)
end
class LoadingModule
@@ -84,6 +214,20 @@ module Dependencies #:nodoc:
end
end
end
+
+protected
+
+ # Convert the provided const desc to a qualified constant name (as a string).
+ # A module, class, symbol, or string may be provided.
+ def to_constant_name(desc)
+ name = case desc
+ when String then desc.starts_with?('::') ? desc[2..-1] : desc
+ when Symbol then desc.to_s
+ when Module then desc.name
+ else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
+ 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)
@@ -97,37 +241,7 @@ class Module #:nodoc:
# 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
-
- qualified_name = Dependencies.constant_path_for self, class_id
- raise NameError.new("uninitialized constant #{qualified_name}").copy_blame!(e)
- end
+ Dependencies.load_missing_constant self, class_id
end
end
@@ -140,9 +254,9 @@ class Class
parent.send :const_missing, class_id
rescue NameError => e
# Make sure that the name we are missing is the one that caused the error
- parent_qualified_name = Dependencies.constant_path_for parent, class_id
+ parent_qualified_name = Dependencies.qualified_name_for parent, class_id
raise unless e.missing_name? parent_qualified_name
- qualified_name = Dependencies.constant_path_for self, class_id
+ qualified_name = Dependencies.qualified_name_for self, class_id
raise NameError.new("uninitialized constant #{qualified_name}").copy_blame!(e)
end
end
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 5aff753616..d6c9317f06 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -49,24 +49,30 @@ module ActiveSupport
module Assertions
def assert_deprecated(match = nil, &block)
- last = with_last_message_tracking_deprecation_behavior(&block)
+ last = collect_deprecations(&block).last
assert last, "Expected a deprecation warning within the block but received none"
- match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp)
- assert_match match, last, "Deprecation warning didn't match #{match}: #{last}"
+ if match
+ match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp)
+ assert_match match, last, "Deprecation warning didn't match #{match}: #{last}"
+ end
end
def assert_not_deprecated(&block)
- last = with_last_message_tracking_deprecation_behavior(&block)
- assert_nil last, "Expected no deprecation warning within the block but received one: #{last}"
+ deprecations = collect_deprecations(&block)
+ assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}"
end
private
- def with_last_message_tracking_deprecation_behavior
+
+ def collect_deprecations
old_behavior = ActiveSupport::Deprecation.behavior
- last_message = nil
- ActiveSupport::Deprecation.behavior = Proc.new { |message| last_message = message; old_behavior.call(message) if old_behavior }
+ deprecations = []
+ ActiveSupport::Deprecation.behavior = Proc.new do |message|
+ deprecations << message
+ old_behavior.call(message) if old_behavior
+ end
yield
- last_message
+ return deprecations
ensure
ActiveSupport::Deprecation.behavior = old_behavior
end
diff --git a/activesupport/lib/active_support/reloadable.rb b/activesupport/lib/active_support/reloadable.rb
index 3fd13e3d0f..3f57723e15 100644
--- a/activesupport/lib/active_support/reloadable.rb
+++ b/activesupport/lib/active_support/reloadable.rb
@@ -1,13 +1,25 @@
-# Classes that include this module will automatically be reloaded
-# by the Rails dispatcher when Dependencies.mechanism = :load.
+require 'active_support/deprecation'
+
+# A deprecated mechanism to mark a class reloadable.
+#
+# Deprecated as of Rails 1.2.
+# All autoloaded objects are now unloaded.
module Reloadable
class << self
+
def included(base) #nodoc:
+ unless base.ancestors.include?(Reloadable::Subclasses) # Avoid double warning
+ ActiveSupport::Deprecation.warn "Reloadable has been deprecated and has no effect.", caller
+ end
+
raise TypeError, "Only Classes can be Reloadable!" unless base.is_a? Class
unless base.respond_to?(:reloadable?)
class << base
- define_method(:reloadable?) { true }
+ define_method(:reloadable?) do
+ ActiveSupport::Deprecation.warn "Reloadable has been deprecated and reloadable? has no effect", caller
+ true
+ end
end
end
end
@@ -15,16 +27,37 @@ module Reloadable
def reloadable_classes
included_in_classes.select { |klass| klass.reloadable? }
end
+ # Commented out so dispatcher doesn't warn. Should we just disable Reloadable?
+ # deprecate :reloadable_classes
end
# Captures the common pattern where a base class should not be reloaded,
# but its subclasses should be.
+ #
+ # Deprecated as of Rails 1.2.
+ # All autoloaded objects are now unloaded.
module Subclasses
def self.included(base) #nodoc:
base.send :include, Reloadable
+ ActiveSupport::Deprecation.warn "Reloadable::Subclasses has been deprecated and has no effect.", caller
(class << base; self; end).send(:define_method, :reloadable?) do
- base != self
+ ActiveSupport::Deprecation.warn "Reloadable has been deprecated and reloadable? has no effect", caller
+ base != self
end
end
end
+
+ module Deprecated
+
+ def self.included(base)
+ class << base
+ define_method(:reloadable?) do
+ ActiveSupport::Deprecation.warn "Reloadable has been deprecated and reloadable? has no effect", caller
+ true # This might not have the desired effect, as AR::B.reloadable? => true.
+ end
+ end
+ end
+
+ end
+
end \ No newline at end of file