diff options
Diffstat (limited to 'railties')
27 files changed, 470 insertions, 94 deletions
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index d66add2ca0..9e8b38733a 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,5 @@ +## Rails 6.0.0.beta2 (February 25, 2019) ## + * Fix non-symbol access to nested hashes returned from `Rails::Application.config_for` being broken by allowing non-symbol access with a deprecation notice. diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc index 1c4252edd0..f1783a4737 100644 --- a/railties/RDOC_MAIN.rdoc +++ b/railties/RDOC_MAIN.rdoc @@ -4,7 +4,7 @@ \Rails is a web-application framework that includes everything needed to create database-backed web applications according to the -{Model-View-Controller (MVC)}[http://en.wikipedia.org/wiki/Model-view-controller] +{Model-View-Controller (MVC)}[https://en.wikipedia.org/wiki/Model-view-controller] pattern. Understanding the MVC pattern is key to understanding \Rails. MVC divides your @@ -79,7 +79,7 @@ and may also be used independently outside \Rails. * The \README file created within your application. * {Getting Started with \Rails}[https://guides.rubyonrails.org/getting_started.html]. * {Ruby on \Rails Guides}[https://guides.rubyonrails.org]. - * {The API Documentation}[http://api.rubyonrails.org]. + * {The API Documentation}[https://api.rubyonrails.org]. * {Ruby on \Rails Tutorial}[https://www.railstutorial.org/book]. == Contributing @@ -88,7 +88,7 @@ We encourage you to contribute to Ruby on \Rails! Please check out the {Contributing to Ruby on \Rails guide}[https://guides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how to proceed. {Join us!}[http://contributors.rubyonrails.org] Trying to report a possible security vulnerability in \Rails? Please -check out our {security policy}[http://rubyonrails.org/security/] for +check out our {security policy}[https://rubyonrails.org/security/] for guidelines about how to proceed. Everyone interacting in \Rails and its sub-projects' codebases, issue trackers, chat rooms, and mailing lists is expected to follow the \Rails {code of conduct}[http://rubyonrails.org/conduct/]. diff --git a/railties/README.rdoc b/railties/README.rdoc index 2715ccac3f..51437e3147 100644 --- a/railties/README.rdoc +++ b/railties/README.rdoc @@ -29,7 +29,7 @@ Railties is released under the MIT license: API documentation is at -* http://api.rubyonrails.org +* https://api.rubyonrails.org Bug reports can be filed for the Ruby on Rails project here: 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/application.rb b/railties/lib/rails/application.rb index d0417f8a49..fbad3e5db3 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -602,10 +602,7 @@ module Rails end def []=(key, value) - if value.is_a?(Hash) - value = self.class.new(value) - end - super(key.to_sym, value) + regular_writer(key.to_sym, convert_value(value, for: :assignment)) end private @@ -623,6 +620,23 @@ module Rails key end + + def convert_value(value, options = {}) # :doc: + if value.is_a? Hash + if options[:for] == :to_hash + value.to_hash + else + self.class.new(value) + end + elsif value.is_a?(Array) + if options[:for] != :assignment || value.frozen? + value = value.dup + end + value.map! { |e| convert_value(e, options) } + else + value + end + end end end end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 16fbc99e7a..83a7b6cf01 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -184,6 +184,26 @@ module Rails end end + # Load the database YAML without evaluating ERB. This allows us to + # create the rake tasks for multiple databases without filling in the + # configuration values or loading the environment. Do not use this + # method. + # + # This uses a DummyERB custom compiler so YAML can ignore the ERB + # tags and load the database.yml for the rake tasks. + def load_database_yaml # :nodoc: + if path = paths["config/database"].existent.first + require "rails/application/dummy_erb_compiler" + + yaml = Pathname.new(path) + erb = DummyERB.new(yaml.read) + + YAML.load(erb.result) + else + {} + end + end + # Loads and returns the entire raw configuration of database from # values stored in <tt>config/database.yml</tt>. def database_configuration @@ -271,7 +291,11 @@ module Rails end def autoloader=(autoloader) - if %i(classic zeitwerk).include?(autoloader) + case autoloader + when :classic + @autoloader = autoloader + when :zeitwerk + require "zeitwerk" @autoloader = autoloader else raise ArgumentError, "config.autoloader may be :classic or :zeitwerk, got #{autoloader.inspect} instead" diff --git a/railties/lib/rails/application/dummy_erb_compiler.rb b/railties/lib/rails/application/dummy_erb_compiler.rb new file mode 100644 index 0000000000..4e92526969 --- /dev/null +++ b/railties/lib/rails/application/dummy_erb_compiler.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# These classes are used to strip out the ERB configuration +# values so we can evaluate the database.yml without evaluating +# the ERB values. +class DummyERB < ERB # :nodoc: + def make_compiler(trim_mode) + DummyCompiler.new trim_mode + end +end + +class DummyCompiler < ERB::Compiler # :nodoc: + def compile_content(stag, out) + out.push "_erbout << 'dummy_compiler'" + end +end diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 39e8ef6631..8d2c13d2a8 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -73,6 +73,9 @@ module Rails initializer :eager_load! do if config.eager_load ActiveSupport.run_load_hooks(:before_eager_load, self) + # Checks defined?(Zeitwerk) instead of zeitwerk_enabled? because we + # want to eager load any dependency managed by Zeitwerk regardless of + # the autoloading mode of the application. Zeitwerk::Loader.eager_load_all if defined?(Zeitwerk) config.eager_load_namespaces.each(&:eager_load!) end diff --git a/railties/lib/rails/autoloaders.rb b/railties/lib/rails/autoloaders.rb new file mode 100644 index 0000000000..1bd3f18a74 --- /dev/null +++ b/railties/lib/rails/autoloaders.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "active_support/dependencies/zeitwerk_integration" + +module Rails + module Autoloaders # :nodoc: + class << self + include Enumerable + + def main + if zeitwerk_enabled? + @main ||= Zeitwerk::Loader.new.tap do |loader| + loader.tag = "rails.main" + loader.inflector = ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector + end + end + end + + def once + if zeitwerk_enabled? + @once ||= Zeitwerk::Loader.new.tap do |loader| + loader.tag = "rails.once" + loader.inflector = ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector + end + end + end + + def each + if zeitwerk_enabled? + yield main + yield once + end + end + + def logger=(logger) + each { |loader| loader.logger = logger } + 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 1a8b707e1c..07bd56c978 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/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb index 82df56559c..249894f9d0 100644 --- a/railties/lib/rails/gem_version.rb +++ b/railties/lib/rails/gem_version.rb @@ -10,7 +10,7 @@ module Rails MAJOR = 6 MINOR = 0 TINY = 0 - PRE = "beta1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 0eed552042..66f6b57379 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -316,7 +316,7 @@ module Rails if options.dev? || options.edge? GemfileEntry.github "webpacker", "rails/webpacker", nil, "Use development version of Webpacker" else - GemfileEntry.version "webpacker", ">= 4.0.0.rc.3", "Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker" + GemfileEntry.version "webpacker", "~> 4.0", "Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker" end end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt index a1f1224a45..18de6948f0 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt @@ -28,7 +28,7 @@ ruby <%= "'#{RUBY_VERSION}'" -%> <% if depend_on_bootsnap? -%> # Reduces boot times through caching; required in config/boot.rb -gem 'bootsnap', '>= 1.4.0', require: false +gem 'bootsnap', '>= 1.4.1', require: false <%- end -%> <%- if options.api? -%> @@ -36,9 +36,7 @@ gem 'bootsnap', '>= 1.4.0', require: false # gem 'rack-cors' <%- end -%> -<% if RUBY_ENGINE == "ruby" -%> -gem "zeitwerk", ">= 1.0.0" - +<% if RUBY_ENGINE == 'ruby' -%> group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] diff --git a/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt index 908487d500..e67e742263 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt @@ -13,3 +13,11 @@ require("@rails/activestorage").start() <%- unless options[:skip_action_cable] -%> require("channels") <%- end -%> + + +// Uncomment to copy all static images under ../images to the output folder and reference +// them with the image_pack_tag helper in views (e.g <%%= image_pack_tag 'rails.png' %>) +// or the `imagePath` JavaScript helper below. +// +// const images = require.context('../images', true) +// const imagePath = (name) => images(name, true) diff --git a/railties/lib/rails/generators/rails/db/system/change/change_generator.rb b/railties/lib/rails/generators/rails/db/system/change/change_generator.rb index 63849eb18d..24db92fad7 100644 --- a/railties/lib/rails/generators/rails/db/system/change/change_generator.rb +++ b/railties/lib/rails/generators/rails/db/system/change/change_generator.rb @@ -35,8 +35,9 @@ module Rails end def edit_gemfile - database_gem_name, _ = gem_for_database - gsub_file("Gemfile", all_database_gems_regex, database_gem_name) + name, version = gem_for_database + gsub_file("Gemfile", all_database_gems_regex, name) + gsub_file("Gemfile", gem_entry_regex_for(name), gem_entry_for(name, *version)) end private @@ -48,6 +49,15 @@ module Rails all_database_gem_names = all_database_gems.map(&:first) /(\b#{all_database_gem_names.join('\b|\b')}\b)/ end + + def gem_entry_regex_for(gem_name) + /^gem.*\b#{gem_name}\b.*/ + end + + def gem_entry_for(*gem_name_and_version) + gem_name_and_version.map! { |segment| "'#{segment}'" } + "gem #{gem_name_and_version.join(", ")}" + end end end end diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml.tt b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml.tt index 0681780c97..ee4ae47727 100644 --- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml.tt +++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml.tt @@ -1,4 +1,4 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html <% unless attributes.empty? -%> <% %w(one two).each do |name| %> <%= name %>: diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 1bfb0dfe48..519b08746a 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |s| s.author = "David Heinemeier Hansson" s.email = "david@loudthinking.com" - s.homepage = "http://rubyonrails.org" + s.homepage = "https://rubyonrails.org" s.files = Dir["CHANGELOG.md", "README.rdoc", "MIT-LICENSE", "RDOC_MAIN.rdoc", "exe/**/*", "lib/**/{*,.[a-z]*}"] s.require_path = "lib" diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 960f708bdf..9ba5517afd 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -1157,23 +1157,34 @@ 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 + assert_equal ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector, Rails.autoloaders.main.inflector + assert_equal ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector, Rails.autoloaders.once.inflector 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_equal ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector, Rails.autoloaders.main.inflector + assert_equal ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector, Rails.autoloaders.once.inflector assert_raises(ArgumentError) { config.autoloader = :unknown } end @@ -1787,6 +1798,28 @@ module ApplicationTests end end + test "config_for loads nested custom configuration inside array from yaml with deprecated non-symbol access" do + app_file "config/custom.yml", <<-RUBY + development: + foo: + bar: + - baz: 1 + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app "development" + + config = Rails.application.config.my_custom_config + assert_instance_of Rails::Application::NonSymbolAccessDeprecatedHash, config[:foo][:bar].first + + assert_deprecated do + assert_equal 1, config[:foo][:bar].first["baz"] + end + end + test "config_for makes all hash methods available" do app_file "config/custom.yml", <<-RUBY development: diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index bfa66770bd..9c98489590 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -34,6 +34,22 @@ class LoadingTest < ActiveSupport::TestCase assert_equal "omg", p.title end + test "constants without a matching file raise NameError" do + app_file "app/models/post.rb", <<-RUBY + class Post + NON_EXISTING_CONSTANT + end + RUBY + + boot_app + + e = assert_raise(NameError) { User } + assert_equal "uninitialized constant #{self.class}::User", e.message + + e = assert_raise(NameError) { Post } + assert_equal "uninitialized constant Post::NON_EXISTING_CONSTANT", e.message + end + test "concerns in app are autoloaded" do app_file "app/controllers/concerns/trackable.rb", <<-CONCERN module Trackable diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 5879949b61..4f689bcb78 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true require "isolation/abstract_unit" +require "env_helpers" module ApplicationTests module RakeTests class RakeDbsTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation + include ActiveSupport::Testing::Isolation, EnvHelpers def setup build_app @@ -53,26 +54,17 @@ module ApplicationTests test "db:create and db:drop respect environment setting" do app_file "config/database.yml", <<-YAML development: - database: db/development.sqlite3 + database: <%= Rails.application.config.database %> adapter: sqlite3 YAML app_file "config/environments/development.rb", <<-RUBY Rails.application.configure do - config.read_encrypted_secrets = true + config.database = "db/development.sqlite3" end RUBY - app_file "lib/tasks/check_env.rake", <<-RUBY - Rake::Task["db:create"].enhance do - File.write("tmp/config_value", Rails.application.config.read_encrypted_secrets) - end - RUBY - - db_create_and_drop("db/development.sqlite3", environment_loaded: false) do - assert File.exist?("tmp/config_value") - assert_equal "true", File.read("tmp/config_value") - end + db_create_and_drop("db/development.sqlite3", environment_loaded: false) end def with_database_existing @@ -139,6 +131,59 @@ module ApplicationTests end end + test "db:truncate_all truncates all non-internal tables" do + Dir.chdir(app_path) do + rails "generate", "model", "book", "title:string" + rails "db:migrate" + require "#{app_path}/config/environment" + Book.create!(title: "Remote") + assert_equal 1, Book.count + schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + + rails "db:truncate_all" + + assert_equal( + schema_migrations, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + ) + assert_equal( + internal_metadata, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + ) + assert_equal 0, Book.count + end + end + + test "db:truncate_all does not truncate any tables when environment is protected" do + with_rails_env "production" do + Dir.chdir(app_path) do + rails "generate", "model", "book", "title:string" + rails "db:migrate" + require "#{app_path}/config/environment" + Book.create!(title: "Remote") + assert_equal 1, Book.count + schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + books = ActiveRecord::Base.connection.execute("SELECT * from \"books\"") + + output = rails("db:truncate_all", allow_failure: true) + assert_match(/ActiveRecord::ProtectedEnvironmentError/, output) + + assert_equal( + schema_migrations, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + ) + assert_equal( + internal_metadata, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + ) + assert_equal 1, Book.count + assert_equal(books, ActiveRecord::Base.connection.execute("SELECT * from \"books\"")) + end + end + end + def db_migrate_and_status(expected_database) rails "generate", "model", "book", "title:string" rails "db:migrate" @@ -179,9 +224,10 @@ module ApplicationTests def db_fixtures_load(expected_database) Dir.chdir(app_path) do rails "generate", "model", "book", "title:string" + reload rails "db:migrate", "db:fixtures:load" + assert_match expected_database, ActiveRecord::Base.connection_config[:database] - require "#{app_path}/app/models/book" assert_equal 2, Book.count end end @@ -201,8 +247,9 @@ module ApplicationTests require "#{app_path}/config/environment" rails "generate", "model", "admin::book", "title:string" + reload rails "db:migrate", "db:fixtures:load" - require "#{app_path}/app/models/admin/book" + assert_equal 2, Admin::Book.count end @@ -385,6 +432,72 @@ module ApplicationTests assert_equal "test", test_environment.call end + + test "db:seed:replant truncates all non-internal tables and loads the seeds" do + Dir.chdir(app_path) do + rails "generate", "model", "book", "title:string" + rails "db:migrate" + require "#{app_path}/config/environment" + Book.create!(title: "Remote") + assert_equal 1, Book.count + schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + + app_file "db/seeds.rb", <<-RUBY + Book.create!(title: "Rework") + Book.create!(title: "Ruby Under a Microscope") + RUBY + + rails "db:seed:replant" + + assert_equal( + schema_migrations, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + ) + assert_equal( + internal_metadata, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + ) + assert_equal 2, Book.count + assert_not_predicate Book.where(title: "Remote"), :exists? + assert_predicate Book.where(title: "Rework"), :exists? + assert_predicate Book.where(title: "Ruby Under a Microscope"), :exists? + end + end + + test "db:seed:replant does not truncate any tables and does not load the seeds when environment is protected" do + with_rails_env "production" do + Dir.chdir(app_path) do + rails "generate", "model", "book", "title:string" + rails "db:migrate" + require "#{app_path}/config/environment" + Book.create!(title: "Remote") + assert_equal 1, Book.count + schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + books = ActiveRecord::Base.connection.execute("SELECT * from \"books\"") + + app_file "db/seeds.rb", <<-RUBY + Book.create!(title: "Rework") + RUBY + + output = rails("db:seed:replant", allow_failure: true) + assert_match(/ActiveRecord::ProtectedEnvironmentError/, output) + + assert_equal( + schema_migrations, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + ) + assert_equal( + internal_metadata, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + ) + assert_equal 1, Book.count + assert_equal(books, ActiveRecord::Base.connection.execute("SELECT * from \"books\"")) + assert_not_predicate Book.where(title: "Rework"), :exists? + end + end + end end end end diff --git a/railties/test/application/rake/multi_dbs_test.rb b/railties/test/application/rake/multi_dbs_test.rb index ef99365e75..d676e7486e 100644 --- a/railties/test/application/rake/multi_dbs_test.rb +++ b/railties/test/application/rake/multi_dbs_test.rb @@ -170,6 +170,7 @@ module ApplicationTests rails "generate", "model", "book", "title:string" rails "generate", "model", "dog", "name:string" write_models_for_animals + reload end test "db:create and db:drop works on all databases for env" do diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb index fb43bebfbe..83e63718df 100644 --- a/railties/test/application/test_test.rb +++ b/railties/test/application/test_test.rb @@ -234,7 +234,7 @@ module ApplicationTests # TODO: would be nice if we could detect the schema change automatically. # For now, the user has to synchronize the schema manually. - # This test-case serves as a reminder for this use-case. + # This test case serves as a reminder for this use case. test "manually synchronize test schema after rollback" do output = rails("generate", "model", "user", "name:string") version = output.match(/(\d+)_create_users\.rb/)[1] diff --git a/railties/test/application/zeitwerk_integration_test.rb b/railties/test/application/zeitwerk_integration_test.rb index 628a85acd8..cddbf5a22d 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,35 @@ 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 "autoloaders inflect with Active Support" do + app_file "config/initializers/inflections.rb", <<-RUBY + ActiveSupport::Inflector.inflections(:en) do |inflect| + inflect.acronym 'RESTful' + end + RUBY + + app_file "app/controllers/restful_controller.rb", <<-RUBY + class RESTfulController < ApplicationController + end + RUBY + + boot + + basename = "restful_controller" + abspath = "#{Rails.root}/app/controllers/#{basename}.rb" + camelized = "RESTfulController" + + Rails.autoloaders.each do |autoloader| + assert_equal camelized, autoloader.inflector.camelize(basename, abspath) + end + + assert RESTfulController end test "constantize returns the value stored in the constant" do @@ -136,8 +163,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 +172,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 +186,72 @@ 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 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_same logger, autoloader.logger + end + end + + test "verbose = true sets the Rails logger as fallback" do + boot + + ActiveSupport::Dependencies.verbose = true + + Rails.autoloaders.each do |autoloader| + assert_same Rails.logger, 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 + + test "unhooks" do + boot + + assert_equal Module, Module.method(:const_missing).owner + assert_equal :no_op, deps.unhook! + end + + test "autoloaders.logger=" do + boot + + logger = ->(_msg) { } + Rails.autoloaders.logger = logger + + Rails.autoloaders.each do |autoloader| + assert_same logger, autoloader.logger + end + + Rails.autoloaders.logger = Rails.logger + + Rails.autoloaders.each do |autoloader| + assert_same Rails.logger, autoloader.logger + end + + Rails.autoloaders.logger = nil + + Rails.autoloaders.each do |autoloader| + assert_nil autoloader.logger + end end end diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb index 90e084ddca..ec512b6b64 100644 --- a/railties/test/backtrace_cleaner_test.rb +++ b/railties/test/backtrace_cleaner_test.rb @@ -18,7 +18,7 @@ class BacktraceCleanerTest < ActiveSupport::TestCase end test "should omit ActionView template methods names" do - method_name = ActionView::Template.new(nil, "app/views/application/index.html.erb", nil, {}).send :method_name + method_name = ActionView::Template.new(nil, "app/views/application/index.html.erb", nil, locals: []).send :method_name backtrace = [ "app/views/application/index.html.erb:4:in `block in #{method_name}'"] result = @cleaner.clean(backtrace, :all) assert_equal "app/views/application/index.html.erb:4", result[0] diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 937b8eb427..1ee9e43e89 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -660,15 +660,6 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_gem "jbuilder" end - def test_inclusion_of_zeitwerk - run_generator - if RUBY_ENGINE == "ruby" - assert_gem "zeitwerk" - else - assert_no_gem "zeitwerk" - end - end - def test_inclusion_of_a_debugger run_generator if defined?(JRUBY_VERSION) || RUBY_ENGINE == "rbx" diff --git a/railties/test/generators/db_system_change_generator_test.rb b/railties/test/generators/db_system_change_generator_test.rb index d476bfd2dc..d3d27b616a 100644 --- a/railties/test/generators/db_system_change_generator_test.rb +++ b/railties/test/generators/db_system_change_generator_test.rb @@ -40,7 +40,7 @@ module Rails assert_file("Gemfile") do |content| assert_match "# Use pg as the database for Active Record", content - assert_match "gem 'pg'", content + assert_match "gem 'pg', '>= 0.18', '< 2.0'", content end end @@ -54,7 +54,7 @@ module Rails assert_file("Gemfile") do |content| assert_match "# Use mysql2 as the database for Active Record", content - assert_match "gem 'mysql2'", content + assert_match "gem 'mysql2', '>= 0.4.4'", content end end @@ -68,7 +68,22 @@ module Rails assert_file("Gemfile") do |content| assert_match "# Use sqlite3 as the database for Active Record", content - assert_match "gem 'sqlite3'", content + assert_match "gem 'sqlite3', '~> 1.3', '>= 1.3.6'", content + end + end + + test "change from versioned gem to other versioned gem" do + run_generator ["--to", "sqlite3"] + run_generator ["--to", "mysql", "--force"] + + assert_file("config/database.yml") do |content| + assert_match "adapter: mysql2", content + assert_match "database: test_app", content + end + + assert_file("Gemfile") do |content| + assert_match "# Use mysql2 as the database for Active Record", content + assert_match "gem 'mysql2', '>= 0.4.4'", content end end end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 47d42645c6..b0662e0159 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -458,12 +458,19 @@ module TestHelpers end end end + + module Reload + def reload + ActiveSupport::Dependencies.clear + end + end end class ActiveSupport::TestCase include TestHelpers::Paths include TestHelpers::Rack include TestHelpers::Generation + include TestHelpers::Reload include ActiveSupport::Testing::Stream include ActiveSupport::Testing::MethodCallAssertions end @@ -481,7 +488,14 @@ Module.new do f.puts "require 'rails/all'" end + unless File.exist?("#{RAILS_FRAMEWORK_ROOT}/actionview/lib/assets/compiled/rails-ujs.js") + Dir.chdir("#{RAILS_FRAMEWORK_ROOT}/actionview") { `yarn build` } + end + assets_path = "#{RAILS_FRAMEWORK_ROOT}/railties/test/isolation/assets" + unless Dir.exist?("#{assets_path}/node_modules") + Dir.chdir(assets_path) { `yarn install` } + end FileUtils.cp("#{assets_path}/package.json", "#{app_template_path}/package.json") FileUtils.cp("#{assets_path}/config/webpacker.yml", "#{app_template_path}/config/webpacker.yml") FileUtils.cp_r("#{assets_path}/config/webpack", "#{app_template_path}/config/webpack") @@ -491,11 +505,7 @@ Module.new do # Fake 'Bundler.require' -- we run using the repo's Gemfile, not an # app-specific one: we don't want to require every gem that lists. contents = File.read("#{app_template_path}/config/application.rb") - if RUBY_ENGINE == "ruby" - contents.sub!(/^Bundler\.require.*/, "%w(turbolinks webpacker zeitwerk).each { |r| require r }") - else - contents.sub!(/^Bundler\.require.*/, "%w(turbolinks webpacker).each { |r| require r }") - end + contents.sub!(/^Bundler\.require.*/, "%w(turbolinks webpacker).each { |r| require r }") File.write("#{app_template_path}/config/application.rb", contents) require "rails" |