aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile.lock4
-rw-r--r--actionpack/lib/abstract_controller/translation.rb1
-rw-r--r--actionview/lib/action_view/helpers/csp_helper.rb6
-rw-r--r--actionview/test/template/csp_helper_test.rb4
-rw-r--r--activerecord/CHANGELOG.md7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb6
-rw-r--r--activerecord/lib/active_record/relation.rb36
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/scoping.rb8
-rw-r--r--activerecord/lib/active_record/scoping/named.rb10
-rw-r--r--activerecord/test/cases/base_test.rb2
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb12
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb26
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb20
-rw-r--r--activerecord/test/cases/relations_test.rb48
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb6
-rw-r--r--activerecord/test/models/bird.rb4
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support/dependencies/zeitwerk_integration.rb15
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb2
-rw-r--r--activesupport/lib/active_support/message_verifier.rb2
-rw-r--r--guides/source/active_record_basics.md2
-rw-r--r--railties/lib/rails.rb19
-rw-r--r--railties/lib/rails/autoloaders.rb30
-rw-r--r--railties/lib/rails/engine.rb2
-rw-r--r--railties/test/application/configuration_test.rb27
-rw-r--r--railties/test/application/zeitwerk_integration_test.rb64
28 files changed, 236 insertions, 137 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index b89c64d34e..5901314183 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -70,7 +70,7 @@ PATH
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
- zeitwerk (~> 1.0)
+ zeitwerk (~> 1.1)
rails (6.0.0.beta1)
actioncable (= 6.0.0.beta1)
actionmailbox (= 6.0.0.beta1)
@@ -517,7 +517,7 @@ GEM
websocket-extensions (0.1.3)
xpath (3.2.0)
nokogiri (~> 1.8)
- zeitwerk (1.0.0)
+ zeitwerk (1.1.0)
PLATFORMS
java
diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb
index 666e154e4c..4dad2a2b93 100644
--- a/actionpack/lib/abstract_controller/translation.rb
+++ b/actionpack/lib/abstract_controller/translation.rb
@@ -11,6 +11,7 @@ module AbstractController
# to translate many keys within the same controller / action and gives you a
# simple framework for scoping them consistently.
def translate(key, options = {})
+ options = options.dup
if key.to_s.first == "."
path = controller_path.tr("/", ".")
defaults = [:"#{path}#{key}"]
diff --git a/actionview/lib/action_view/helpers/csp_helper.rb b/actionview/lib/action_view/helpers/csp_helper.rb
index e2e065c218..4415018845 100644
--- a/actionview/lib/action_view/helpers/csp_helper.rb
+++ b/actionview/lib/action_view/helpers/csp_helper.rb
@@ -14,9 +14,11 @@ module ActionView
# This is used by the Rails UJS helper to create dynamically
# loaded inline <script> elements.
#
- def csp_meta_tag
+ def csp_meta_tag(**options)
if content_security_policy?
- tag("meta", name: "csp-nonce", content: content_security_policy_nonce)
+ options[:name] = "csp-nonce"
+ options[:content] = content_security_policy_nonce
+ tag("meta", options)
end
end
end
diff --git a/actionview/test/template/csp_helper_test.rb b/actionview/test/template/csp_helper_test.rb
index 8bad25ba7d..1b7fd4665f 100644
--- a/actionview/test/template/csp_helper_test.rb
+++ b/actionview/test/template/csp_helper_test.rb
@@ -16,6 +16,10 @@ class CspHelperWithCspEnabledTest < ActionView::TestCase
def test_csp_meta_tag
assert_equal "<meta name=\"csp-nonce\" content=\"iyhD0Yc0W+c=\" />", csp_meta_tag
end
+
+ def test_csp_meta_tag_with_options
+ assert_equal "<meta property=\"csp-nonce\" name=\"csp-nonce\" content=\"iyhD0Yc0W+c=\" />", csp_meta_tag(property: "csp-nonce")
+ end
end
class CspHelperWithCspDisabledTest < ActionView::TestCase
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 47ae71f2a0..8d8aa89368 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,6 +1,9 @@
-* Fix `relation.create` to avoid leaking scope to initialization block and callbacks.
+* SQLite3: Implement `add_foreign_key` and `remove_foreign_key`.
- Fixes #9894, #17577.
+ *Ryuta Kamizono*
+
+* Deprecate using class level querying methods if the receiver scope
+ regarded as leaked. Use `klass.unscoped` to avoid the leaking scope.
*Ryuta Kamizono*
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 4e55fcae2f..d950099bab 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -96,6 +96,12 @@ module ActiveRecord
if @query_cache_enabled && !locked?(arel)
arel = arel_from_relation(arel)
sql, binds = to_sql_and_binds(arel, binds)
+
+ if binds.length > bind_params_length
+ sql, binds = unprepared_statement { to_sql_and_binds(arel) }
+ preparable = false
+ end
+
cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) }
else
super
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
index 0a637d81fa..bd649451d6 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
@@ -74,10 +74,8 @@ module ActiveRecord
fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table}")
- alter_table(from_table, foreign_keys) do |definition|
- fk_to_table = strip_table_name_prefix_and_suffix(fkey.to_table)
- definition.foreign_keys.delete([fk_to_table, fkey.options])
- end
+ foreign_keys.delete(fkey)
+ alter_table(from_table, foreign_keys)
end
def create_schema_dumper(options)
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index f98d9bb2c0..feaa20ccc6 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -67,7 +67,7 @@ module ActiveRecord
# user = users.new { |user| user.name = 'Oscar' }
# user.name # => Oscar
def new(attributes = nil, &block)
- block = klass.current_scope_restoring_block(&block)
+ block = _deprecated_scope_block("new", &block)
scoping { klass.new(attributes, &block) }
end
@@ -96,7 +96,8 @@ module ActiveRecord
if attributes.is_a?(Array)
attributes.collect { |attr| create(attr, &block) }
else
- new(attributes, &block).tap(&:save)
+ block = _deprecated_scope_block("create", &block)
+ scoping { klass.create(attributes, &block) }
end
end
@@ -110,7 +111,8 @@ module ActiveRecord
if attributes.is_a?(Array)
attributes.collect { |attr| create!(attr, &block) }
else
- new(attributes, &block).tap(&:save!)
+ block = _deprecated_scope_block("create!", &block)
+ scoping { klass.create!(attributes, &block) }
end
end
@@ -321,12 +323,12 @@ module ActiveRecord
# Please check unscoped if you want to remove all previous scopes (including
# the default_scope) during the execution of a block.
def scoping
- @delegate_to_klass ? yield : _scoping(self) { yield }
+ already_in_scope? ? yield : _scoping(self) { yield }
end
- def _exec_scope(*args, &block) # :nodoc:
+ def _exec_scope(name, *args, &block) # :nodoc:
@delegate_to_klass = true
- instance_exec(*args, &block) || self
+ _scoping(_deprecated_spawn(name)) { instance_exec(*args, &block) || self }
ensure
@delegate_to_klass = false
end
@@ -525,6 +527,7 @@ module ActiveRecord
def reset
@delegate_to_klass = false
+ @_deprecated_scope_source = nil
@to_sql = @arel = @loaded = @should_eager_load = nil
@records = [].freeze
@offsets = {}
@@ -633,7 +636,10 @@ module ActiveRecord
end
end
+ attr_reader :_deprecated_scope_source # :nodoc:
+
protected
+ attr_writer :_deprecated_scope_source # :nodoc:
def load_records(records)
@records = records.freeze
@@ -641,6 +647,24 @@ module ActiveRecord
end
private
+ def already_in_scope?
+ @delegate_to_klass && begin
+ scope = klass.current_scope(true)
+ scope && !scope._deprecated_scope_source
+ end
+ end
+
+ def _deprecated_spawn(name)
+ spawn.tap { |scope| scope._deprecated_scope_source = name }
+ end
+
+ def _deprecated_scope_block(name, &block)
+ -> record do
+ klass.current_scope = _deprecated_spawn(name)
+ yield record if block_given?
+ end
+ end
+
def _scoping(scope)
previous, klass.current_scope = klass.current_scope(true), scope
yield
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 7874c4c35a..efc4b447aa 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -8,7 +8,7 @@ module ActiveRecord
module SpawnMethods
# This is overridden by Associations::CollectionProxy
def spawn #:nodoc:
- @delegate_to_klass ? klass.all : clone
+ already_in_scope? ? klass.all : clone
end
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index 1142a87d25..35e9dcbffc 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -30,14 +30,6 @@ module ActiveRecord
def current_scope=(scope)
ScopeRegistry.set_value_for(:current_scope, self, scope)
end
-
- def current_scope_restoring_block(&block)
- current_scope = self.current_scope(true)
- -> *args do
- self.current_scope = current_scope
- yield(*args) if block_given?
- end
- end
end
def populate_with_current_scope_attributes # :nodoc:
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 5278eb29a9..681a5c6250 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -27,6 +27,14 @@ module ActiveRecord
scope = current_scope
if scope
+ if scope._deprecated_scope_source
+ ActiveSupport::Deprecation.warn(<<~MSG.squish)
+ Class level methods will no longer inherit scoping from `#{scope._deprecated_scope_source}`
+ in Rails 6.1. To continue using the scoped relation, pass it into the block directly.
+ To instead access the full set of models, as Rails 6.1 will, use `#{name}.unscoped`.
+ MSG
+ end
+
if self == scope.klass
scope.clone
else
@@ -180,7 +188,7 @@ module ActiveRecord
if body.respond_to?(:to_proc)
singleton_class.define_method(name) do |*args|
- scope = all._exec_scope(*args, &body)
+ scope = all._exec_scope(name, *args, &body)
scope = scope.extending(extension) if extension
scope
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 1b8f748bad..d560b4e519 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1536,7 +1536,7 @@ class BasicsTest < ActiveRecord::TestCase
Bird.create!(name: "Bluejay")
ActiveRecord::Base.connection.while_preventing_writes do
- assert_nothing_raised { Bird.where(name: "Bluejay").explain }
+ assert_queries(2) { Bird.where(name: "Bluejay").explain }
end
end
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 22a98036f3..ca97a5dc65 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -44,6 +44,18 @@ if ActiveRecord::Base.connection.prepared_statements
assert_equal 0, topics.count
end
+ def test_too_many_binds_with_query_cache
+ Topic.connection.enable_query_cache!
+ bind_params_length = @connection.send(:bind_params_length)
+ topics = Topic.where(id: (1 .. bind_params_length + 1).to_a)
+ assert_equal Topic.count, topics.count
+
+ topics = Topic.where.not(id: (1 .. bind_params_length + 1).to_a)
+ assert_equal 0, topics.count
+ ensure
+ Topic.connection.disable_query_cache!
+ end
+
def test_bind_from_join_in_subquery
subquery = Author.joins(:thinking_posts).where(name: "David")
scope = Author.from(subquery, "authors").where(id: 1)
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index 32586e525b..6547ebb5d1 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -556,30 +556,4 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
end
end
-else
- module ActiveRecord
- class Migration
- class NoForeignKeySupportTest < ActiveRecord::TestCase
- setup do
- @connection = ActiveRecord::Base.connection
- end
-
- def test_add_foreign_key_should_be_noop
- @connection.add_foreign_key :clubs, :categories
- end
-
- def test_remove_foreign_key_should_be_noop
- @connection.remove_foreign_key :clubs, :categories
- end
-
- unless current_adapter?(:SQLite3Adapter)
- def test_foreign_keys_should_raise_not_implemented
- assert_raises NotImplementedError do
- @connection.foreign_keys("clubs")
- end
- end
- end
- end
- end
- end
end
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index 0d9adcc145..90a50a5651 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -235,24 +235,4 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
end
end
-else
- class ReferencesWithoutForeignKeySupportTest < ActiveRecord::TestCase
- setup do
- @connection = ActiveRecord::Base.connection
- @connection.create_table(:testing_parents, force: true)
- end
-
- teardown do
- @connection.drop_table("testings", if_exists: true)
- @connection.drop_table("testing_parents", if_exists: true)
- end
-
- test "ignores foreign keys defined with the table" do
- @connection.create_table :testings do |t|
- t.references :testing_parent, foreign_key: true
- end
-
- assert_includes @connection.data_sources, "testings"
- end
- end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 857d743605..2ac81ba425 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1167,13 +1167,23 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "green", parrot.color
end
+ def test_first_or_create_with_after_initialize
+ Bird.create!(color: "yellow", name: "canary")
+ parrot = assert_deprecated do
+ Bird.where(color: "green").first_or_create do |bird|
+ bird.name = "parrot"
+ bird.enable_count = true
+ end
+ end
+ assert_equal 0, parrot.total_count
+ end
+
def test_first_or_create_with_block
- canary = Bird.create!(color: "yellow", name: "canary")
+ Bird.create!(color: "yellow", name: "canary")
parrot = Bird.where(color: "green").first_or_create do |bird|
bird.name = "parrot"
- assert_equal canary, Bird.find_by!(name: "canary")
+ assert_deprecated { assert_equal 0, Bird.count }
end
- assert_equal 1, parrot.total_count
assert_kind_of Bird, parrot
assert_predicate parrot, :persisted?
assert_equal "green", parrot.color
@@ -1214,13 +1224,23 @@ class RelationTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordInvalid) { Bird.where(color: "green").first_or_create! }
end
+ def test_first_or_create_bang_with_after_initialize
+ Bird.create!(color: "yellow", name: "canary")
+ parrot = assert_deprecated do
+ Bird.where(color: "green").first_or_create! do |bird|
+ bird.name = "parrot"
+ bird.enable_count = true
+ end
+ end
+ assert_equal 0, parrot.total_count
+ end
+
def test_first_or_create_bang_with_valid_block
- canary = Bird.create!(color: "yellow", name: "canary")
+ Bird.create!(color: "yellow", name: "canary")
parrot = Bird.where(color: "green").first_or_create! do |bird|
bird.name = "parrot"
- assert_equal canary, Bird.find_by!(name: "canary")
+ assert_deprecated { assert_equal 0, Bird.count }
end
- assert_equal 1, parrot.total_count
assert_kind_of Bird, parrot
assert_predicate parrot, :persisted?
assert_equal "green", parrot.color
@@ -1269,13 +1289,23 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "green", parrot.color
end
+ def test_first_or_initialize_with_after_initialize
+ Bird.create!(color: "yellow", name: "canary")
+ parrot = assert_deprecated do
+ Bird.where(color: "green").first_or_initialize do |bird|
+ bird.name = "parrot"
+ bird.enable_count = true
+ end
+ end
+ assert_equal 0, parrot.total_count
+ end
+
def test_first_or_initialize_with_block
- canary = Bird.create!(color: "yellow", name: "canary")
+ Bird.create!(color: "yellow", name: "canary")
parrot = Bird.where(color: "green").first_or_initialize do |bird|
bird.name = "parrot"
- assert_equal canary, Bird.find_by!(name: "canary")
+ assert_deprecated { assert_equal 0, Bird.count }
end
- assert_equal 1, parrot.total_count
assert_kind_of Bird, parrot
assert_not_predicate parrot, :persisted?
assert_predicate parrot, :valid?
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 1a3a95f168..8b08e40468 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -50,7 +50,7 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_calling_merge_at_first_in_scope
Topic.class_eval do
- scope :calling_merge_at_first_in_scope, Proc.new { merge(Topic.replied) }
+ scope :calling_merge_at_first_in_scope, Proc.new { merge(Topic.unscoped.replied) }
end
assert_equal Topic.calling_merge_at_first_in_scope.to_a, Topic.replied.to_a
end
@@ -448,7 +448,9 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_class_method_in_scope
- assert_equal [topics(:second)], topics(:first).approved_replies.ordered
+ assert_deprecated do
+ assert_equal [topics(:second)], topics(:first).approved_replies.ordered
+ end
end
def test_nested_scoping
diff --git a/activerecord/test/models/bird.rb b/activerecord/test/models/bird.rb
index c9f6759c7d..20af7c6122 100644
--- a/activerecord/test/models/bird.rb
+++ b/activerecord/test/models/bird.rb
@@ -17,8 +17,8 @@ class Bird < ActiveRecord::Base
throw(:abort)
end
- attr_accessor :total_count
+ attr_accessor :total_count, :enable_count
after_initialize do
- self.total_count = Bird.count
+ self.total_count = Bird.count if enable_count
end
end
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 92cdfd89fe..0879660f9e 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -34,5 +34,5 @@ Gem::Specification.new do |s|
s.add_dependency "tzinfo", "~> 1.1"
s.add_dependency "minitest", "~> 5.1"
s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
- s.add_dependency "zeitwerk", "~> 1.0"
+ s.add_dependency "zeitwerk", "~> 1.1"
end
diff --git a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb
index 75624bae09..23c237796e 100644
--- a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb
+++ b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb
@@ -6,7 +6,7 @@ module ActiveSupport
module Decorations
def clear
Dependencies.unload_interlock do
- Rails.autoloader.reload
+ Rails.autoloaders.main.reload
end
end
@@ -19,15 +19,18 @@ module ActiveSupport
end
def autoloaded_constants
- Rails.autoloaders.flat_map do |autoloader|
- autoloader.loaded.to_a
- end
+ (Rails.autoloaders.main.loaded + Rails.autoloaders.once.loaded).to_a
end
def autoloaded?(object)
cpath = object.is_a?(Module) ? object.name : object.to_s
Rails.autoloaders.any? { |autoloader| autoloader.loaded?(cpath) }
end
+
+ def verbose=(verbose)
+ l = verbose ? (logger || Rails.logger).method(:debug) : nil
+ Rails.autoloaders.each { |autoloader| autoloader.logger = l }
+ end
end
class << self
@@ -46,9 +49,9 @@ module ActiveSupport
next unless File.directory?(autoload_path)
if autoload_once?(autoload_path)
- Rails.once_autoloader.push_dir(autoload_path)
+ Rails.autoloaders.once.push_dir(autoload_path)
else
- Rails.autoloader.push_dir(autoload_path)
+ Rails.autoloaders.main.push_dir(autoload_path)
end
end
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 6f7302e732..7d6f8937f0 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -53,7 +53,7 @@ module ActiveSupport
# crypt.encrypt_and_sign(parcel, expires_in: 1.month)
# crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year)
#
- # Then the messages can be verified and returned upto the expire time.
+ # Then the messages can be verified and returned up to the expire time.
# Thereafter, verifying returns +nil+.
#
# === Rotating keys
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 64c557bec6..c4a4afe95f 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -71,7 +71,7 @@ module ActiveSupport
# @verifier.generate(parcel, expires_in: 1.month)
# @verifier.generate(doowad, expires_at: Time.now.end_of_year)
#
- # Then the messages can be verified and returned upto the expire time.
+ # Then the messages can be verified and returned up to the expire time.
# Thereafter, the +verified+ method returns +nil+ while +verify+ raises
# <tt>ActiveSupport::MessageVerifier::InvalidSignature</tt>.
#
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index b0d4bbd2c0..4cf4111bf0 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -45,7 +45,7 @@ relationships of the objects in an application can be easily stored and
retrieved from a database without writing SQL statements directly and with less
overall database access code.
-NOTE: If you are not familiar enough with relational database management systems (RDBMS) or structured query language (SQL), please go through [this tutorial](https://www.w3schools.com/sql/default.asp) (or [this one](http://www.sqlcourse.com/)) or study them by other means. Understanding how relational databases work is crucial to understanding Active Records and Rails in general.
+NOTE: Basic knowledge of relational database management systems (RDBMS) and structured query language (SQL) is helpful in order to fully understand Active Record. Please refer to [this tutorial](https://www.w3schools.com/sql/default.asp) (or [this one](http://www.sqlcourse.com/)) or study them by other means if you would like to learn more.
### Active Record as an ORM Framework
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index bca2cf34e1..1f533a8c04 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -13,6 +13,7 @@ require "active_support/core_ext/object/blank"
require "rails/application"
require "rails/version"
+require "rails/autoloaders"
require "active_support/railtie"
require "action_dispatch/railtie"
@@ -111,24 +112,8 @@ module Rails
application && Pathname.new(application.paths["public"].first)
end
- def autoloader
- if configuration.autoloader == :zeitwerk
- @autoloader ||= Zeitwerk::Loader.new
- end
- end
-
- def once_autoloader
- if configuration.autoloader == :zeitwerk
- @once_autoloader ||= Zeitwerk::Loader.new
- end
- end
-
def autoloaders
- if configuration.autoloader == :zeitwerk
- [autoloader, once_autoloader]
- else
- []
- end
+ Autoloaders
end
end
end
diff --git a/railties/lib/rails/autoloaders.rb b/railties/lib/rails/autoloaders.rb
new file mode 100644
index 0000000000..4b71f90c6b
--- /dev/null
+++ b/railties/lib/rails/autoloaders.rb
@@ -0,0 +1,30 @@
+module Rails
+ module Autoloaders # :nodoc:
+ class << self
+ include Enumerable
+
+ def main
+ if zeitwerk_enabled?
+ @main ||= Zeitwerk::Loader.new.tap { |loader| loader.tag = "rails.main" }
+ end
+ end
+
+ def once
+ if zeitwerk_enabled?
+ @once ||= Zeitwerk::Loader.new.tap { |loader| loader.tag = "rails.once" }
+ end
+ end
+
+ def each
+ if zeitwerk_enabled?
+ yield main
+ yield once
+ end
+ end
+
+ def zeitwerk_enabled?
+ Rails.configuration.autoloader == :zeitwerk
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 2485158a7b..76be6a8ac7 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -472,7 +472,7 @@ module Rails
# Eager load the application by loading all ruby
# files inside eager_load paths.
def eager_load!
- if Rails.autoloader
+ if Rails.autoloaders.zeitwerk_enabled?
eager_load_with_zeitwerk!
else
eager_load_with_dependencies!
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index f3161ad2a8..73773602a3 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -1157,23 +1157,30 @@ module ApplicationTests
end
end
- test "autoloader & autoloader=" do
+ test "autoloaders" do
app "development"
config = Rails.application.config
- assert_instance_of Zeitwerk::Loader, Rails.autoloader
- assert_instance_of Zeitwerk::Loader, Rails.once_autoloader
- assert_equal [Rails.autoloader, Rails.once_autoloader], Rails.autoloaders
+ assert Rails.autoloaders.zeitwerk_enabled?
+ assert_instance_of Zeitwerk::Loader, Rails.autoloaders.main
+ assert_equal "rails.main", Rails.autoloaders.main.tag
+ assert_instance_of Zeitwerk::Loader, Rails.autoloaders.once
+ assert_equal "rails.once", Rails.autoloaders.once.tag
+ assert_equal [Rails.autoloaders.main, Rails.autoloaders.once], Rails.autoloaders.to_a
config.autoloader = :classic
- assert_nil Rails.autoloader
- assert_nil Rails.once_autoloader
- assert_empty Rails.autoloaders
+ assert_not Rails.autoloaders.zeitwerk_enabled?
+ assert_nil Rails.autoloaders.main
+ assert_nil Rails.autoloaders.once
+ assert_equal 0, Rails.autoloaders.count
config.autoloader = :zeitwerk
- assert_instance_of Zeitwerk::Loader, Rails.autoloader
- assert_instance_of Zeitwerk::Loader, Rails.once_autoloader
- assert_equal [Rails.autoloader, Rails.once_autoloader], Rails.autoloaders
+ assert Rails.autoloaders.zeitwerk_enabled?
+ assert_instance_of Zeitwerk::Loader, Rails.autoloaders.main
+ assert_equal "rails.main", Rails.autoloaders.main.tag
+ assert_instance_of Zeitwerk::Loader, Rails.autoloaders.once
+ assert_equal "rails.once", Rails.autoloaders.once.tag
+ assert_equal [Rails.autoloaders.main, Rails.autoloaders.once], Rails.autoloaders.to_a
assert_raises(ArgumentError) { config.autoloader = :unknown }
end
diff --git a/railties/test/application/zeitwerk_integration_test.rb b/railties/test/application/zeitwerk_integration_test.rb
index 628a85acd8..c536c2f7f4 100644
--- a/railties/test/application/zeitwerk_integration_test.rb
+++ b/railties/test/application/zeitwerk_integration_test.rb
@@ -30,9 +30,10 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase
boot
assert decorated?
- assert_instance_of Zeitwerk::Loader, Rails.autoloader
- assert_instance_of Zeitwerk::Loader, Rails.once_autoloader
- assert_equal [Rails.autoloader, Rails.once_autoloader], Rails.autoloaders
+ assert Rails.autoloaders.zeitwerk_enabled?
+ assert_instance_of Zeitwerk::Loader, Rails.autoloaders.main
+ assert_instance_of Zeitwerk::Loader, Rails.autoloaders.once
+ assert_equal [Rails.autoloaders.main, Rails.autoloaders.once], Rails.autoloaders.to_a
end
test "ActiveSupport::Dependencies is not decorated in classic mode" do
@@ -40,9 +41,10 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase
boot
assert_not decorated?
- assert_nil Rails.autoloader
- assert_nil Rails.once_autoloader
- assert_empty Rails.autoloaders
+ assert_not Rails.autoloaders.zeitwerk_enabled?
+ assert_nil Rails.autoloaders.main
+ assert_nil Rails.autoloaders.once
+ assert_equal 0, Rails.autoloaders.count
end
test "constantize returns the value stored in the constant" do
@@ -136,8 +138,8 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase
)
boot
- assert_not_includes Rails.autoloader.dirs, "#{app_path}/extras"
- assert_includes Rails.once_autoloader.dirs, "#{app_path}/extras"
+ assert_not_includes Rails.autoloaders.main.dirs, "#{app_path}/extras"
+ assert_includes Rails.autoloaders.once.dirs, "#{app_path}/extras"
end
test "clear reloads the main autoloader, and does not reload the once one" do
@@ -145,13 +147,13 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase
$zeitwerk_integration_reload_test = []
- autoloader = Rails.autoloader
- def autoloader.reload
- $zeitwerk_integration_reload_test << :autoloader
+ main_autoloader = Rails.autoloaders.main
+ def main_autoloader.reload
+ $zeitwerk_integration_reload_test << :main_autoloader
super
end
- once_autoloader = Rails.once_autoloader
+ once_autoloader = Rails.autoloaders.once
def once_autoloader.reload
$zeitwerk_integration_reload_test << :once_autoloader
super
@@ -159,6 +161,42 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
- assert_equal %i(autoloader), $zeitwerk_integration_reload_test
+ assert_equal %i(main_autoloader), $zeitwerk_integration_reload_test
+ end
+
+ test "verbose = true sets the debug method of the dependencies logger if present" do
+ boot
+
+ logger = Logger.new(File::NULL)
+ ActiveSupport::Dependencies.logger = logger
+ ActiveSupport::Dependencies.verbose = true
+
+ Rails.autoloaders.each do |autoloader|
+ assert_equal logger.method(:debug), autoloader.logger
+ end
+ end
+
+ test "verbose = true sets the debug method of the Rails logger as fallback" do
+ boot
+
+ ActiveSupport::Dependencies.verbose = true
+
+ Rails.autoloaders.each do |autoloader|
+ assert_equal Rails.logger.method(:debug), autoloader.logger
+ end
+ end
+
+ test "verbose = false sets loggers to nil" do
+ boot
+
+ ActiveSupport::Dependencies.verbose = true
+ Rails.autoloaders.each do |autoloader|
+ assert autoloader.logger
+ end
+
+ ActiveSupport::Dependencies.verbose = false
+ Rails.autoloaders.each do |autoloader|
+ assert_nil autoloader.logger
+ end
end
end