aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/lib/abstract_controller/base.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb8
-rw-r--r--activemodel/lib/active_model/validations.rb11
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb57
-rw-r--r--activemodel/test/cases/validations/callbacks_test.rb77
-rwxr-xr-xactiverecord/lib/active_record/associations.rb2
-rwxr-xr-xactiverecord/lib/active_record/base.rb36
-rw-r--r--activerecord/lib/active_record/callbacks.rb27
-rw-r--r--activerecord/lib/active_record/observer.rb3
-rw-r--r--activerecord/lib/active_record/railtie.rb1
-rw-r--r--activerecord/lib/active_record/validations.rb4
-rwxr-xr-xactiverecord/test/cases/base_test.rb4
-rw-r--r--activesupport/lib/active_support.rb1
-rw-r--r--activesupport/lib/active_support/callbacks.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb10
-rw-r--r--activesupport/lib/active_support/descendants_tracker.rb39
-rw-r--r--activesupport/test/descendants_tracker_test.rb75
-rw-r--r--railties/lib/rails/application/bootstrap.rb2
-rw-r--r--railties/test/application/initializers/load_path_test.rb2
-rw-r--r--railties/test/application/loading_test.rb73
-rw-r--r--railties/test/application/model_initialization_test.rb33
-rw-r--r--railties/test/application/rake_test.rb2
22 files changed, 364 insertions, 126 deletions
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index e1027840ef..8a8337858b 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,4 +1,5 @@
require 'active_support/configurable'
+require 'active_support/descendants_tracker'
require 'active_support/core_ext/module/anonymous'
module AbstractController
@@ -10,6 +11,7 @@ module AbstractController
attr_internal :action_name
include ActiveSupport::Configurable
+ extend ActiveSupport::DescendantsTracker
class << self
attr_reader :abstract
@@ -21,17 +23,6 @@ module AbstractController
@abstract = true
end
- def inherited(klass)
- ::AbstractController::Base.descendants << klass.to_s
- super
- end
-
- # A list of all descendents of AbstractController::Base. This is
- # useful for initializers which need to add behavior to all controllers.
- def descendants
- @descendants ||= []
- end
-
# A list of all internal methods for a controller. This finds the first
# abstract superclass of a controller, and gets a list of all public
# instance methods on that abstract class. Public instance methods of
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index d07841218a..e4ae480bfb 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -8,7 +8,7 @@ module ActionDispatch
class Callbacks
include ActiveSupport::Callbacks
- define_callbacks :call, :terminator => "result == false", :rescuable => true
+ define_callbacks :call, :rescuable => true
define_callbacks :prepare, :scope => :name
# Add a preparation callback. Preparation callbacks are run before every
@@ -37,12 +37,12 @@ module ActionDispatch
def initialize(app, prepare_each_request = false)
@app, @prepare_each_request = app, prepare_each_request
- run_callbacks(:prepare)
+ _run_prepare_callbacks
end
def call(env)
- run_callbacks(:call) do
- run_callbacks(:prepare) if @prepare_each_request
+ _run_call_callbacks do
+ _run_prepare_callbacks if @prepare_each_request
@app.call(env)
end
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 57487cf75a..fa6bd91ff7 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/keys'
require 'active_model/errors'
+require 'active_model/validations/callbacks'
module ActiveModel
@@ -164,8 +165,7 @@ module ActiveModel
def valid?(context = nil)
current_context, self.validation_context = validation_context, context
errors.clear
- _run_validate_callbacks
- errors.empty?
+ run_validations!
ensure
self.validation_context = current_context
end
@@ -194,6 +194,13 @@ module ActiveModel
# end
#
alias :read_attribute_for_validation :send
+
+ protected
+
+ def run_validations!
+ _run_validate_callbacks
+ errors.empty?
+ end
end
end
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
new file mode 100644
index 0000000000..afd65d3dd5
--- /dev/null
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -0,0 +1,57 @@
+require 'active_support/callbacks'
+
+module ActiveModel
+ module Validations
+ module Callbacks
+ # == Active Model Validation callbacks
+ #
+ # Provides an interface for any class to have <tt>before_validation</tt> and
+ # <tt>after_validation</tt> callbacks.
+ #
+ # First, extend ActiveModel::Callbacks from the class you are creating:
+ #
+ # class MyModel
+ # include ActiveModel::Validations::Callbacks
+ #
+ # before_validation :do_stuff_before_validation
+ # after_validation :do_tuff_after_validation
+ # end
+ #
+ # Like other before_* callbacks if <tt>before_validation</tt> returns false
+ # then <tt>valid?</tt> will not be called.
+ extend ActiveSupport::Concern
+
+ included do
+ include ActiveSupport::Callbacks
+ define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+ end
+
+ module ClassMethods
+ def before_validation(*args, &block)
+ options = args.last
+ if options.is_a?(Hash) && options[:on]
+ options[:if] = Array.wrap(options[:if])
+ options[:if] << "self.validation_context == :#{options[:on]}"
+ end
+ set_callback(:validation, :before, *args, &block)
+ end
+
+ def after_validation(*args, &block)
+ options = args.extract_options!
+ options[:prepend] = true
+ options[:if] = Array.wrap(options[:if])
+ options[:if] << "!halted && value != false"
+ options[:if] << "self.validation_context == :#{options[:on]}" if options[:on]
+ set_callback(:validation, :after, *(args << options), &block)
+ end
+ end
+
+ protected
+
+ # Overwrite run validations to include callbacks.
+ def run_validations!
+ _run_validation_callbacks { super }
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb
new file mode 100644
index 0000000000..67b21eb106
--- /dev/null
+++ b/activemodel/test/cases/validations/callbacks_test.rb
@@ -0,0 +1,77 @@
+# encoding: utf-8
+require 'cases/helper'
+
+class Dog
+ include ActiveModel::Validations
+ include ActiveModel::Validations::Callbacks
+
+ attr_accessor :name, :history
+
+ def history
+ @history ||= []
+ end
+end
+
+class DogWithMethodCallbacks < Dog
+ before_validation :set_before_validation_marker
+ after_validation :set_after_validation_marker
+
+ def set_before_validation_marker; self.history << 'before_validation_marker'; end
+ def set_after_validation_marker; self.history << 'after_validation_marker' ; end
+end
+
+class DogValidtorsAreProc < Dog
+ before_validation { self.history << 'before_validation_marker' }
+ after_validation { self.history << 'after_validation_marker' }
+end
+
+class DogWithTwoValidators < Dog
+ before_validation { self.history << 'before_validation_marker1' }
+ before_validation { self.history << 'before_validation_marker2' }
+end
+
+class DogValidatorReturningFalse < Dog
+ before_validation { false }
+ before_validation { self.history << 'before_validation_marker2' }
+end
+
+class DogWithMissingName < Dog
+ before_validation { self.history << 'before_validation_marker' }
+ validates_presence_of :name
+end
+
+class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase
+
+ def test_before_validation_and_after_validation_callbacks_should_be_called
+ d = DogWithMethodCallbacks.new
+ d.valid?
+ assert_equal ['before_validation_marker', 'after_validation_marker'], d.history
+ end
+
+ def test_before_validation_and_after_validation_callbacks_should_be_called_with_proc
+ d = DogValidtorsAreProc.new
+ d.valid?
+ assert_equal ['before_validation_marker', 'after_validation_marker'], d.history
+ end
+
+ def test_before_validation_and_after_validation_callbacks_should_be_called_in_declared_order
+ d = DogWithTwoValidators.new
+ d.valid?
+ assert_equal ['before_validation_marker1', 'before_validation_marker2'], d.history
+ end
+
+ def test_further_callbacks_should_not_be_called_if_before_validation_returns_false
+ d = DogValidatorReturningFalse.new
+ output = d.valid?
+ assert_equal [], d.history
+ assert_equal false, output
+ end
+
+ def test_validation_test_should_be_done
+ d = DogWithMissingName.new
+ output = d.valid?
+ assert_equal ['before_validation_marker'], d.history
+ assert_equal false, output
+ end
+
+end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 9b59266bbc..567218e6fb 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -2066,7 +2066,7 @@ module ActiveRecord
unless klass.descends_from_active_record?
sti_column = aliased_table[klass.inheritance_column]
sti_condition = sti_column.eq(klass.sti_name)
- klass.send(:subclasses).each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
+ klass.descendants.each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
@join << sti_condition
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 3b6ffa46f2..def0fdaa2f 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -2,6 +2,7 @@ require 'yaml'
require 'set'
require 'active_support/benchmarkable'
require 'active_support/dependencies'
+require 'active_support/descendants_tracker'
require 'active_support/time'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/class/attribute_accessors'
@@ -274,28 +275,6 @@ module ActiveRecord #:nodoc:
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
cattr_accessor :logger, :instance_writer => false
- def self.inherited(child) #:nodoc:
- @@subclasses[self] ||= []
- @@subclasses[self] << child
- super
- end
-
- def self.reset_subclasses #:nodoc:
- nonreloadables = []
- subclasses.each do |klass|
- unless ActiveSupport::Dependencies.autoloaded? klass
- nonreloadables << klass
- next
- end
- klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
- klass.instance_methods(false).each { |m| klass.send :undef_method, m }
- end
- @@subclasses = {}
- nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
- end
-
- @@subclasses = {}
-
##
# :singleton-method:
# Contains the database configuration - as is typically stored in config/database.yml -
@@ -810,7 +789,7 @@ module ActiveRecord #:nodoc:
end
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
- subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
+ descendants.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
end
def attribute_method?(attribute)
@@ -975,7 +954,7 @@ module ActiveRecord #:nodoc:
def type_condition
sti_column = arel_table[inheritance_column]
condition = sti_column.eq(sti_name)
- subclasses.each{|subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
+ descendants.each { |subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
condition
end
@@ -1165,14 +1144,6 @@ module ActiveRecord #:nodoc:
with_scope(method_scoping, :overwrite, &block)
end
- # Returns a list of all subclasses of this class, meaning all descendants.
- def subclasses
- @@subclasses[self] ||= []
- @@subclasses[self] + @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
- end
-
- public :subclasses
-
# Sets the default options for the model. The format of the
# <tt>options</tt> argument is the same as in find.
#
@@ -1900,6 +1871,7 @@ module ActiveRecord #:nodoc:
extend ActiveModel::Naming
extend QueryCache::ClassMethods
extend ActiveSupport::Benchmarkable
+ extend ActiveSupport::DescendantsTracker
include ActiveModel::Conversion
include Validations
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 44fee12001..997c85ef8c 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -234,8 +234,7 @@ module ActiveRecord
included do
extend ActiveModel::Callbacks
-
- define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+ include ActiveModel::Validations::Callbacks
define_model_callbacks :initialize, :find, :only => :after
define_model_callbacks :save, :create, :update, :destroy
@@ -249,29 +248,6 @@ module ActiveRecord
send(meth.to_sym, meth.to_sym)
end
end
-
- def before_validation(*args, &block)
- options = args.last
- if options.is_a?(Hash) && options[:on]
- options[:if] = Array.wrap(options[:if])
- options[:if] << "@_on_validate == :#{options[:on]}"
- end
- set_callback(:validation, :before, *args, &block)
- end
-
- def after_validation(*args, &block)
- options = args.extract_options!
- options[:prepend] = true
- options[:if] = Array.wrap(options[:if])
- options[:if] << "!halted && value != false"
- options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
- set_callback(:validation, :after, *(args << options), &block)
- end
- end
-
- def valid?(*) #:nodoc:
- @_on_validate = new_record? ? :create : :update
- _run_validation_callbacks { super }
end
def destroy #:nodoc:
@@ -286,6 +262,7 @@ module ActiveRecord
end
private
+
def create_or_update #:nodoc:
_run_save_callbacks { super }
end
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 0ea7fe7365..fabbc33005 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -105,8 +105,9 @@ module ActiveRecord
end
protected
+
def observed_subclasses
- observed_classes.sum([]) { |klass| klass.send(:subclasses) }
+ observed_classes.sum([]) { |klass| klass.send(:descendants) }
end
def observe_callbacks?
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index a32fb7d399..0386fd4651 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -68,7 +68,6 @@ module ActiveRecord
unless app.config.cache_classes
ActiveSupport.on_load(:active_record) do
ActionDispatch::Callbacks.after do
- ActiveRecord::Base.reset_subclasses
ActiveRecord::Base.clear_reloadable_connections!
end
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index be64e00bd1..6ef9382b2a 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -49,12 +49,12 @@ module ActiveRecord
# Runs all the specified validations and returns true if no errors were added otherwise false.
def valid?(context = nil)
context ||= (new_record? ? :create : :update)
- super(context)
+ output = super(context)
deprecated_callback_method(:validate)
deprecated_callback_method(:"validate_on_#{context}")
- errors.empty?
+ errors.empty? && output
end
protected
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 5c175de6d4..7c74d87b61 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -2076,10 +2076,6 @@ class BasicsTest < ActiveRecord::TestCase
assert !SubStiPost.descends_from_active_record?
end
- def test_base_subclasses_is_public_method
- assert ActiveRecord::Base.public_methods.map(&:to_sym).include?(:subclasses)
- end
-
def test_find_on_abstract_base_class_doesnt_use_type_condition
old_class = LooseDescendant
Object.send :remove_const, :LooseDescendant
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index e34e46b4cf..f93b351655 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -51,6 +51,7 @@ module ActiveSupport
autoload :Concern
autoload :Configurable
autoload :Deprecation
+ autoload :DescendantsTracker
autoload :Gzip
autoload :Inflector
autoload :JSON
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 3ff33eea72..c4e1eb2c04 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -1,6 +1,6 @@
+require 'active_support/descendants_tracker'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/inheritable_attributes'
-require 'active_support/core_ext/class/subclasses'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
@@ -85,6 +85,10 @@ module ActiveSupport
module Callbacks
extend Concern
+ included do
+ extend ActiveSupport::DescendantsTracker
+ end
+
def run_callbacks(kind, *args, &block)
send("_run_#{kind}_callbacks", *args, &block)
end
@@ -428,7 +432,7 @@ module ActiveSupport
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block
- ([self] + self.descendents).each do |target|
+ ([self] + self.descendants).each do |target|
chain = target.send("_#{name}_callbacks")
yield chain, type, filters, options
target.__define_runner(name)
@@ -502,7 +506,7 @@ module ActiveSupport
def reset_callbacks(symbol)
callbacks = send("_#{symbol}_callbacks")
- self.descendents.each do |target|
+ self.descendants.each do |target|
chain = target.send("_#{symbol}_callbacks")
callbacks.each { |c| chain.delete(c) }
target.__define_runner(symbol)
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index bbd8f5aef6..7d58a8b56a 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -11,9 +11,9 @@ class Class #:nodoc:
# Rubinius
if defined?(Class.__subclasses__)
- def descendents
+ def descendants
subclasses = []
- __subclasses__.each {|k| subclasses << k; subclasses.concat k.descendents }
+ __subclasses__.each {|k| subclasses << k; subclasses.concat k.descendants }
subclasses
end
else
@@ -21,7 +21,7 @@ class Class #:nodoc:
begin
ObjectSpace.each_object(Class.new) {}
- def descendents
+ def descendants
subclasses = []
ObjectSpace.each_object(class << self; self; end) do |k|
subclasses << k unless k == self
@@ -30,7 +30,7 @@ class Class #:nodoc:
end
# JRuby
rescue StandardError
- def descendents
+ def descendants
subclasses = []
ObjectSpace.each_object(Class) do |k|
subclasses << k if k < self
@@ -48,7 +48,7 @@ class Class #:nodoc:
def self.subclasses_of(*superclasses) #:nodoc:
subclasses = []
superclasses.each do |klass|
- subclasses.concat klass.descendents.select {|k| k.anonymous? || k.reachable?}
+ subclasses.concat klass.descendants.select {|k| k.anonymous? || k.reachable?}
end
subclasses
end
diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb
new file mode 100644
index 0000000000..a587d7770c
--- /dev/null
+++ b/activesupport/lib/active_support/descendants_tracker.rb
@@ -0,0 +1,39 @@
+require 'active_support/dependencies'
+
+module ActiveSupport
+ # This module provides an internal implementation to track descendants
+ # which is faster than iterating through ObjectSpace.
+ module DescendantsTracker
+ @@descendants = Hash.new { |h, k| h[k] = [] }
+
+ def self.descendants
+ @@descendants
+ end
+
+ def self.clear
+ @@descendants.each do |klass, descendants|
+ if ActiveSupport::Dependencies.autoloaded?(klass)
+ @@descendants.delete(klass)
+ else
+ descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
+ end
+ end
+ end
+
+ def inherited(base)
+ self.direct_descendants << base
+ super
+ end
+
+ def direct_descendants
+ @@descendants[self]
+ end
+
+ def descendants
+ @@descendants[self].inject([]) do |descendants, klass|
+ descendants << klass
+ descendants.concat klass.descendants
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/descendants_tracker_test.rb b/activesupport/test/descendants_tracker_test.rb
new file mode 100644
index 0000000000..3a424180de
--- /dev/null
+++ b/activesupport/test/descendants_tracker_test.rb
@@ -0,0 +1,75 @@
+require 'abstract_unit'
+require 'test/unit'
+require 'active_support'
+require 'active_support/core_ext/hash/slice'
+
+class DescendantsTrackerTest < Test::Unit::TestCase
+ class Parent
+ extend ActiveSupport::DescendantsTracker
+ end
+
+ class Child1 < Parent
+ end
+
+ class Child2 < Parent
+ end
+
+ class Grandchild1 < Child1
+ end
+
+ class Grandchild2 < Child1
+ end
+
+ ALL = [Parent, Child1, Child2, Grandchild1, Grandchild2]
+
+ def test_descendants
+ assert_equal [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants
+ assert_equal [Grandchild1, Grandchild2], Child1.descendants
+ assert_equal [], Child2.descendants
+ end
+
+ def test_direct_descendants
+ assert_equal [Child1, Child2], Parent.direct_descendants
+ assert_equal [Grandchild1, Grandchild2], Child1.direct_descendants
+ assert_equal [], Child2.direct_descendants
+ end
+
+ def test_clear_with_autoloaded_parent_children_and_granchildren
+ mark_as_autoloaded *ALL do
+ ActiveSupport::DescendantsTracker.clear
+ assert ActiveSupport::DescendantsTracker.descendants.slice(*ALL).empty?
+ end
+ end
+
+ def test_clear_with_autoloaded_children_and_granchildren
+ mark_as_autoloaded Child1, Grandchild1, Grandchild2 do
+ ActiveSupport::DescendantsTracker.clear
+ assert_equal [Child2], Parent.descendants
+ assert_equal [], Child2.descendants
+ end
+ end
+
+ def test_clear_with_autoloaded_granchildren
+ mark_as_autoloaded Grandchild1, Grandchild2 do
+ ActiveSupport::DescendantsTracker.clear
+ assert_equal [Child1, Child2], Parent.descendants
+ assert_equal [], Child1.descendants
+ assert_equal [], Child2.descendants
+ end
+ end
+
+ protected
+
+ def mark_as_autoloaded(*klasses)
+ old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup
+ ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name)
+
+ old_descendants = ActiveSupport::DescendantsTracker.descendants.dup
+ old_descendants.each { |k, v| old_descendants[k] = v.dup }
+
+ yield
+ ensure
+ ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded
+ ActiveSupport::DescendantsTracker.descendants.replace(old_descendants)
+ end
+end \ No newline at end of file
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 0a435f0f36..44e26b5713 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -1,4 +1,5 @@
require "active_support/notifications"
+require "active_support/descendants_tracker"
module Rails
class Application
@@ -55,6 +56,7 @@ module Rails
initializer :set_clear_dependencies_hook do
unless config.cache_classes
ActionDispatch::Callbacks.after do
+ ActiveSupport::DescendantsTracker.clear
ActiveSupport::Dependencies.clear
end
end
diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb
index d31915e129..714d62311d 100644
--- a/railties/test/application/initializers/load_path_test.rb
+++ b/railties/test/application/initializers/load_path_test.rb
@@ -19,7 +19,7 @@ module ApplicationTests
assert $:.include?("#{app_path}/app/models")
end
- test "initializing an application adds lib path on inheritance hook" do
+ test "initializing an application allows to load code on lib path inside application class definitation" do
app_file "lib/foo.rb", <<-RUBY
module Foo; end
RUBY
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
new file mode 100644
index 0000000000..b337d3fc6e
--- /dev/null
+++ b/railties/test/application/loading_test.rb
@@ -0,0 +1,73 @@
+require 'isolation/abstract_unit'
+
+class LoadingTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ def app
+ @app ||= Rails.application
+ end
+
+ def test_load_should_load_constants
+ app_file "app/models/post.rb", <<-MODEL
+ class Post < ActiveRecord::Base
+ validates_acceptance_of :title, :accept => "omg"
+ end
+ MODEL
+
+ require "#{rails_root}/config/environment"
+ setup_ar!
+
+ p = Post.create(:title => 'omg')
+ assert_equal 1, Post.count
+ assert_equal 'omg', p.title
+ p = Post.first
+ assert_equal 'omg', p.title
+ end
+
+ def test_descendants_are_cleaned_on_each_request_without_cache_classes
+ add_to_config <<-RUBY
+ config.cache_classes = false
+ RUBY
+
+ app_file "app/models/post.rb", <<-MODEL
+ class Post < ActiveRecord::Base
+ end
+ MODEL
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do |map|
+ match '/load', :to => lambda { |env| [200, {}, Post.all] }
+ match '/unload', :to => lambda { |env| [200, {}, []] }
+ end
+ RUBY
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ require "#{rails_root}/config/environment"
+ setup_ar!
+
+ assert_equal [], ActiveRecord::Base.descendants
+ get "/load"
+ assert_equal [Post], ActiveRecord::Base.descendants
+ get "/unload"
+ assert_equal [], ActiveRecord::Base.descendants
+ end
+
+ protected
+
+ def setup_ar!
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
+ ActiveRecord::Migration.verbose = false
+ ActiveRecord::Schema.define(:version => 1) do
+ create_table :posts do |t|
+ t.string :title
+ end
+ end
+ end
+end
diff --git a/railties/test/application/model_initialization_test.rb b/railties/test/application/model_initialization_test.rb
deleted file mode 100644
index 6a22f8d8df..0000000000
--- a/railties/test/application/model_initialization_test.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'isolation/abstract_unit'
-
-class PostTest < Test::Unit::TestCase
- include ActiveSupport::Testing::Isolation
-
- def setup
- build_app
- boot_rails
- end
-
- def test_reload_should_reload_constants
- app_file "app/models/post.rb", <<-MODEL
- class Post < ActiveRecord::Base
- validates_acceptance_of :title, :accept => "omg"
- end
- MODEL
-
- require "#{rails_root}/config/environment"
- ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
- ActiveRecord::Migration.verbose = false
- ActiveRecord::Schema.define(:version => 1) do
- create_table :posts do |t|
- t.string :title
- end
- end
-
- p = Post.create(:title => 'omg')
- assert_equal 1, Post.count
- assert_equal 'omg', p.title
- p = Post.first
- assert_equal 'omg', p.title
- end
-end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 6b7a471494..40fb446b16 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -9,7 +9,7 @@ module ApplicationTests
boot_rails
FileUtils.rm_rf("#{app_path}/config/environments")
end
-
+
def test_gems_tasks_are_loaded_first_than_application_ones
app_file "lib/tasks/app.rake", <<-RUBY
$task_loaded = Rake::Task.task_defined?("db:create:all")