Added reusable reloading support through the inclusion of the Relodable module that all subclasses of ActiveRecord::Base, ActiveRecord::Observer, ActiveController::Base, and ActionMailer::Base automatically gets [DHH]. Added auto-loading support for classes in modules, so Conductor::Migration will look for conductor/migration.rb and Conductor::Database::Settings will look for conductor/database/settings.rb [Nicholas Seckar]. Refactored extensions to module, class, and object in active support [DHH]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3493 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
David Heinemeier Hansson 2006-01-29 00:37:39 +00:00
parent 94046542e2
commit 01b1a87729
36 changed files with 298 additions and 74 deletions

View File

@ -121,6 +121,13 @@ module ActionMailer
class Base
include AdvAttrAccessor, PartContainer
# Action Mailer subclasses should be reloaded by the dispatcher in Rails
# when Dependencies.mechanism = :load.
def self.inherited(child) #:nodoc:
child.send :include, Reloadable
super
end
private_class_method :new #:nodoc:
cattr_accessor :template_root

View File

@ -224,6 +224,13 @@ module ActionController #:nodoc:
# FCGI.each_cgi{ |cgi| WeblogController.process_cgi(cgi) }
class Base
DEFAULT_RENDER_STATUS_CODE = "200 OK"
# Action Controller subclasses should be reloaded by the dispatcher in Rails
# when Dependencies.mechanism = :load.
def self.inherited(child) #:nodoc:
child.send :include, Reloadable
super
end
# Determines whether the view has access to controller internals @request, @response, @session, and @template.
# By default, it does.

View File

@ -1,16 +1,16 @@
module ActionController #:nodoc:
module Layout #:nodoc:
def self.append_features(base)
super
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
alias_method :render_with_no_layout, :render
alias_method :render, :render_with_a_layout
class << self
alias_method :inherited_without_layout, :inherited
alias_method :inherited, :inherited_with_layout
end
end
base.extend(ClassMethods)
end
# Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
@ -172,8 +172,9 @@ module ActionController #:nodoc:
end
private
def inherited(child)
def inherited_with_layout(child)
inherited_without_layout(child)
child.send :include, Reloadable
layout_match = child.name.underscore.sub(/_controller$/, '')
child.layout(layout_match) unless layout_list.grep(%r{layouts/#{layout_match}\.[a-z][0-9a-z]*$}).empty?
end

View File

@ -333,6 +333,13 @@ module ActionController #:nodoc:
end
end
def build_request_uri(action, parameters)
options = @controller.send(:rewrite_options, parameters)
options.update(:only_path => true, :action => action)
url = ActionController::UrlRewriter.new(@request, parameters)
@request.set_REQUEST_URI(url.rewrite(options))
end
def session
@response.session
end

View File

@ -3,7 +3,7 @@ require "#{File.dirname(__FILE__)}/../testing_sandbox"
require File.dirname(__FILE__) + '/../../lib/action_view/helpers/text_helper'
require File.dirname(__FILE__) + '/../../../activesupport/lib/active_support/core_ext/numeric' # for human_size
require File.dirname(__FILE__) + '/../../../activesupport/lib/active_support/core_ext/hash' # for stringify_keys
require File.dirname(__FILE__) + '/../../../activesupport/lib/active_support/core_ext/object_and_class.rb' # for blank?
require File.dirname(__FILE__) + '/../../../activesupport/lib/active_support/core_ext/object.rb' # for blank?
class TextHelperTest < Test::Unit::TestCase
include ActionView::Helpers::TextHelper

View File

@ -244,6 +244,7 @@ module ActiveRecord #:nodoc:
cattr_accessor :logger
def self.inherited(child) #:nodoc:
child.send :include, Reloadable
@@subclasses[self] ||= []
@@subclasses[self] << child
super

View File

@ -81,6 +81,13 @@ module ActiveRecord
class Observer
include Singleton
# Observer subclasses should be reloaded by the dispatcher in Rails
# when Dependencies.mechanism = :load.
def self.inherited(child) #:nodoc:
child.send :include, Reloadable
super
end
# Attaches the observer to the supplied model classes.
def self.observe(*models)
define_method(:observed_class) { models }

View File

@ -1,6 +1,6 @@
require 'test/unit'
require 'abstract_unit'
require 'active_support/class_inheritable_attributes'
require 'active_support/core_ext/class/inheritable_attributes'
class A
include ClassInheritableAttributes

View File

@ -1,5 +1,15 @@
*SVN*
* Added reusable reloading support through the inclusion of the Relodable module that all subclasses of ActiveRecord::Base, ActiveRecord::Observer, ActiveController::Base, and ActionMailer::Base automatically gets. This means that these classes will be reloaded by the dispatcher when Dependencies.mechanism = :load. You can make your own models reloadable easily:
class Setting
include Reloadable
end
Reloading a class is done by removing its constant which will cause it to be loaded again on the next reference. [DHH]
* Added auto-loading support for classes in modules, so Conductor::Migration will look for conductor/migration.rb and Conductor::Database::Settings will look for conductor/database/settings.rb [Nicholas Seckar]
* Add Object#instance_exec, like instance_eval but passes its arguments to the block. (Active Support will not override the Ruby 1.9 implementation of this method.) [Sam Stephenson]
* Add Proc#bind(object) for changing a proc or block's self by returning a Method bound to the given object. Based on why the lucky stiff's "cloaker" method. [Sam Stephenson]

View File

@ -23,13 +23,12 @@
$:.unshift(File.dirname(__FILE__))
require 'active_support/class_attribute_accessors'
require 'active_support/class_inheritable_attributes'
require 'active_support/inflector'
require 'active_support/core_ext'
require 'active_support/clean_logger'
require 'active_support/dependencies'
require 'active_support/reloadable'
require 'active_support/ordered_options'
require 'active_support/option_merger'

View File

@ -1,5 +1,5 @@
require 'logger'
require File.dirname(__FILE__) + '/class_attribute_accessors'
require File.dirname(__FILE__) + '/core_ext/class/attribute_accessors'
class Logger #:nodoc:
cattr_accessor :silencer

View File

@ -1,4 +1,16 @@
# The methods here are provided to speed up function blank? in class Object
class Object #:nodoc:
# "", " ", nil, [], and {} are blank
def blank?
if respond_to?(:empty?) && respond_to?(:strip)
empty? or strip.empty?
elsif respond_to?(:empty?)
empty?
else
!self
end
end
end
class NilClass #:nodoc:
def blank?
true
@ -35,4 +47,4 @@ class Numeric #:nodoc:
def blank?
false
end
end
end

View File

@ -0,0 +1,3 @@
require File.dirname(__FILE__) + '/class/attribute_accessors'
require File.dirname(__FILE__) + '/class/inheritable_attributes'
require File.dirname(__FILE__) + '/class/removal'

View File

@ -0,0 +1,21 @@
class Class #:nodoc:
def remove_subclasses
Object.remove_subclasses_of(self)
end
def subclasses
Object.subclasses_of(self).map { |o| o.to_s }
end
def remove_class(klass)
if klass.to_s.include? "::"
modules = klass.to_s.split("::")
final_klass = modules.pop
final_module = modules.inject(Object) { |final_type, part| final_type.const_get(part) }
final_module.send(:remove_const, final_klass) rescue nil
else
Object.send(:remove_const, klass.to_s) rescue nil
end
end
end

View File

@ -1,5 +1,4 @@
class Exception
alias :clean_message :message
TraceSubstitutions = []

View File

@ -0,0 +1,2 @@
require File.dirname(__FILE__) + '/module/inclusion'
require File.dirname(__FILE__) + '/module/attribute_accessors'

View File

@ -0,0 +1,15 @@
class Module
def remove_classes_including
included_in_classes.each { |klass| Class.remove_class(klass) }
end
def included_in_classes
classes = []
ObjectSpace.each_object(Class) { |k| classes << k if k.included_modules.include?(self) }
classes.reverse.inject([]) do |unique_classes, klass|
unique_classes << klass unless unique_classes.collect { |k| k.to_s }.include?(klass.to_s)
unique_classes
end
end
end

View File

@ -0,0 +1,2 @@
require File.dirname(__FILE__) + '/object/extending'
require File.dirname(__FILE__) + '/object/misc'

View File

@ -4,7 +4,7 @@ class Object #:nodoc:
Object.send(:remove_const, subclass.to_s) rescue nil
end
end
def subclasses_of(*superclasses)
subclasses = []
ObjectSpace.each_object(Class) do |k|
@ -20,41 +20,16 @@ class Object #:nodoc:
end
def copy_instance_variables_from(object, exclude = [])
exclude += object.protected_instance_variables if
object.respond_to? :protected_instance_variables
exclude += object.protected_instance_variables if object.respond_to? :protected_instance_variables
instance_variables = object.instance_variables - exclude.map { |name| name.to_s }
instance_variables.each do |name|
instance_variable_set name, object.instance_variable_get(name)
end
instance_variables.each { |name| instance_variable_set(name, object.instance_variable_get(name)) }
end
def extend_with_included_modules_from(object)
object.extended_by.each { |mod| extend mod }
end
# "", " ", nil, [], and {} are blank
def blank?
if respond_to?(:empty?) && respond_to?(:strip)
empty? or strip.empty?
elsif respond_to?(:empty?)
empty?
else
!self
end
end
def suppress(*exception_classes)
begin yield
rescue Exception => e
raise unless exception_classes.any? {|cls| e.kind_of? cls}
end
end
def with_options(options)
yield ActiveSupport::OptionMerger.new(self, options)
end
def instance_values
instance_variables.inject({}) do |values, name|
values[name[1..-1]] = instance_variable_get(name)
@ -62,23 +37,9 @@ class Object #:nodoc:
end
end
def to_json
ActiveSupport::JSON.encode(self)
end
unless defined? instance_exec # 1.9
def instance_exec(*arguments, &block)
block.bind(self)[*arguments]
end
end
end
class Class #:nodoc:
def remove_subclasses
Object.remove_subclasses_of(self)
end
def subclasses
Object.subclasses_of(self).map { |o| o.to_s }
end
end
end

View File

@ -0,0 +1,16 @@
class Object #:nodoc:
def with_options(options)
yield ActiveSupport::OptionMerger.new(self, options)
end
def to_json
ActiveSupport::JSON.encode(self)
end
def suppress(*exception_classes)
begin yield
rescue Exception => e
raise unless exception_classes.any? { |cls| e.kind_of?(cls) }
end
end
end

View File

@ -1,5 +1,5 @@
require 'set'
require File.dirname(__FILE__) + '/module_attribute_accessors'
require File.dirname(__FILE__) + '/core_ext/module/attribute_accessors'
require File.dirname(__FILE__) + '/core_ext/load_error'
require File.dirname(__FILE__) + '/core_ext/kernel'
@ -69,10 +69,6 @@ module Dependencies #:nodoc:
history << 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
@ -211,27 +207,70 @@ Object.send(:define_method, :require_association) { |file_name| Dependencies.ass
class Module #:nodoc:
# Rename the original handler so we can chain it to the new one
alias :rails_original_const_missing :const_missing
def parent
parent_name = name.split('::')[0..-2] * '::'
parent_name.empty? ? Object : parent_name.constantize
end
def as_load_path
if self == Object || self == Kernel
''
elsif is_a? Class
parent == self ? '' : parent.as_load_path
else
name.split('::').collect do |word|
word.underscore
end * '/'
end
end
# 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)
if Object.const_defined?(:Controllers) && 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
if parent && parent != self
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)
@ -267,4 +306,4 @@ class Exception #:nodoc:
@blamed_files = exc.blamed_files.clone
self
end
end
end

View File

@ -0,0 +1,4 @@
# Classes that include this module will automatically be reloaded
# by the Rails dispatcher when Dependencies.mechanism = :load.
module Reloadable
end

View File

@ -0,0 +1,2 @@
class A::B
end

View File

@ -0,0 +1,2 @@
class A::C::D
end

View File

@ -0,0 +1,2 @@
class E
end

View File

@ -1,5 +1,5 @@
require 'test/unit'
require File.dirname(__FILE__) + '/../lib/active_support/class_inheritable_attributes'
require File.dirname(__FILE__) + '/../lib/active_support/core_ext/class/inheritable_attributes'
class ClassInheritableAttributesTest < Test::Unit::TestCase
def setup

View File

@ -1,5 +1,5 @@
require 'test/unit'
require File.dirname(__FILE__) + '/../../lib/active_support/core_ext/object_and_class'
require File.dirname(__FILE__) + '/../../lib/active_support/core_ext/object'
require File.dirname(__FILE__) + '/../../lib/active_support/core_ext/blank'
class BlankTest < Test::Unit::TestCase

View File

@ -0,0 +1,37 @@
require 'test/unit'
require File.dirname(__FILE__) + '/../../lib/active_support/core_ext/class'
class A
end
module X
class B
end
end
module Y
module Z
class C
end
end
end
class ClassTest < Test::Unit::TestCase
def test_removing_class_in_root_namespace
assert A.is_a?(Class)
Class.remove_class(A)
assert_raises(NameError) { A.is_a?(Class) }
end
def test_removing_class_in_one_level_namespace
assert X::B.is_a?(Class)
Class.remove_class(X::B)
assert_raises(NameError) { X::B.is_a?(Class) }
end
def test_removing_class_in_two_level_namespace
assert Y::Z::C.is_a?(Class)
Class.remove_class(Y::Z::C)
assert_raises(NameError) { Y::Z::C.is_a?(Class) }
end
end

View File

@ -0,0 +1,51 @@
require 'test/unit'
require File.dirname(__FILE__) + '/../../lib/active_support/core_ext/class'
require File.dirname(__FILE__) + '/../../lib/active_support/core_ext/module'
module One
end
class Ab
include One
end
module Xy
class Bc
include One
end
end
module Yz
module Zy
class Cd
include One
end
end
end
class De
end
class ModuleTest < Test::Unit::TestCase
def test_included_in_classes
assert One.included_in_classes.include?(Ab)
assert One.included_in_classes.include?(Xy::Bc)
assert One.included_in_classes.include?(Yz::Zy::Cd)
assert !One.included_in_classes.include?(De)
end
def test_remove_classes_including
assert Ab.is_a?(Class)
assert Xy::Bc.is_a?(Class)
assert Yz::Zy::Cd.is_a?(Class)
assert De.is_a?(Class)
One.remove_classes_including
assert_raises(NameError) { Ae.is_a?(Class) }
assert_raises(NameError) { Xy::Bc.is_a?(Class) }
assert_raises(NameError) { Yz::Zy::Cd.is_a?(Class) }
assert De.is_a?(Class)
end
end

View File

@ -1,5 +1,6 @@
require 'test/unit'
require File.dirname(__FILE__) + '/../../lib/active_support/core_ext/object_and_class'
require File.dirname(__FILE__) + '/../../lib/active_support/core_ext/object'
require File.dirname(__FILE__) + '/../../lib/active_support/core_ext/class'
class ClassA; end
class ClassB < ClassA; end

View File

@ -1,5 +1,6 @@
require 'test/unit'
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib/active_support/'
require 'core_ext/string'
require 'dependencies'
class DependenciesTest < Test::Unit::TestCase
@ -99,4 +100,20 @@ class DependenciesTest < Test::Unit::TestCase
$LOAD_PATH.shift
Dependencies.mechanism = old_mechanism
end
def test_as_load_path
assert_equal '', DependenciesTest.as_load_path
end
def test_module_loading
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/autoloading_fixtures"
old_mechanism, Dependencies.mechanism = Dependencies.mechanism, :load
assert_kind_of Module, A
assert_kind_of Class, A::B
assert_kind_of Class, A::C::D
ensure
$LOAD_PATH.shift
Dependencies.mechanism = old_mechanism
end
end

View File

@ -2,7 +2,7 @@ require 'test/unit'
unless defined? ActiveSupport::OptionMerger
require File.dirname(__FILE__) + '/../lib/active_support/option_merger'
require File.dirname(__FILE__) + '/../lib/active_support/core_ext/object_and_class'
require File.dirname(__FILE__) + '/../lib/active_support/core_ext/object'
end
class OptionMergerTest < Test::Unit::TestCase

View File

@ -53,8 +53,7 @@ class Dispatcher
Controllers.clear!
Dependencies.clear
ActiveRecord::Base.reset_subclasses
Dependencies.remove_subclasses_for(ActiveRecord::Base, ActiveRecord::Observer, ActionController::Base)
Dependencies.remove_subclasses_for(ActionMailer::Base) if defined?(ActionMailer::Base)
Reloadable.remove_classes_including
end
private