diff options
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 |