diff options
Diffstat (limited to 'railties')
146 files changed, 3139 insertions, 1163 deletions
diff --git a/railties/.gitignore b/railties/.gitignore index c08562e016..17a49da08c 100644 --- a/railties/.gitignore +++ b/railties/.gitignore @@ -2,4 +2,5 @@ /test/500.html /test/fixtures/tmp/ /test/initializer/root/log/ +/test/isolation/assets/yarn.lock /tmp/ diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 65dccbb3be..df0d6d4fa0 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,8 +1,167 @@ -* Add JSON support to rails properties route (`/rails/info/properties`). +* Applications running in `:zeitwerk` mode that use `bootsnap` need + to upgrade `bootsnap` to at least 1.4.2. - Now, `Rails::Info` properties may be accessed in JSON format at `/rails/info/properties.json`. + *Xavier Noria* - *Yoshiyuki Hirano* +* Add `config.disable_sandbox` option to Rails console. + + This setting will disable `rails console --sandbox` mode, preventing + developer from accidentally starting a sandbox console, + which when left inactive, can cause the database server to run out of memory. + + *Prem Sichanugrist* + +* Add `-e/--environment` option to `rails initializers`. + + *Yuji Yaginuma* + +## Rails 6.0.0.beta3 (March 11, 2019) ## + +* Generate random development secrets + + A random development secret is now generated to tmp/development_secret.txt + + This avoids an issue where development mode servers were vulnerable to + remote code execution. + + Fixes CVE-2019-5420 + + *Eileen M. Uchitelle*, *Aaron Patterson*, *John Hawthorn* + + + +## 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. + + *Ufuk Kayserilioglu* + +* Fix deeply nested namespace command printing. + + *Gannon McGibbon* + + +## Rails 6.0.0.beta1 (January 18, 2019) ## + +* Remove deprecated `after_bundle` helper inside plugins templates. + + *Rafael Mendonça França* + +* Remove deprecated support to old `config.ru` that use the application class as argument of `run`. + + *Rafael Mendonça França* + +* Remove deprecated `environment` argument from the rails commands. + + *Rafael Mendonça França* + +* Remove deprecated `capify!`. + + *Rafael Mendonça França* + +* Remove deprecated `config.secret_token`. + + *Rafael Mendonça França* + +* Seed database with inline ActiveJob job adapter. + + *Gannon McGibbon* + +* Add `rails db:system:change` command for changing databases. + + ``` + bin/rails db:system:change --to=postgresql + force config/database.yml + gsub Gemfile + ``` + + The change command copies a template `config/database.yml` with + the target database adapter into your app, and replaces your database gem + with the target database gem. + + *Gannon McGibbon* + +* Add `rails test:channels`. + + *bogdanvlviv* + +* Use original `bundler` environment variables during the process of generating a new rails project. + + *Marco Costa* + +* Send Active Storage analysis and purge jobs to dedicated queues by default. + + Analysis jobs now use the `:active_storage_analysis` queue, and purge jobs + now use the `:active_storage_purge` queue. This matches Action Mailbox, + which sends its jobs to dedicated queues by default. + + *George Claghorn* + +* Add `rails test:mailboxes`. + + *George Claghorn* + +* Introduce guard against DNS rebinding attacks. + + The `ActionDispatch::HostAuthorization` is a new middleware that prevents + against DNS rebinding and other `Host` header attacks. It is included in + the development environment by default with the following configuration: + + Rails.application.config.hosts = [ + IPAddr.new("0.0.0.0/0"), # All IPv4 addresses. + IPAddr.new("::/0"), # All IPv6 addresses. + "localhost" # The localhost reserved domain. + ] + + In other environments `Rails.application.config.hosts` is empty and no + `Host` header checks will be done. If you want to guard against header + attacks on production, you have to manually permit the allowed hosts + with: + + Rails.application.config.hosts << "product.com" + + The host of a request is checked against the `hosts` entries with the case + operator (`#===`), which lets `hosts` support entries of type `RegExp`, + `Proc` and `IPAddr` to name a few. Here is an example with a regexp. + + # Allow requests from subdomains like `www.product.com` and + # `beta1.product.com`. + Rails.application.config.hosts << /.*\.product\.com/ + + A special case is supported that allows you to permit all sub-domains: + + # Allow requests from subdomains like `www.product.com` and + # `beta1.product.com`. + Rails.application.config.hosts << ".product.com" + + *Genadi Samokovarov* + +* Remove redundant suffixes on generated helpers. + + *Gannon McGibbon* + +* Remove redundant suffixes on generated integration tests. + + *Gannon McGibbon* + +* Fix boolean interaction in scaffold system tests. + + *Gannon McGibbon* + +* Remove redundant suffixes on generated system tests. + + *Gannon McGibbon* + +* Add an `abort_on_failure` boolean option to the generator method that shell + out (`generate`, `rake`, `rails_command`) to abort the generator if the + command fails. + + *David RodrÃguez* + +* Remove `app/assets` and `app/javascript` from `eager_load_paths` and `autoload_paths`. + + *Gannon McGibbon* * Use Ids instead of memory addresses when displaying references in scaffold views. @@ -45,12 +204,20 @@ *Richard Schneeman* -* Support environment specific credentials file. +* Support environment specific credentials overrides. + + So any environment will look for `config/credentials/#{Rails.env}.yml.enc` and fall back + to `config/credentials.yml.enc`. + + The encryption key can be in `ENV["RAILS_MASTER_KEY"]` or `config/credentials/production.key`. + + Environment credentials overrides can be edited with `rails credentials:edit --environment production`. + If no override is set up for the passed environment, it will be created. + + Additionally, the default lookup paths can be overwritten with these configs: - For `production` environment look first for `config/credentials/production.yml.enc` file that can be decrypted by - `ENV["RAILS_MASTER_KEY"]` or `config/credentials/production.key` master key. - Edit given environment credentials file by command `rails credentials:edit --environment production`. - Default paths can be overwritten by setting `config.credentials.content_path` and `config.credentials.key_path`. + - `config.credentials.content_path` + - `config.credentials.key_path` *Wojciech WnÄ™trzak* @@ -135,9 +302,9 @@ *Jose Luis Duran* -* Deprecate support for using the `HOST` environment to specify the server IP. +* Deprecate support for using the `HOST` environment variable to specify the server IP. - The `BINDING` environment should be used instead. + The `BINDING` environment variable should be used instead. Fixes #29516. @@ -180,9 +347,9 @@ *Benoit Tigeot* -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. - *Jeremy Daer* + *Jeremy Daer*, *Kasper Timm Hansen* Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/railties/CHANGELOG.md) for previous changes. diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE index cce00cbc3a..ea8823d7ef 100644 --- a/railties/MIT-LICENSE +++ b/railties/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2018 David Heinemeier Hansson +Copyright (c) 2004-2019 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc index 89fc6bcbce..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 @@ -44,11 +44,11 @@ or to generate the body of an email. In \Rails, View generation is handled by {A {Active Record}[link:files/activerecord/README_rdoc.html], {Active Model}[link:files/activemodel/README_rdoc.html], {Action Pack}[link:files/actionpack/README_rdoc.html], and {Action View}[link:files/actionview/README_rdoc.html] can each be used independently outside \Rails. In addition to that, \Rails also comes with {Action Mailer}[link:files/actionmailer/README_rdoc.html], a library -to generate and send emails; {Active Job}[link:files/activejob/README_md.html], a -framework for declaring jobs and making them run on a variety of queueing +to generate and send emails; {Action Mailbox}[link:files/actionmailbox/README_md.html], a library to receive emails within a Rails application; +{Active Job}[link:files/activejob/README_md.html], a framework for declaring jobs and making them run on a variety of queueing backends; {Action Cable}[link:files/actioncable/README_md.html], a framework to integrate WebSockets with a \Rails application; {Active Storage}[link:files/activestorage/README_md.html], -a library to attach cloud and local files to \Rails applications; +a library to attach cloud and local files to \Rails applications; {Action Text}[link:files/actiontext/README_md.html], a library to handle rich text content; and {Active Support}[link:files/activesupport/README_rdoc.html], a collection of utility classes and standard library extensions that are useful for \Rails, and may also be used independently outside \Rails. @@ -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/Rakefile b/railties/Rakefile index 445f6217b3..4ae546b202 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -36,10 +36,20 @@ namespace :test do dirs = (ENV["TEST_DIR"] || ENV["TEST_DIRS"] || "**").split(",") test_options = ENV["TESTOPTS"].to_s.split(/[\s]+/) - test_files = dirs.map { |dir| "test/#{dir}/*_test.rb" } - Dir[*test_files].each do |file| - next true if file.start_with?("test/fixtures/") + test_patterns = dirs.map { |dir| "test/#{dir}/*_test.rb" } + test_files = Dir[*test_patterns].select do |file| + !file.start_with?("test/fixtures/") && !file.start_with?("test/isolation/assets/") + end.sort + if ENV["BUILDKITE_PARALLEL_JOB_COUNT"] + n = ENV["BUILDKITE_PARALLEL_JOB"].to_i + m = ENV["BUILDKITE_PARALLEL_JOB_COUNT"].to_i + + test_files = test_files.each_slice(m).map { |slice| slice[n] }.compact + end + + test_files.each do |file| + puts "--- #{file}" fake_command = Shellwords.join([ FileUtils::RUBY, "-w", @@ -59,10 +69,13 @@ namespace :test do unless $?.success? failing_files << file + puts "^^^ +++" end end + puts "--- All tests completed" unless failing_files.empty? + puts "^^^ +++" puts puts "Failed in:" failing_files.each do |file| @@ -70,7 +83,7 @@ namespace :test do end puts - raise "Failure in isolated test runner" + exit 1 end end end diff --git a/railties/lib/minitest/rails_plugin.rb b/railties/lib/minitest/rails_plugin.rb index 6486fa1798..4b7df6360a 100644 --- a/railties/lib/minitest/rails_plugin.rb +++ b/railties/lib/minitest/rails_plugin.rb @@ -54,6 +54,6 @@ module Minitest end end - # Backwardscompatibility with Rails 5.0 generated plugin test scripts + # Backwards compatibility with Rails 5.0 generated plugin test scripts mattr_reader :run_via, default: {} end diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index 092105d502..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" @@ -110,5 +111,9 @@ module Rails def public_path application && Pathname.new(application.paths["public"].first) end + + def autoloaders + Autoloaders + end end end diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb index f5dccd2381..da810f1eed 100644 --- a/railties/lib/rails/all.rb +++ b/railties/lib/rails/all.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# rubocop:disable Style/RedundantBegin + require "rails" %w( @@ -10,6 +12,8 @@ require "rails" action_mailer/railtie active_job/railtie action_cable/engine + action_mailbox/engine + action_text/engine rails/test_unit/railtie sprockets/railtie ).each do |railtie| diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb index e7f0557584..d5312843e4 100644 --- a/railties/lib/rails/api/task.rb +++ b/railties/lib/rails/api/task.rb @@ -74,6 +74,22 @@ module Rails ) }, + "actionmailbox" => { + include: %w( + README.md + app/**/action_mailbox/**/*.rb + lib/action_mailbox/**/*.rb + ) + }, + + "actiontext" => { + include: %w( + README.md + app/**/action_text/**/*.rb + lib/action_text/**/*.rb + ) + }, + "railties" => { include: %w( README.rdoc diff --git a/railties/lib/rails/app_loader.rb b/railties/lib/rails/app_loader.rb index aabcc5970c..cc057a407d 100644 --- a/railties/lib/rails/app_loader.rb +++ b/railties/lib/rails/app_loader.rb @@ -23,7 +23,7 @@ control: # too that you may or may not want (like yarn) If you already have Rails binstubs in source control, you might be -inadverently overwriting them during deployment by using bundle install +inadvertently overwriting them during deployment by using bundle install with the --binstubs option. If your application was created prior to Rails 4, here's how to upgrade: diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 656786246d..038284ebdd 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -7,6 +7,7 @@ require "active_support/key_generator" require "active_support/message_verifier" require "active_support/encrypted_configuration" require "active_support/deprecation" +require "active_support/hash_with_indifferent_access" require "rails/engine" require "rails/secrets" @@ -172,14 +173,9 @@ module Rails def key_generator # number of iterations selected based on consultation with the google security # team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220 - @caching_key_generator ||= - if secret_key_base - ActiveSupport::CachingKeyGenerator.new( - ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000) - ) - else - ActiveSupport::LegacyKeyGenerator.new(secrets.secret_token) - end + @caching_key_generator ||= ActiveSupport::CachingKeyGenerator.new( + ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000) + ) end # Returns a message verifier object. @@ -232,10 +228,12 @@ module Rails if yaml.exist? require "erb" - require "active_support/ordered_options" + config = YAML.load(ERB.new(yaml.read).result) || {} + config = (config["shared"] || {}).merge(config[env] || {}) - config = (YAML.load(ERB.new(yaml.read).result) || {})[env] || {} - ActiveSupport::InheritableOptions.new(config.deep_symbolize_keys) + ActiveSupport::OrderedOptions.new.tap do |options| + options.update(NonSymbolAccessDeprecatedHash.new(config)) + end else raise "Could not load configuration. No such file - #{yaml}" end @@ -252,7 +250,6 @@ module Rails super.merge( "action_dispatch.parameter_filter" => config.filter_parameters, "action_dispatch.redirect_filter" => config.filter_redirect, - "action_dispatch.secret_token" => secrets.secret_token, "action_dispatch.secret_key_base" => secret_key_base, "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions, "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local, @@ -402,14 +399,6 @@ module Rails # Fallback to config.secret_key_base if secrets.secret_key_base isn't set secrets.secret_key_base ||= config.secret_key_base - # Fallback to config.secret_token if secrets.secret_token isn't set - secrets.secret_token ||= config.secret_token - - if secrets.secret_token.present? - ActiveSupport::Deprecation.warn( - "`secrets.secret_token` is deprecated in favor of `secret_key_base` and will be removed in Rails 6.0." - ) - end secrets end @@ -420,14 +409,15 @@ module Rails # The secret_key_base is used as the input secret to the application's key generator, which in turn # is used to create all MessageVerifiers/MessageEncryptors, including the ones that sign and encrypt cookies. # - # In test and development, this is simply derived as a MD5 hash of the application's name. + # In development and test, this is randomly generated and stored in a + # temporary file in <tt>tmp/development_secret.txt</tt>. # # In all other environments, we look for it first in ENV["SECRET_KEY_BASE"], # then credentials.secret_key_base, and finally secrets.secret_key_base. For most applications, # the correct place to store it is in the encrypted credentials file. def secret_key_base - if Rails.env.test? || Rails.env.development? - secrets.secret_key_base || Digest::MD5.hexdigest(self.class.name) + if Rails.env.development? || Rails.env.test? + secrets.secret_key_base ||= generate_development_secret else validate_secret_key_base( ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base @@ -585,13 +575,29 @@ module Rails secret_key_base elsif secret_key_base raise ArgumentError, "`secret_key_base` for #{Rails.env} environment must be a type of String`" - elsif secrets.secret_token.blank? + else raise ArgumentError, "Missing `secret_key_base` for '#{Rails.env}' environment, set this string with `rails credentials:edit`" end end private + def generate_development_secret + if secrets.secret_key_base.nil? + key_file = Rails.root.join("tmp/development_secret.txt") + + if !File.exist?(key_file) + random_key = SecureRandom.hex(64) + FileUtils.mkdir_p(key_file.dirname) + File.binwrite(key_file, random_key) + end + + secrets.secret_key_base = File.binread(key_file) + end + + secrets.secret_key_base + end + def build_request(env) req = super env["ORIGINAL_FULLPATH"] = req.fullpath @@ -602,5 +608,52 @@ module Rails def build_middleware config.app_middleware + super end + + class NonSymbolAccessDeprecatedHash < HashWithIndifferentAccess # :nodoc: + def initialize(value = nil) + if value.is_a?(Hash) + value.each_pair { |k, v| self[k] = v } + else + super + end + end + + def []=(key, value) + regular_writer(key.to_sym, convert_value(value, for: :assignment)) + end + + private + + def convert_key(key) + unless key.kind_of?(Symbol) + ActiveSupport::Deprecation.warn(<<~MESSAGE.squish) + Accessing hashes returned from config_for by non-symbol keys + is deprecated and will be removed in Rails 6.1. + Use symbols for access instead. + MESSAGE + + key = key.to_sym + end + + 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 d6f8c4f47c..b79dbdbc6f 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "ipaddr" require "active_support/core_ext/kernel/reporting" require "active_support/file_update_checker" require "rails/engine/configuration" @@ -11,15 +12,16 @@ module Rails attr_accessor :allow_concurrency, :asset_host, :autoflush_log, :cache_classes, :cache_store, :consider_all_requests_local, :console, :eager_load, :exceptions_app, :file_watcher, :filter_parameters, - :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, - :railties_order, :relative_url_root, :secret_key_base, :secret_token, + :force_ssl, :helpers_paths, :hosts, :logger, :log_formatter, :log_tags, + :railties_order, :relative_url_root, :secret_key_base, :ssl_options, :public_file_server, :session_options, :time_zone, :reload_classes_only_on_change, :beginning_of_week, :filter_redirect, :x, :enable_dependency_loading, :read_encrypted_secrets, :log_level, :content_security_policy_report_only, - :content_security_policy_nonce_generator, :require_master_key, :credentials + :content_security_policy_nonce_generator, :require_master_key, :credentials, + :disable_sandbox - attr_reader :encoding, :api_only, :loaded_config_version + attr_reader :encoding, :api_only, :loaded_config_version, :autoloader def initialize(*) super @@ -29,6 +31,7 @@ module Rails @filter_parameters = [] @filter_redirect = [] @helpers_paths = [] + @hosts = Array(([IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0"), ".localhost"] if Rails.env.development?)) @public_file_server = ActiveSupport::OrderedOptions.new @public_file_server.enabled = true @public_file_server.index_name = "index" @@ -48,7 +51,6 @@ module Rails @autoflush_log = true @log_formatter = ActiveSupport::Logger::SimpleFormatter.new @eager_load = nil - @secret_token = nil @secret_key_base = nil @api_only = false @debug_exception_response_format = nil @@ -63,6 +65,8 @@ module Rails @credentials = ActiveSupport::OrderedOptions.new @credentials.content_path = default_credentials_content_path @credentials.key_path = default_credentials_key_path + @autoloader = :classic + @disable_sandbox = false end def load_defaults(target_version) @@ -95,10 +99,6 @@ module Rails if respond_to?(:active_record) active_record.cache_versioning = true - # Remove the temporary load hook from SQLite3Adapter when this is removed - ActiveSupport.on_load(:active_record_sqlite3adapter) do - ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true - end end if respond_to?(:action_dispatch) @@ -120,6 +120,8 @@ module Rails when "6.0" load_defaults "5.2" + self.autoloader = :zeitwerk if RUBY_ENGINE == "ruby" + if respond_to?(:action_view) action_view.default_enforce_utf8 = false end @@ -127,6 +129,19 @@ module Rails if respond_to?(:action_dispatch) action_dispatch.use_cookies_with_metadata = true end + + if respond_to?(:action_mailer) + action_mailer.delivery_job = "ActionMailer::MailDeliveryJob" + end + + if respond_to?(:active_job) + active_job.return_false_on_aborted_enqueue = true + end + + if respond_to?(:active_storage) + active_storage.queues.analysis = :active_storage_analysis + active_storage.queues.purge = :active_storage_purge + end else raise "Unknown version #{target_version.to_s.inspect}" end @@ -171,6 +186,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 @@ -257,6 +292,18 @@ module Rails end end + def autoloader=(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" + end + end + class Custom #:nodoc: def initialize @configurations = Hash.new @@ -278,25 +325,25 @@ module Rails end private - def credentials_available_for_current_env? - File.exist?("#{root}/config/credentials/#{Rails.env}.yml.enc") - end - def default_credentials_content_path if credentials_available_for_current_env? - File.join(root, "config", "credentials", "#{Rails.env}.yml.enc") + root.join("config", "credentials", "#{Rails.env}.yml.enc") else - File.join(root, "config", "credentials.yml.enc") + root.join("config", "credentials.yml.enc") end end def default_credentials_key_path if credentials_available_for_current_env? - File.join(root, "config", "credentials", "#{Rails.env}.key") + root.join("config", "credentials", "#{Rails.env}.key") else - File.join(root, "config", "master.key") + root.join("config", "master.key") end end + + def credentials_available_for_current_env? + File.exist?(root.join("config", "credentials", "#{Rails.env}.yml.enc")) + end end end end diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index 433a7ab41f..193cc59f3a 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -13,6 +13,8 @@ module Rails def build_stack ActionDispatch::MiddlewareStack.new do |middleware| + middleware.use ::ActionDispatch::HostAuthorization, config.hosts, config.action_dispatch.hosts_response_app + if config.force_ssl middleware.use ::ActionDispatch::SSL, config.ssl_options end 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..c4659123bb --- /dev/null +++ b/railties/lib/rails/application/dummy_erb_compiler.rb @@ -0,0 +1,19 @@ +# 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) + case stag + when "<%=" + out.push "_erbout << 'dummy_compiler'" + end + end +end diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 04aaf6dd9a..8d2c13d2a8 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -21,6 +21,13 @@ module Rails end end + initializer :let_zeitwerk_take_over do + if config.autoloader == :zeitwerk + require "active_support/dependencies/zeitwerk_integration" + ActiveSupport::Dependencies::ZeitwerkIntegration.take_over + end + end + initializer :add_builtin_route do |app| if Rails.env.development? app.routes.prepend do @@ -66,6 +73,10 @@ 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 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/code_statistics.rb b/railties/lib/rails/code_statistics.rb index 19d331ff30..09082282f3 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -95,8 +95,8 @@ class CodeStatistics #:nodoc: end def print_line(name, statistics) - m_over_c = (statistics.methods / statistics.classes) rescue m_over_c = 0 - loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue loc_over_m = 0 + m_over_c = (statistics.methods / statistics.classes) rescue 0 + loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue 0 print "| #{name.ljust(20)} " HEADERS.each_key do |k| diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb index 6d99ac9936..f09aa3ae0d 100644 --- a/railties/lib/rails/command.rb +++ b/railties/lib/rails/command.rb @@ -83,20 +83,21 @@ module Rails end def print_commands # :nodoc: - sorted_groups.each { |b, n| print_list(b, n) } + commands.each { |command| puts(" #{command}") } end - def sorted_groups # :nodoc: - lookup! + private + COMMANDS_IN_USAGE = %w(generate console server test test:system dbconsole new) + private_constant :COMMANDS_IN_USAGE - groups = (subclasses - hidden_commands).group_by { |c| c.namespace.split(":").first } - groups.transform_values! { |commands| commands.flat_map(&:printing_commands).sort } + def commands + lookup! - rails = groups.delete("rails") - [[ "rails", rails ]] + groups.sort.to_a - end + visible_commands = (subclasses - hidden_commands).flat_map(&:printing_commands) + + (visible_commands - COMMANDS_IN_USAGE).sort + end - private def command_type # :doc: @command_type ||= "command" end diff --git a/railties/lib/rails/command/actions.rb b/railties/lib/rails/command/actions.rb index cbb743346b..50651ad61a 100644 --- a/railties/lib/rails/command/actions.rb +++ b/railties/lib/rails/command/actions.rb @@ -11,10 +11,20 @@ module Rails end def require_application_and_environment! + require_application! + require_environment! + end + + def require_application! require ENGINE_PATH if defined?(ENGINE_PATH) if defined?(APP_PATH) require APP_PATH + end + end + + def require_environment! + if defined?(APP_PATH) Rails.application.require_environment! end end diff --git a/railties/lib/rails/command/base.rb b/railties/lib/rails/command/base.rb index 766872de8a..a22b198c66 100644 --- a/railties/lib/rails/command/base.rb +++ b/railties/lib/rails/command/base.rb @@ -115,7 +115,7 @@ module Rails # For a Rails::Command::TestCommand placed in <tt>rails/command/test_command.rb</tt> # would return <tt>rails/test</tt>. def default_command_root - path = File.expand_path(File.join("../commands", command_root_namespace), __dir__) + path = File.expand_path(relative_command_path, __dir__) path if File.exist?(path) end @@ -135,12 +135,20 @@ module Rails end def command_root_namespace - (namespace.split(":") - %w( rails )).first + (namespace.split(":") - %w(rails)).join(":") + end + + def relative_command_path + File.join("../commands", *command_root_namespace.split(":")) end def namespaced_commands commands.keys.map do |key| - key == command_root_namespace ? key : "#{command_root_namespace}:#{key}" + if command_root_namespace.match?(/(\A|\:)#{key}\z/) + command_root_namespace + else + "#{command_root_namespace}:#{key}" + end end end end diff --git a/railties/lib/rails/command/behavior.rb b/railties/lib/rails/command/behavior.rb index 718e2d9ab2..7fb2a99e67 100644 --- a/railties/lib/rails/command/behavior.rb +++ b/railties/lib/rails/command/behavior.rb @@ -56,12 +56,10 @@ module Rails def lookup! $LOAD_PATH.each do |base| Dir[File.join(base, *file_lookup_paths)].each do |path| - begin - path = path.sub("#{base}/", "") - require path - rescue Exception - # No problem - end + path = path.sub("#{base}/", "") + require path + rescue Exception + # No problem end end end @@ -73,8 +71,9 @@ module Rails paths = [] namespaces.each do |namespace| pieces = namespace.split(":") - paths << pieces.dup.push(pieces.last).join("/") - paths << pieces.join("/") + path = pieces.join("/") + paths << "#{path}/#{pieces.last}" + paths << path end paths.uniq! paths diff --git a/railties/lib/rails/command/environment_argument.rb b/railties/lib/rails/command/environment_argument.rb index 5dc98b113d..0cb3f1ce1e 100644 --- a/railties/lib/rails/command/environment_argument.rb +++ b/railties/lib/rails/command/environment_argument.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "active_support" +require "active_support/core_ext/class/attribute" module Rails module Command @@ -8,26 +9,16 @@ module Rails extend ActiveSupport::Concern included do - argument :environment, optional: true, banner: "environment" - - class_option :environment, aliases: "-e", type: :string, - desc: "Specifies the environment to run this console under (test/development/production)." + class_attribute :environment_desc, default: "Specifies the environment to run this #{self.command_name} under (test/development/production)." + class_option :environment, aliases: "-e", type: :string, desc: environment_desc end private - def extract_environment_option_from_argument - if environment - self.options = options.merge(environment: acceptable_environment(environment)) - - ActiveSupport::Deprecation.warn "Passing the environment's name as a " \ - "regular argument is deprecated and " \ - "will be removed in the next Rails " \ - "version. Please, use the -e option " \ - "instead." - elsif options[:environment] + def extract_environment_option_from_argument(default_environment: Rails::Command.environment) + if options[:environment] self.options = options.merge(environment: acceptable_environment(options[:environment])) else - self.options = options.merge(environment: Rails::Command.environment) + self.options = options.merge(environment: default_environment) end end diff --git a/railties/lib/rails/commands/console/console_command.rb b/railties/lib/rails/commands/console/console_command.rb index e35faa5b01..7a9eaefea1 100644 --- a/railties/lib/rails/commands/console/console_command.rb +++ b/railties/lib/rails/commands/console/console_command.rb @@ -26,6 +26,12 @@ module Rails @options = options app.sandbox = sandbox? + + if sandbox? && app.config.disable_sandbox + puts "Error: Unable to start console in sandbox mode as sandbox mode is disabled (config.disable_sandbox is true)." + exit 1 + end + app.load_console @console = app.config.console || IRB diff --git a/railties/lib/rails/commands/credentials/USAGE b/railties/lib/rails/commands/credentials/USAGE index 6b33d1ab74..c8d3fb9eda 100644 --- a/railties/lib/rails/commands/credentials/USAGE +++ b/railties/lib/rails/commands/credentials/USAGE @@ -41,9 +41,18 @@ from leaking. === Environment Specific Credentials -It is possible to have credentials for each environment. If the file for current environment exists it will take -precedence over `config/credentials.yml.enc`, thus for `production` environment first look for -`config/credentials/production.yml.enc` that can be decrypted using master key taken from `ENV["RAILS_MASTER_KEY"]` -or stored in `config/credentials/production.key`. -To edit given file use command `rails credentials:edit --environment production` -Default paths can be overwritten by setting `config.credentials.content_path` and `config.credentials.key_path`. +The `credentials` command supports passing an `--environment` option to create an +environment specific override. That override will take precedence over the +global `config/credentials.yml.enc` file when running in that environment. So: + + rails credentials:edit --environment development + +will create `config/credentials/development.yml.enc` with the corresponding +encryption key in `config/credentials/development.key` if the credentials file +doesn't exist. + +The encryption key can also be put in `ENV["RAILS_MASTER_KEY"]`, which takes +precedence over the file encryption key. + +In addition to that, the default credentials lookup paths can be overridden through +`config.credentials.content_path` and `config.credentials.key_path`. diff --git a/railties/lib/rails/commands/credentials/credentials_command.rb b/railties/lib/rails/commands/credentials/credentials_command.rb index 4b30d208e0..e23a1b3008 100644 --- a/railties/lib/rails/commands/credentials/credentials_command.rb +++ b/railties/lib/rails/commands/credentials/credentials_command.rb @@ -2,14 +2,15 @@ require "active_support" require "rails/command/helpers/editor" +require "rails/command/environment_argument" module Rails module Command class CredentialsCommand < Rails::Command::Base # :nodoc: include Helpers::Editor + include EnvironmentArgument - class_option :environment, aliases: "-e", type: :string, - desc: "Uses credentials from config/credentials/:environment.yml.enc encrypted by config/credentials/:environment.key key" + self.environment_desc = "Uses credentials from config/credentials/:environment.yml.enc encrypted by config/credentials/:environment.key key" no_commands do def help @@ -20,17 +21,16 @@ module Rails end def edit - require_application_and_environment! + extract_environment_option_from_argument(default_environment: nil) + require_application! ensure_editor_available(command: "bin/rails credentials:edit") || (return) - encrypted = Rails.application.encrypted(content_path, key_path: key_path) - - ensure_encryption_key_has_been_added(key_path) if encrypted.key.nil? - ensure_encrypted_file_has_been_added(content_path, key_path) + ensure_encryption_key_has_been_added if credentials.key.nil? + ensure_credentials_have_been_added catch_editing_exceptions do - change_encrypted_file_in_system_editor(content_path, key_path) + change_credentials_in_system_editor end say "File encrypted and saved." @@ -39,38 +39,53 @@ module Rails end def show - require_application_and_environment! - - encrypted = Rails.application.encrypted(content_path, key_path: key_path) + extract_environment_option_from_argument(default_environment: nil) + require_application! - say encrypted.read.presence || missing_encrypted_message(key: encrypted.key, key_path: key_path, file_path: content_path) + say credentials.read.presence || missing_credentials_message end private - def content_path - options[:environment] ? "config/credentials/#{options[:environment]}.yml.enc" : "config/credentials.yml.enc" - end - - def key_path - options[:environment] ? "config/credentials/#{options[:environment]}.key" : "config/master.key" + def credentials + Rails.application.encrypted(content_path, key_path: key_path) end - - def ensure_encryption_key_has_been_added(key_path) + def ensure_encryption_key_has_been_added encryption_key_file_generator.add_key_file(key_path) encryption_key_file_generator.ignore_key_file(key_path) end - def ensure_encrypted_file_has_been_added(file_path, key_path) - encrypted_file_generator.add_encrypted_file_silently(file_path, key_path) + def ensure_credentials_have_been_added + if options[:environment] + encrypted_file_generator.add_encrypted_file_silently(content_path, key_path) + else + credentials_generator.add_credentials_file_silently + end end - def change_encrypted_file_in_system_editor(file_path, key_path) - Rails.application.encrypted(file_path, key_path: key_path).change do |tmp_path| + def change_credentials_in_system_editor + credentials.change do |tmp_path| system("#{ENV["EDITOR"]} #{tmp_path}") end end + def missing_credentials_message + if credentials.key.nil? + "Missing '#{key_path}' to decrypt credentials. See `rails credentials:help`" + else + "File '#{content_path}' does not exist. Use `rails credentials:edit` to change that." + end + end + + + def content_path + options[:environment] ? "config/credentials/#{options[:environment]}.yml.enc" : "config/credentials.yml.enc" + end + + def key_path + options[:environment] ? "config/credentials/#{options[:environment]}.key" : "config/master.key" + end + def encryption_key_file_generator require "rails/generators" @@ -86,12 +101,11 @@ module Rails Rails::Generators::EncryptedFileGenerator.new end - def missing_encrypted_message(key:, key_path:, file_path:) - if key.nil? - "Missing '#{key_path}' to decrypt credentials. See `rails credentials:help`" - else - "File '#{file_path}' does not exist. Use `rails credentials:edit` to change that." - end + def credentials_generator + require "rails/generators" + require "rails/generators/rails/credentials/credentials_generator" + + Rails::Generators::CredentialsGenerator.new end end end diff --git a/railties/lib/rails/commands/db/system/change/change_command.rb b/railties/lib/rails/commands/db/system/change/change_command.rb new file mode 100644 index 0000000000..760c229c07 --- /dev/null +++ b/railties/lib/rails/commands/db/system/change/change_command.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "rails/generators" +require "rails/generators/rails/db/system/change/change_generator" + +module Rails + module Command + module Db + module System + class ChangeCommand < Base # :nodoc: + class_option :to, desc: "The database system to switch to." + + def perform + Rails::Generators::Db::System::ChangeGenerator.start + end + end + end + end + end +end diff --git a/railties/lib/rails/commands/encrypted/USAGE b/railties/lib/rails/commands/encrypted/USAGE new file mode 100644 index 0000000000..253eec2378 --- /dev/null +++ b/railties/lib/rails/commands/encrypted/USAGE @@ -0,0 +1,28 @@ +=== Storing Encrypted Files in Source Control + +The Rails `encrypted` commands provide access to encrypted files or configurations. +See the `Rails.application.encrypted` documentation for using them in your app. + +=== Encryption Keys + +By default, Rails looks for the encryption key in `config/master.key` or +`ENV["RAILS_MASTER_KEY"]`, but that lookup can be overridden with `--key`: + + rails encrypted:edit config/encrypted_file.yml.enc --key config/encrypted_file.key + +Don't commit the key! Add it to your source control's ignore file. If you use +Git, Rails handles this for you. + +=== Editing Files + +To edit or create an encrypted file use: + + rails encrypted:edit config/encrypted_file.yml.enc + +This opens a temporary file in `$EDITOR` with the decrypted contents for editing. + +=== Viewing Files + +To print the decrypted contents of an encrypted file use: + + rails encrypted:show config/encrypted_file.yml.enc diff --git a/railties/lib/rails/commands/encrypted/encrypted_command.rb b/railties/lib/rails/commands/encrypted/encrypted_command.rb index 8d5947652a..f10a07cdf8 100644 --- a/railties/lib/rails/commands/encrypted/encrypted_command.rb +++ b/railties/lib/rails/commands/encrypted/encrypted_command.rb @@ -16,6 +16,7 @@ module Rails def help say "Usage:\n #{self.class.banner}" say "" + say self.class.desc end end diff --git a/railties/lib/rails/commands/initializers/initializers_command.rb b/railties/lib/rails/commands/initializers/initializers_command.rb index 33596177af..bd2f3bed67 100644 --- a/railties/lib/rails/commands/initializers/initializers_command.rb +++ b/railties/lib/rails/commands/initializers/initializers_command.rb @@ -1,10 +1,17 @@ # frozen_string_literal: true +require "rails/command/environment_argument" + module Rails module Command class InitializersCommand < Base # :nodoc: + include EnvironmentArgument + desc "initializers", "Print out all defined initializers in the order they are invoked by Rails." def perform + extract_environment_option_from_argument + ENV["RAILS_ENV"] = options[:environment] + require_application_and_environment! Rails.application.initializers.tsort_each do |initializer| diff --git a/railties/lib/rails/commands/runner/runner_command.rb b/railties/lib/rails/commands/runner/runner_command.rb index cb693bcf34..40fb5e4d89 100644 --- a/railties/lib/rails/commands/runner/runner_command.rb +++ b/railties/lib/rails/commands/runner/runner_command.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true +require "rails/command/environment_argument" + module Rails module Command class RunnerCommand < Base # :nodoc: - class_option :environment, aliases: "-e", type: :string, - default: Rails::Command.environment.dup, - desc: "The environment for the runner to operate under (test/development/production)" + include EnvironmentArgument + + self.environment_desc = "The environment for the runner to operate under (test/development/production)" no_commands do def help @@ -19,6 +21,8 @@ module Rails end def perform(code_or_file = nil, *command_argv) + extract_environment_option_from_argument + unless code_or_file help exit 1 diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index 70789e0303..982b83ead5 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -6,6 +6,7 @@ require "rails" require "active_support/deprecation" require "active_support/core_ext/string/filters" require "rails/dev_caching" +require "rails/command/environment_argument" module Rails class Server < ::Rack::Server @@ -21,19 +22,6 @@ module Rails set_environment end - def app - @app ||= begin - app = super - if app.is_a?(Class) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Using `Rails::Application` subclass to start the server is deprecated and will be removed in Rails 6.0. - Please change `run #{app}` to `run Rails.application` in config.ru. - MSG - end - app.respond_to?(:to_app) ? app.to_app : app - end - end - def opt_parser Options.new end @@ -104,6 +92,8 @@ module Rails module Command class ServerCommand < Base # :nodoc: + include EnvironmentArgument + # Hard-coding a bunch of handlers here as we don't have a public way of # querying them from the Rack::Handler registry. RACK_SERVERS = %w(cgi fastcgi webrick lsws scgi thin puma unicorn) @@ -122,8 +112,6 @@ module Rails desc: "Uses a custom rackup configuration.", banner: :file class_option :daemon, aliases: "-d", type: :boolean, default: false, desc: "Runs server as a Daemon." - class_option :environment, aliases: "-e", type: :string, - desc: "Specifies the environment to run this server under (development/test/production).", banner: :name class_option :using, aliases: "-u", type: :string, desc: "Specifies the Rack server used to run the application (thin/puma/webrick).", banner: :name class_option :pid, aliases: "-P", type: :string, default: DEFAULT_PID_PATH, @@ -143,6 +131,7 @@ module Rails end def perform + extract_environment_option_from_argument set_application_directory! prepare_restart @@ -234,8 +223,8 @@ module Rails if ENV["HOST"] && !ENV["BINDING"] ActiveSupport::Deprecation.warn(<<-MSG.squish) - Using the `HOST` environment to specify the IP is deprecated and will be removed in Rails 6.1. - Please use `BINDING` environment instead. + Using the `HOST` environment variable to specify the IP is deprecated and will be removed in Rails 6.1. + Please use `BINDING` environment variable instead. MSG return ENV["HOST"] @@ -268,7 +257,7 @@ module Rails end def self.banner(*) - "rails server [thin/puma/webrick] [options]" + "rails server -u [thin/puma/webrick] [options]" end def prepare_restart @@ -277,7 +266,7 @@ module Rails def deprecate_positional_rack_server_and_rewrite_to_option(original_options) if using - ActiveSupport::Deprecation.warn(<<~MSG) + ActiveSupport::Deprecation.warn(<<~MSG.squish) Passing the Rack server name as a regular argument is deprecated and will be removed in the next Rails version. Please, use the -u option instead. @@ -302,9 +291,10 @@ module Rails MSG else suggestion = Rails::Command::Spellchecker.suggest(server, from: RACK_SERVERS) + suggestion_msg = "Maybe you meant #{suggestion.inspect}?" if suggestion <<~MSG - Could not find server "#{server}". Maybe you meant #{suggestion.inspect}? + Could not find server "#{server}". #{suggestion_msg} Run `rails server --help` for more options. MSG end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 6a13a84108..07bd56c978 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -472,11 +472,10 @@ module Rails # Eager load the application by loading all ruby # files inside eager_load paths. def eager_load! - config.eager_load_paths.each do |load_path| - matcher = /\A#{Regexp.escape(load_path.to_s)}\/(.*)\.rb\Z/ - Dir.glob("#{load_path}/**/*.rb").sort.each do |file| - require_dependency file.sub(matcher, '\1') - end + if Rails.autoloaders.zeitwerk_enabled? + eager_load_with_zeitwerk! + else + eager_load_with_dependencies! end end @@ -531,9 +530,9 @@ module Rails # Defines the routes for this engine. If a block is given to # routes, it is appended to the engine. - def routes + def routes(&block) @routes ||= ActionDispatch::Routing::RouteSet.new_with_config(config) - @routes.append(&Proc.new) if block_given? + @routes.append(&block) if block_given? @routes end @@ -548,7 +547,7 @@ module Rails # Blog::Engine.load_seed def load_seed seed_file = paths["db/seeds.rb"].existent.first - load(seed_file) if seed_file + with_inline_jobs { load(seed_file) } if seed_file end # Add configured load paths to Ruby's load path, and remove duplicate entries. @@ -652,12 +651,40 @@ module Rails private + def eager_load_with_zeitwerk! + (config.eager_load_paths - Zeitwerk::Loader.all_dirs).each do |path| + Dir.glob("#{path}/**/*.rb").sort.each { |file| require file } + end + end + + def eager_load_with_dependencies! + config.eager_load_paths.each do |load_path| + # Starts after load_path plus a slash, ends before ".rb". + relname_range = (load_path.to_s.length + 1)...-3 + Dir.glob("#{load_path}/**/*.rb").sort.each do |file| + require_dependency file[relname_range] + end + end + end + def load_config_initializer(initializer) # :doc: ActiveSupport::Notifications.instrument("load_config_initializer.railties", initializer: initializer) do load(initializer) end end + def with_inline_jobs + queue_adapter = config.active_job.queue_adapter + ActiveSupport.on_load(:active_job) do + self.queue_adapter = :inline + end + yield + ensure + ActiveSupport.on_load(:active_job) do + self.queue_adapter = queue_adapter + end + end + def has_migrations? paths["db/migrate"].existent.any? end diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 6bf0406b21..4143b3c881 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -38,7 +38,9 @@ module Rails @paths ||= begin paths = Rails::Paths::Root.new(@root) - paths.add "app", eager_load: true, glob: "{*,*/concerns}" + paths.add "app", eager_load: true, + glob: "{*,*/concerns}", + exclude: %w(assets javascript) paths.add "app/assets", glob: "*" paths.add "app/controllers", eager_load: true paths.add "app/channels", eager_load: true, glob: "**/*_channel.rb" diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb index 54bfbdd516..fea24810f5 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 = "alpha" + PRE = "beta3" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 5e8cebc50a..b835b3f3fd 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -23,6 +23,8 @@ module Rails autoload :ActiveModel, "rails/generators/active_model" autoload :Base, "rails/generators/base" autoload :Migration, "rails/generators/migration" + autoload :Database, "rails/generators/database" + autoload :AppName, "rails/generators/app_name" autoload :NamedBase, "rails/generators/named_base" autoload :ResourceHelpers, "rails/generators/resource_helpers" autoload :TestCase, "rails/generators/test_case" @@ -218,6 +220,7 @@ module Rails rails.delete("encryption_key_file") rails.delete("master_key") rails.delete("credentials") + rails.delete("db:system:change") hidden_namespaces.each { |n| groups.delete(n.to_s) } @@ -272,8 +275,10 @@ module Rails else options = sorted_groups.flat_map(&:last) suggestion = Rails::Command::Spellchecker.suggest(namespace.to_s, from: options) + suggestion_msg = "Maybe you meant #{suggestion.inspect}?" if suggestion + puts <<~MSG - Could not find generator '#{namespace}'. Maybe you meant #{suggestion.inspect}? + Could not find generator '#{namespace}'. #{suggestion_msg} Run `rails generate --help` for more options. MSG end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 78d2471890..1a5f2ff203 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -8,7 +8,6 @@ module Rails def initialize(*) # :nodoc: super @indentation = 0 - @after_bundle_callbacks = [] end # Adds an entry into +Gemfile+ for the supplied gem. @@ -221,9 +220,12 @@ module Rails # generate(:authenticated, "user session") def generate(what, *args) log :generate, what + + options = args.extract_options! + options[:without_rails_env] = true argument = args.flat_map(&:to_s).join(" ") - in_root { run_ruby_script("bin/rails generate #{what} #{argument}", verbose: false) } + execute_command :rails, "generate #{what} #{argument}", options end # Runs the supplied rake task (invoked with 'rake ...') @@ -246,15 +248,6 @@ module Rails execute_command :rails, command, options end - # Just run the capify command in root - # - # capify! - def capify! - ActiveSupport::Deprecation.warn("`capify!` is deprecated and will be removed in the next version of Rails.") - log :capify, "" - in_root { run("#{extify(:capify)} .", verbose: false) } - end - # Make an entry in Rails routing file <tt>config/routes.rb</tt> # # route "root 'welcome#index'" @@ -274,16 +267,6 @@ module Rails log File.read(find_in_source_paths(path)) end - # Registers a callback to be executed after bundle and spring binstubs - # have run. - # - # after_bundle do - # git add: '.' - # end - def after_bundle(&block) - @after_bundle_callbacks << block - end - private # Define log for backwards compatibility. If just one argument is sent, @@ -302,13 +285,15 @@ module Rails # based on the executor parameter provided. def execute_command(executor, command, options = {}) # :doc: log executor, command - env = options[:env] || ENV["RAILS_ENV"] || "development" + env = options[:env] || ENV["RAILS_ENV"] || "development" + rails_env = " RAILS_ENV=#{env}" unless options[:without_rails_env] sudo = options[:sudo] && !Gem.win_platform? ? "sudo " : "" config = { verbose: false } config[:capture] = options[:capture] if options[:capture] + config[:abort_on_failure] = options[:abort_on_failure] if options[:abort_on_failure] - in_root { run("#{sudo}#{extify(executor)} #{command} RAILS_ENV=#{env}", config) } + in_root { run("#{sudo}#{extify(executor)} #{command}#{rails_env}", config) } end # Add an extension to the given name based on the platform. diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 4dc4d27a46..66f6b57379 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -11,9 +11,8 @@ require "active_support/core_ext/array/extract_options" module Rails module Generators class AppBase < Base # :nodoc: - DATABASES = %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver ) - JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc ) - DATABASES.concat(JDBC_DATABASES) + include Database + include AppName attr_accessor :rails_template add_shebang_option! @@ -44,6 +43,12 @@ module Rails default: false, desc: "Skip Action Mailer files" + class_option :skip_action_mailbox, type: :boolean, default: false, + desc: "Skip Action Mailbox gem" + + class_option :skip_action_text, type: :boolean, default: false, + desc: "Skip Action Text gem" + class_option :skip_active_record, type: :boolean, aliases: "-O", default: false, desc: "Skip Active Record files" @@ -100,7 +105,6 @@ module Rails @gem_filter = lambda { |gem| true } @extra_entries = [] super - convert_database_option_for_jruby end private @@ -124,7 +128,7 @@ module Rails def gemfile_entries # :doc: [rails_gemfile_entry, database_gemfile_entry, - webserver_gemfile_entry, + web_server_gemfile_entry, assets_gemfile_entry, webpacker_gemfile_entry, javascript_gemfile_entry, @@ -185,7 +189,7 @@ module Rails "Use #{options[:database]} as the database for Active Record" end - def webserver_gemfile_entry # :doc: + def web_server_gemfile_entry # :doc: return [] if options[:skip_puma] comment = "Use Puma as the app server" GemfileEntry.new("puma", "~> 3.11", comment) @@ -200,7 +204,9 @@ module Rails :skip_sprockets, :skip_action_cable ), - skip_active_storage? + skip_active_storage?, + skip_action_mailbox?, + skip_action_text? ].flatten.none? end @@ -229,6 +235,14 @@ module Rails options[:skip_active_storage] || options[:skip_active_record] end + def skip_action_mailbox? # :doc: + options[:skip_action_mailbox] || skip_active_storage? + end + + def skip_action_text? # :doc: + options[:skip_action_text] || skip_active_storage? + end + class GemfileEntry < Struct.new(:name, :version, :comment, :options, :commented_out) def initialize(name, version, comment, options = {}, commented_out = false) super @@ -290,34 +304,6 @@ module Rails end end - def gem_for_database - # %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql ) - case options[:database] - when "mysql" then ["mysql2", [">= 0.4.4"]] - when "postgresql" then ["pg", [">= 0.18", "< 2.0"]] - when "oracle" then ["activerecord-oracle_enhanced-adapter", nil] - when "frontbase" then ["ruby-frontbase", nil] - when "sqlserver" then ["activerecord-sqlserver-adapter", nil] - when "jdbcmysql" then ["activerecord-jdbcmysql-adapter", nil] - when "jdbcsqlite3" then ["activerecord-jdbcsqlite3-adapter", nil] - when "jdbcpostgresql" then ["activerecord-jdbcpostgresql-adapter", nil] - when "jdbc" then ["activerecord-jdbc-adapter", nil] - else [options[:database], nil] - end - end - - def convert_database_option_for_jruby - if defined?(JRUBY_VERSION) - opt = options.dup - case opt[:database] - when "postgresql" then opt[:database] = "jdbcpostgresql" - when "mysql" then opt[:database] = "jdbcmysql" - when "sqlite3" then opt[:database] = "jdbcsqlite3" - end - self.options = opt.freeze - end - end - def assets_gemfile_entry return [] if options[:skip_sprockets] @@ -330,7 +316,7 @@ module Rails if options.dev? || options.edge? GemfileEntry.github "webpacker", "rails/webpacker", nil, "Use development version of Webpacker" else - GemfileEntry.new "webpacker", nil, "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 @@ -364,7 +350,7 @@ module Rails gems end - def bundle_command(command) + def bundle_command(command, env = {}) say_status :run, "bundle #{command}" # We are going to shell out rather than invoking Bundler::CLI.new(command) @@ -372,19 +358,21 @@ module Rails # its own vendored Thor, which could be a different version. Running both # things in the same process is a recipe for a night with paracetamol. # - # We unset temporary bundler variables to load proper bundler and Gemfile. - # # Thanks to James Tucker for the Gem tricks involved in this call. _bundle_command = Gem.bin_path("bundler", "bundle") require "bundler" - Bundler.with_clean_env do - full_command = %Q["#{Gem.ruby}" "#{_bundle_command}" #{command}] - if options[:quiet] - system(full_command, out: File::NULL) - else - system(full_command) - end + Bundler.with_original_env do + exec_bundle_command(_bundle_command, command, env) + end + end + + def exec_bundle_command(bundle_command, command, env) + full_command = %Q["#{Gem.ruby}" "#{bundle_command}" #{command}] + if options[:quiet] + system(env, full_command, out: File::NULL) + else + system(env, full_command) end end @@ -417,7 +405,7 @@ module Rails end def run_bundle - bundle_command("install") if bundle_install? + bundle_command("install", "BUNDLE_IGNORE_MESSAGES" => "1") if bundle_install? end def run_webpack diff --git a/railties/lib/rails/generators/app_name.rb b/railties/lib/rails/generators/app_name.rb new file mode 100644 index 0000000000..c4f71694d8 --- /dev/null +++ b/railties/lib/rails/generators/app_name.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Rails + module Generators + module AppName # :nodoc: + RESERVED_NAMES = %w(application destroy plugin runner test) + + private + def app_name + @app_name ||= original_app_name.tr("-", "_") + end + + def original_app_name + @original_app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr('\\', "").tr(". ", "_") + end + + def defined_app_name + defined_app_const_base.underscore + end + + def defined_app_const_base + Rails.respond_to?(:application) && defined?(Rails::Application) && + Rails.application.is_a?(Rails::Application) && Rails.application.class.name.chomp("::Application") + end + + alias :defined_app_const_base? :defined_app_const_base + + def app_const_base + @app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, "_").squeeze("_").camelize + end + alias :camelized :app_const_base + + def app_const + @app_const ||= "#{app_const_base}::Application" + end + + def valid_const? + if /^\d/.match?(app_const) + raise Error, "Invalid application name #{original_app_name}. Please give a name which does not start with numbers." + elsif RESERVED_NAMES.include?(original_app_name) + raise Error, "Invalid application name #{original_app_name}. Please give a " \ + "name which does not match one of the reserved rails " \ + "words: #{RESERVED_NAMES.join(", ")}" + elsif Object.const_defined?(app_const_base) + raise Error, "Invalid application name #{original_app_name}, constant #{app_const_base} is already in use. Please choose another application name." + end + end + end + end +end diff --git a/railties/lib/rails/generators/database.rb b/railties/lib/rails/generators/database.rb new file mode 100644 index 0000000000..18fc7be3ff --- /dev/null +++ b/railties/lib/rails/generators/database.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Rails + module Generators + module Database # :nodoc: + JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc ) + DATABASES = %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver ) + JDBC_DATABASES + + def initialize(*) + super + convert_database_option_for_jruby + end + + def gem_for_database(database = options[:database]) + case database + when "mysql" then ["mysql2", [">= 0.4.4"]] + when "postgresql" then ["pg", [">= 0.18", "< 2.0"]] + when "sqlite3" then ["sqlite3", ["~> 1.3", ">= 1.3.6"]] + when "oracle" then ["activerecord-oracle_enhanced-adapter", nil] + when "frontbase" then ["ruby-frontbase", nil] + when "sqlserver" then ["activerecord-sqlserver-adapter", nil] + when "jdbcmysql" then ["activerecord-jdbcmysql-adapter", nil] + when "jdbcsqlite3" then ["activerecord-jdbcsqlite3-adapter", nil] + when "jdbcpostgresql" then ["activerecord-jdbcpostgresql-adapter", nil] + when "jdbc" then ["activerecord-jdbc-adapter", nil] + else [database, nil] + end + end + + def convert_database_option_for_jruby + if defined?(JRUBY_VERSION) + opt = options.dup + case opt[:database] + when "postgresql" then opt[:database] = "jdbcpostgresql" + when "mysql" then opt[:database] = "jdbcmysql" + when "sqlite3" then opt[:database] = "jdbcsqlite3" + end + self.options = opt.freeze + end + end + + private + def mysql_socket + @mysql_socket ||= [ + "/tmp/mysql.sock", # default + "/var/run/mysqld/mysqld.sock", # debian/gentoo + "/var/tmp/mysql.sock", # freebsd + "/var/lib/mysql/mysql.sock", # fedora + "/opt/local/lib/mysql/mysql.sock", # fedora + "/opt/local/var/run/mysqld/mysqld.sock", # mac + darwinports + mysql + "/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4 + "/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5 + "/opt/lampp/var/mysql/mysql.sock" # xampp for linux + ].find { |f| File.exist?(f) } unless Gem.win_platform? + end + end + end +end diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt index 518cb1121e..b80c1280ce 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt @@ -4,9 +4,9 @@ <h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2> <ul> - <%% <%= singular_table_name %>.errors.full_messages.each do |message| %> - <li><%%= message %></li> - <%% end %> + <%% <%= singular_table_name %>.errors.full_messages.each do |message| %> + <li><%%= message %></li> + <%% end %> </ul> </div> <%% end %> diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 3f20f5a718..a8f7729fd3 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -39,23 +39,23 @@ module Rails private - # parse possible attribute options like :limit for string/text/binary/integer, :precision/:scale for decimals or :polymorphic for references/belongs_to - # when declaring options curly brackets should be used - def parse_type_and_options(type) - case type - when /(string|text|binary|integer)\{(\d+)\}/ - return $1, limit: $2.to_i - when /decimal\{(\d+)[,.-](\d+)\}/ - return :decimal, precision: $1.to_i, scale: $2.to_i - when /(references|belongs_to)\{(.+)\}/ - type = $1 - provided_options = $2.split(/[,.-]/) - options = Hash[provided_options.map { |opt| [opt.to_sym, true] }] - return type, options - else - return type, {} + # parse possible attribute options like :limit for string/text/binary/integer, :precision/:scale for decimals or :polymorphic for references/belongs_to + # when declaring options curly brackets should be used + def parse_type_and_options(type) + case type + when /(string|text|binary|integer)\{(\d+)\}/ + return $1, limit: $2.to_i + when /decimal\{(\d+)[,.-](\d+)\}/ + return :decimal, precision: $1.to_i, scale: $2.to_i + when /(references|belongs_to)\{(.+)\}/ + type = $1 + provided_options = $2.split(/[,.-]/) + options = Hash[provided_options.map { |opt| [opt.to_sym, true] }] + return type, options + else + return type, {} + end end - end end def initialize(name, type = nil, index_type = false, attr_options = {}) diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb index 5081060895..b6ec0160cf 100644 --- a/railties/lib/rails/generators/migration.rb +++ b/railties/lib/rails/generators/migration.rb @@ -63,8 +63,7 @@ module Rails numbered_destination = File.join(dir, ["%migration_number%", base].join("_")) create_migration numbered_destination, nil, config do - match = ERB.version.match(/\Aerb\.rb \[(?<version>[^ ]+) /) - if match && match[:version] >= "2.2.0" # Ruby 2.6+ + if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+ ERB.new(::File.binread(source), trim_mode: "-", eoutvar: "@output_buffer").result(context) else ERB.new(::File.binread(source), nil, "-", "@output_buffer").result(context) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index f56f79b8d4..f2f46d6e25 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -213,6 +213,7 @@ module Rails empty_directory_with_keep_file "test/helpers" empty_directory_with_keep_file "test/integration" + template "test/channels/application_cable/connection_test.rb" template "test/test_helper.rb" end @@ -241,7 +242,6 @@ module Rails # We need to store the RAILS_DEV_PATH in a constant, otherwise the path # can change in Ruby 1.8.7 when we FileUtils.cd. RAILS_DEV_PATH = File.expand_path("../../../../../..", __dir__) - RESERVED_NAMES = %w[application destroy plugin runner test] class AppGenerator < AppBase # :nodoc: WEBPACKS = %w( react vue angular elm stimulus ) @@ -258,8 +258,8 @@ module Rails class_option :skip_bundle, type: :boolean, aliases: "-B", default: false, desc: "Don't run bundle install" - class_option :webpack, type: :string, default: nil, - desc: "Preconfigure Webpack with a particular framework (options: #{WEBPACKS.join('/')})" + class_option :webpack, type: :string, aliases: "--webpacker", default: nil, + desc: "Preconfigure Webpack with a particular framework (options: #{WEBPACKS.join(", ")})" class_option :skip_webpack_install, type: :boolean, default: false, desc: "Don't run Webpack install" @@ -268,7 +268,7 @@ module Rails super if !options[:skip_active_record] && !DATABASES.include?(options[:database]) - raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}." + raise Error, "Invalid value for --database option. Supported preconfigurations are: #{DATABASES.join(", ")}." end # Force sprockets and yarn to be skipped when generating API only apps. @@ -276,6 +276,8 @@ module Rails if options[:api] self.options = options.merge(skip_sprockets: true, skip_javascript: true).freeze end + + @after_bundle_callbacks = [] end public_task :set_default_accessors! @@ -305,6 +307,13 @@ module Rails end remove_task :update_bin_files + def update_active_storage + unless skip_active_storage? + rails_command "active_storage:update" + end + end + remove_task :update_active_storage + def create_config_files build(:config) end @@ -440,6 +449,7 @@ module Rails if options[:skip_action_cable] remove_dir "app/javascript/channels" remove_dir "app/channels" + remove_dir "test/channels" end end @@ -489,58 +499,14 @@ module Rails create_file(*args, &block) end - def app_name - @app_name ||= original_app_name.tr("-", "_") - end - - def original_app_name - @original_app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr('\\', "").tr(". ", "_") - end - - def defined_app_name - defined_app_const_base.underscore - end - - def defined_app_const_base - Rails.respond_to?(:application) && defined?(Rails::Application) && - Rails.application.is_a?(Rails::Application) && Rails.application.class.name.sub(/::Application$/, "") - end - - alias :defined_app_const_base? :defined_app_const_base - - def app_const_base - @app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, "_").squeeze("_").camelize - end - alias :camelized :app_const_base - - def app_const - @app_const ||= "#{app_const_base}::Application" - end - - def valid_const? - if /^\d/.match?(app_const) - raise Error, "Invalid application name #{original_app_name}. Please give a name which does not start with numbers." - elsif RESERVED_NAMES.include?(original_app_name) - raise Error, "Invalid application name #{original_app_name}. Please give a " \ - "name which does not match one of the reserved rails " \ - "words: #{RESERVED_NAMES.join(", ")}" - elsif Object.const_defined?(app_const_base) - raise Error, "Invalid application name #{original_app_name}, constant #{app_const_base} is already in use. Please choose another application name." - end - end - - def mysql_socket - @mysql_socket ||= [ - "/tmp/mysql.sock", # default - "/var/run/mysqld/mysqld.sock", # debian/gentoo - "/var/tmp/mysql.sock", # freebsd - "/var/lib/mysql/mysql.sock", # fedora - "/opt/local/lib/mysql/mysql.sock", # fedora - "/opt/local/var/run/mysqld/mysqld.sock", # mac + darwinports + mysql - "/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4 - "/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5 - "/opt/lampp/var/mysql/mysql.sock" # xampp for linux - ].find { |f| File.exist?(f) } unless Gem.win_platform? + # Registers a callback to be executed after bundle and spring binstubs + # have run. + # + # after_bundle do + # git add: '.' + # end + def after_bundle(&block) # :doc: + @after_bundle_callbacks << block end def get_builder_class diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt index fb264935bd..d7221453e7 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt @@ -18,20 +18,17 @@ ruby <%= "'#{RUBY_VERSION}'" -%> <% end -%> <% end -%> -# Use ActiveModel has_secure_password +# Use Active Model has_secure_password # gem 'bcrypt', '~> 3.1.7' <% unless skip_active_storage? -%> -# Use ActiveStorage variant +# Use Active Storage variant # gem 'image_processing', '~> 1.2' <% end -%> -# Use Capistrano for deployment -# gem 'capistrano-rails', group: :development - <% if depend_on_bootsnap? -%> # Reduces boot times through caching; required in config/boot.rb -gem 'bootsnap', '>= 1.1.0', require: false +gem 'bootsnap', '>= 1.4.2', require: false <%- end -%> <%- if options.api? -%> @@ -72,8 +69,8 @@ group :test do # Adds support for Capybara system testing and selenium driver gem 'capybara', '>= 2.15' gem 'selenium-webdriver' - # Easy installation and use of chromedriver to run system tests with Chrome - gem 'chromedriver-helper' + # Easy installation and use of web drivers to run system tests with browsers + gem 'webdrivers' end <%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/consumer.js b/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/consumer.js index 76ca3d0f2f..0eceb59b18 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/consumer.js +++ b/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/consumer.js @@ -1,6 +1,6 @@ // Action Cable provides the framework to deal with WebSockets in Rails. // You can generate new channels where WebSocket features live using the `rails generate channel` command. -import ActionCable from "actioncable" +import { createConsumer } from "@rails/actioncable" -export default ActionCable.createConsumer() +export default createConsumer() 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 4d7a145cd6..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 @@ -3,19 +3,21 @@ // a relevant structure within app/javascript and only use these pack files to reference // that code so it'll be compiled. -import Rails from "rails-ujs" -Rails.start() +require("@rails/ujs").start() <%- unless options[:skip_turbolinks] -%> - -import Turbolinks from "turbolinks" -Turbolinks.start() +require("turbolinks").start() <%- end -%> <%- unless skip_active_storage? -%> - -import * as ActiveStorage from "activestorage" -ActiveStorage.start() +require("@rails/activestorage").start() <%- end -%> <%- unless options[:skip_action_cable] -%> - -import "channels" +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/app/templates/bin/setup.tt b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt index a18e03e7db..3f73bae3da 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt +++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt @@ -23,7 +23,7 @@ FileUtils.chdir APP_ROOT do # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') - # cp 'config/database.yml.sample', 'config/database.yml' + # FileUtils.cp 'config/database.yml.sample', 'config/database.yml' # end puts "\n== Preparing database ==" diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt index 9a427113c7..1b0ee54071 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt @@ -11,6 +11,8 @@ require "active_job/railtie" <%= comment_if :skip_active_storage %>require "active_storage/engine" require "action_controller/railtie" <%= comment_if :skip_action_mailer %>require "action_mailer/railtie" +<%= comment_if :skip_action_mailbox %>require "action_mailbox/engine" +<%= comment_if :skip_action_text %>require "action_text/engine" require "action_view/railtie" <%= comment_if :skip_action_cable %>require "action_cable/engine" <%= comment_if :skip_sprockets %>require "sprockets/railtie" diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt index 33f422c622..6ab4a26084 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt @@ -24,7 +24,7 @@ test: <<: *default database: <%= app_name %>_test -# As with config/secrets.yml, you never want to store sensitive information, +# As with config/credentials.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is # ever seen by anyone, they now have access to your database. # diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt index 681c765e93..e422aa31fc 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt @@ -60,7 +60,7 @@ test: <<: *default database: <%= app_name[0,4] %>_tst -# As with config/secrets.yml, you never want to store sensitive information, +# As with config/credentials.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is # ever seen by anyone, they now have access to your database. # diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt index af69f12059..678455c622 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt @@ -54,7 +54,7 @@ test: <<: *default url: jdbc:db://localhost/<%= app_name %>_test -# As with config/secrets.yml, you never want to store sensitive information, +# As with config/credentials.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is # ever seen by anyone, they now have access to your database. # diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt index f39593372c..b5a0efef47 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt @@ -27,7 +27,7 @@ test: <<: *default database: <%= app_name %>_test -# As with config/secrets.yml, you never want to store sensitive information, +# As with config/credentials.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is # ever seen by anyone, they now have access to your database. # diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt index df8a6ad627..009a81a6b8 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt @@ -1,4 +1,4 @@ -# PostgreSQL. Versions 9.1 and up are supported. +# PostgreSQL. Versions 9.3 and up are supported. # # Configure Using Gemfile # gem 'activerecord-jdbcpostgresql-adapter' @@ -43,7 +43,7 @@ test: <<: *default database: <%= app_name %>_test -# As with config/secrets.yml, you never want to store sensitive information, +# As with config/credentials.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is # ever seen by anyone, they now have access to your database. # diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt index b6c2e7448a..386eb511e5 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt @@ -32,7 +32,7 @@ test: <<: *default database: <%= app_name %>_test -# As with config/secrets.yml, you never want to store sensitive information, +# As with config/credentials.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is # ever seen by anyone, they now have access to your database. # diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt index 8d9d33ba6c..f7b6dfafab 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt @@ -33,7 +33,7 @@ test: <<: *default database: <%= app_name %>_test -# As with config/secrets.yml, you never want to store sensitive information, +# As with config/credentials.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is # ever seen by anyone, they now have access to your database. # diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt index dcd57425e2..44dafbd0c0 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt @@ -1,4 +1,4 @@ -# PostgreSQL. Versions 9.1 and up are supported. +# PostgreSQL. Versions 9.3 and up are supported. # # Install the pg driver: # gem install pg @@ -59,7 +59,7 @@ test: <<: *default database: <%= app_name %>_test -# As with config/secrets.yml, you never want to store sensitive information, +# As with config/credentials.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is # ever seen by anyone, they now have access to your database. # diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt index 0246fb0d02..27e8f2f35d 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt @@ -26,7 +26,7 @@ test: <<: *default database: <%= app_name %>_test -# As with config/secrets.yml, you never want to store sensitive information, +# As with config/credentials.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is # ever seen by anyone, they now have access to your database. # diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index 3807c8a9aa..2887bc2d67 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -16,6 +16,7 @@ Rails.application.configure do # Run rails dev:cache to toggle caching. if Rails.root.join('tmp', 'caching-dev.txt').exist? config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true config.cache_store = :memory_store config.public_file_server.headers = { diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 08befd9196..ed1cf09eeb 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -98,4 +98,25 @@ Rails.application.configure do # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false <%- end -%> + + # Inserts middleware to perform automatic connection switching. + # The `database_selector` hash is used to pass options to the DatabaseSelector + # middleware. The `delay` is used to determine how long to wait after a write + # to send a subsequent read to the primary. + # + # The `database_resolver` class is used by the middleware to determine which + # database is appropriate to use based on the time delay. + # + # The `database_resolver_context` class is used by the middleware to set + # timestamps for the last write to the primary. The resolver uses the context + # class timestamps to determine how long to wait before reading from the + # replica. + # + # By default Rails will store a last write timestamp in the session. The + # DatabaseSelector middleware is designed as such you can define your own + # strategy for connection switching and pass that into the middleware through + # these configuration options. + # config.active_record.database_selector = { delay: 2.seconds } + # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver + # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index 223aa56187..63ed3fa952 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -48,7 +48,4 @@ Rails.application.configure do # Raises error for missing translations. # config.action_view.raise_on_missing_translations = true - - # Prevent expensive template finalization at end of test suite runs. - config.action_view.finalize_compiled_template_methods = false end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt index fe48fc34ee..e92382f2d9 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt @@ -5,6 +5,10 @@ Rails.application.config.assets.version = '1.0' # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path +<%- unless options[:skip_javascript] -%> +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') +<%- end -%> # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in the app/assets diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_6_0.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_6_0.rb.tt index 54eb0cb1d2..d25552e923 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_6_0.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_6_0.rb.tt @@ -6,7 +6,7 @@ # # Read the Guide for Upgrading Ruby on Rails for more info on each option. -# Don't force requests from old versions of IE to be UTF-8 encoded +# Don't force requests from old versions of IE to be UTF-8 encoded. # Rails.application.config.action_view.default_enforce_utf8 = false # Embed purpose and expiry metadata inside signed and encrypted @@ -15,3 +15,19 @@ # This option is not backwards compatible with earlier Rails versions. # It's best enabled when your entire app is migrated and stable on 6.0. # Rails.application.config.action_dispatch.use_cookies_with_metadata = true + +# Return false instead of self when enqueuing is aborted from a callback. +# Rails.application.config.active_job.return_false_on_aborted_enqueue = true + +# Send Active Storage analysis and purge jobs to dedicated queues. +# Rails.application.config.active_storage.queues.analysis = :active_storage_analysis +# Rails.application.config.active_storage.queues.purge = :active_storage_purge + +# Use ActionMailer::MailDeliveryJob for sending parameterized and normal mail. +# +# The default delivery jobs (ActionMailer::Parameterized::DeliveryJob, ActionMailer::DeliveryJob), +# will be removed in Rails 6.1. This setting is not backwards compatible with earlier Rails versions. +# If you send mail in the background, job workers need to have a copy of +# MailDeliveryJob to ensure all delivery jobs are processed properly. +# Make sure your entire app is migrated and stable on 6.0 before using this setting. +# Rails.application.config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob" diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt index f6146e7259..649253aeca 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt @@ -17,7 +17,7 @@ port ENV.fetch("PORT") { 3000 } environment ENV.fetch("RAILS_ENV") { "development" } # Specifies the number of `workers` to boot in clustered mode. -# Workers are forked webserver processes. If using threads and workers together +# Workers are forked web server processes. If using threads and workers together # the concurrency of the application would be max `threads` * `workers`. # Workers do not work on JRuby or Windows (both of which do not support # processes). diff --git a/railties/lib/rails/generators/rails/app/templates/package.json.tt b/railties/lib/rails/generators/rails/app/templates/package.json.tt index 7174116989..07207e1747 100644 --- a/railties/lib/rails/generators/rails/app/templates/package.json.tt +++ b/railties/lib/rails/generators/rails/app/templates/package.json.tt @@ -2,10 +2,10 @@ "name": "<%= app_name %>", "private": true, "dependencies": { - "rails-ujs": ">=5.2.1"<% unless options[:skip_turbolinks] %>, - "turbolinks": "5.1.1"<% end -%><% unless skip_active_storage? %>, - "activestorage": ">=5.2.1"<% end -%><% unless options[:skip_action_cable] %>, - "actioncable": ">=5.2.1"<% end %> + "@rails/ujs": "^6.0.0-alpha"<% unless options[:skip_turbolinks] %>, + "turbolinks": "^5.2.0"<% end -%><% unless skip_active_storage? %>, + "@rails/activestorage": "^6.0.0-alpha"<% end -%><% unless options[:skip_action_cable] %>, + "@rails/actioncable": "^6.0.0-alpha"<% end %> }, "version": "0.1.0" } diff --git a/railties/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt b/railties/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt new file mode 100644 index 0000000000..800405f15e --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt @@ -0,0 +1,11 @@ +require "test_helper" + +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + # test "connects with cookies" do + # cookies.signed[:user_id] = 42 + # + # connect + # + # assert_equal connection.user_id, "42" + # end +end diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt index c918b57eca..47b4cf745c 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt @@ -4,10 +4,10 @@ require 'rails/test_help' class ActiveSupport::TestCase # Run tests in parallel with specified workers -<% if defined?(JRUBY_VERSION) -%> - parallelize(workers: 2, with: :threads) +<% if defined?(JRUBY_VERSION) || Gem.win_platform? -%> + parallelize(workers: :number_of_processors, with: :threads) <%- else -%> - parallelize(workers: 2) + parallelize(workers: :number_of_processors) <% end -%> <% unless options[:skip_active_record] -%> 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 new file mode 100644 index 0000000000..24db92fad7 --- /dev/null +++ b/railties/lib/rails/generators/rails/db/system/change/change_generator.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "rails/generators/base" + +module Rails + module Generators + module Db + module System + class ChangeGenerator < Base # :nodoc: + include Database + include AppName + + class_option :to, required: true, + desc: "The database system to switch to." + + def self.default_generator_root + path = File.expand_path(File.join(base_name, "app"), base_root) + path if File.exist?(path) + end + + def initialize(*) + super + + unless DATABASES.include?(options[:to]) + raise Error, "Invalid value for --to option. Supported preconfigurations are: #{DATABASES.join(", ")}." + end + + opt = options.dup + opt[:database] ||= opt[:to] + self.options = opt.freeze + end + + def edit_database_config + template("config/databases/#{options[:database]}.yml", "config/database.yml") + end + + def edit_gemfile + 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 + def all_database_gems + DATABASES.map { |database| gem_for_database(database) } + end + + def all_database_gems_regex + 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 + end +end diff --git a/railties/lib/rails/generators/rails/helper/helper_generator.rb b/railties/lib/rails/generators/rails/helper/helper_generator.rb index 3837c10ca0..542eb4c9e8 100644 --- a/railties/lib/rails/generators/rails/helper/helper_generator.rb +++ b/railties/lib/rails/generators/rails/helper/helper_generator.rb @@ -10,6 +10,11 @@ module Rails end hook_for :test_framework + + private + def file_name + @_file_name ||= super.sub(/_helper\z/i, "") + end end end end diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index 239b3a5739..79a06648b5 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -263,16 +263,6 @@ task default: :test public_task :apply_rails_template - def run_after_bundle_callbacks - unless @after_bundle_callbacks.empty? - ActiveSupport::Deprecation.warn("`after_bundle` is deprecated and will be removed in the next version of Rails. ") - end - - @after_bundle_callbacks.each do |callback| - callback.call - end - end - def name @name ||= begin # same as ActiveSupport::Inflector#underscore except not replacing '-' @@ -349,9 +339,9 @@ task default: :test def wrap_in_modules(unwrapped_code) unwrapped_code = "#{unwrapped_code}".strip.gsub(/\s$\n/, "") modules.reverse.inject(unwrapped_code) do |content, mod| - str = "module #{mod}\n" - str += content.lines.map { |line| " #{line}" }.join - str += content.present? ? "\nend" : "end" + str = +"module #{mod}\n" + str << content.lines.map { |line| " #{line}" }.join + str << (content.present? ? "\nend" : "end") end end diff --git a/railties/lib/rails/generators/test_unit/integration/integration_generator.rb b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb index ae307c5cd9..ba27ed329b 100644 --- a/railties/lib/rails/generators/test_unit/integration/integration_generator.rb +++ b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb @@ -10,6 +10,12 @@ module TestUnit # :nodoc: def create_test_files template "integration_test.rb", File.join("test/integration", class_path, "#{file_name}_test.rb") end + + private + + def file_name + @_file_name ||= super.sub(/_test\z/i, "") + 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/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb index e2e8b18eab..6df50c3217 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -54,6 +54,11 @@ module TestUnit # :nodoc: end end.sort.to_h end + + def boolean?(name) + attribute = attributes.find { |attr| attr.name == name } + attribute&.type == :boolean + end end end end diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt b/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt index 15bd7956b6..4f5bbf1108 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt @@ -16,8 +16,12 @@ class <%= class_name.pluralize %>Test < ApplicationSystemTestCase click_on "New <%= class_name.titleize %>" <%- attributes_hash.each do |attr, value| -%> + <%- if boolean?(attr) -%> + check "<%= attr.humanize %>" if <%= value %> + <%- else -%> fill_in "<%= attr.humanize %>", with: <%= value %> <%- end -%> + <%- end -%> click_on "Create <%= human_name %>" assert_text "<%= human_name %> was successfully created" @@ -29,8 +33,12 @@ class <%= class_name.pluralize %>Test < ApplicationSystemTestCase click_on "Edit", match: :first <%- attributes_hash.each do |attr, value| -%> + <%- if boolean?(attr) -%> + check "<%= attr.humanize %>" if <%= value %> + <%- else -%> fill_in "<%= attr.humanize %>", with: <%= value %> <%- end -%> + <%- end -%> click_on "Update <%= human_name %>" assert_text "<%= human_name %> was successfully updated" diff --git a/railties/lib/rails/generators/test_unit/system/system_generator.rb b/railties/lib/rails/generators/test_unit/system/system_generator.rb index 08504d4124..adecf74b70 100644 --- a/railties/lib/rails/generators/test_unit/system/system_generator.rb +++ b/railties/lib/rails/generators/test_unit/system/system_generator.rb @@ -14,6 +14,11 @@ module TestUnit # :nodoc: template "system_test.rb", File.join("test/system", class_path, "#{file_name.pluralize}_test.rb") end + + private + def file_name + @_file_name ||= super.sub(/_test\z/i, "") + end end end end diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index b8173c8d11..72b555ec19 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -54,10 +54,6 @@ module Rails table << "</table>" end end - - def to_json - Hash[properties].to_json - end end # The Rails version. @@ -67,7 +63,7 @@ module Rails # The Ruby version and platform, e.g. "2.0.0-p247 (x86_64-darwin12.4.0)". property "Ruby version" do - "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})" + RUBY_DESCRIPTION end # The RubyGems version, if it's installed. diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index 50fe176946..f74d979721 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -4,7 +4,7 @@ require "rails/application_controller" require "action_dispatch/routing/inspector" class Rails::InfoController < Rails::ApplicationController # :nodoc: - prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH + prepend_view_path ActionDispatch::DebugView::RESCUES_TEMPLATE_PATH layout -> { request.xhr? ? false : "application" } before_action :require_local! @@ -14,16 +14,8 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc: end def properties - respond_to do |format| - format.html do - @info = Rails::Info.to_html - @page_title = "Properties" - end - - format.json do - render json: Rails::Info.to_json - end - end + @info = Rails::Info.to_html + @page_title = "Properties" end def routes diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb index e2d36d7654..5cffa52860 100644 --- a/railties/lib/rails/mailers_controller.rb +++ b/railties/lib/rails/mailers_controller.rb @@ -3,7 +3,7 @@ require "rails/application_controller" class Rails::MailersController < Rails::ApplicationController # :nodoc: - prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH + prepend_view_path ActionDispatch::DebugView::RESCUES_TEMPLATE_PATH before_action :require_local!, unless: :show_previews? before_action :find_preview, :set_locale, only: :preview @@ -38,7 +38,7 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: end else @part = find_preferred_part(request.format, Mime[:html], Mime[:text]) - render action: "email", layout: false, formats: %w[html] + render action: "email", layout: false, formats: [:html] end else raise AbstractController::ActionNotFound, "Email '#{@email_action}' not found in #{@preview.name}" diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 87222563fd..8367ac8980 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -113,10 +113,11 @@ module Rails attr_accessor :glob def initialize(root, current, paths, options = {}) - @paths = paths - @current = current - @root = root - @glob = options[:glob] + @paths = paths + @current = current + @root = root + @glob = options[:glob] + @exclude = options[:exclude] options[:autoload_once] ? autoload_once! : skip_autoload_once! options[:eager_load] ? eager_load! : skip_eager_load! @@ -189,13 +190,11 @@ module Rails raise "You need to set a path root" unless @root.path result = [] - each do |p| - path = File.expand_path(p, @root.path) + each do |path| + path = File.expand_path(path, @root.path) if @glob && File.directory?(path) - Dir.chdir(path) do - result.concat(Dir.glob(@glob).map { |file| File.join path, file }.sort) - end + result.concat files_in(path) else result << path end @@ -222,6 +221,17 @@ module Rails end alias to_a expanded + + private + + def files_in(path) + Dir.chdir(path) do + files = Dir.glob(@glob) + files -= @exclude if @exclude + files.map! { |file| File.join(path, file) } + files.sort + end + end end end end diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb index b2d44d9b8e..ab5339bf24 100644 --- a/railties/lib/rails/ruby_version_check.rb +++ b/railties/lib/rails/ruby_version_check.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true -if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.4.1") && RUBY_ENGINE == "ruby" +if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.5.0") && RUBY_ENGINE == "ruby" desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})" abort <<-end_message - Rails 6 requires Ruby 2.4.1 or newer. + Rails 6 requires Ruby 2.5.0 or newer. You're running #{desc} - Please upgrade to Ruby 2.4.1 or newer to continue. + Please upgrade to Ruby 2.5.0 or newer to continue. end_message end diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 1a3711c446..2886986865 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -2,7 +2,7 @@ namespace :app do desc "Update configs and some other initially generated files (or use just update:configs or update:bin)" - task update: [ "update:configs", "update:bin", "update:upgrade_guide_info" ] + task update: [ "update:configs", "update:bin", "update:active_storage", "update:upgrade_guide_info" ] desc "Applies the template supplied by LOCATION=(/path/to/template) or URL" task template: :environment do @@ -51,6 +51,10 @@ namespace :app do Rails::AppUpdater.invoke_from_app_generator :update_bin_files end + task :active_storage do + Rails::AppUpdater.invoke_from_app_generator :update_active_storage + end + task :upgrade_guide_info do Rails::AppUpdater.invoke_from_app_generator :display_upgrade_guide_info end diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake index 8cacf4a49f..5abba7d3b4 100644 --- a/railties/lib/rails/tasks/statistics.rake +++ b/railties/lib/rails/tasks/statistics.rake @@ -9,6 +9,7 @@ STATS_DIRECTORIES = [ %w(Jobs app/jobs), %w(Models app/models), %w(Mailers app/mailers), + %w(Mailboxes app/mailboxes), %w(Channels app/channels), %w(JavaScripts app/assets/javascripts), %w(JavaScript app/javascript), @@ -18,6 +19,8 @@ STATS_DIRECTORIES = [ %w(Helper\ tests test/helpers), %w(Model\ tests test/models), %w(Mailer\ tests test/mailers), + %w(Mailbox\ tests test/mailboxes), + %w(Channel\ tests test/channels), %w(Job\ tests test/jobs), %w(Integration\ tests test/integration), %w(System\ tests test/system), diff --git a/railties/lib/rails/tasks/yarn.rake b/railties/lib/rails/tasks/yarn.rake index 4fb8586b69..48a8d8e143 100644 --- a/railties/lib/rails/tasks/yarn.rake +++ b/railties/lib/rails/tasks/yarn.rake @@ -6,8 +6,7 @@ namespace :yarn do # Install only production deps when for not usual envs. valid_node_envs = %w[test development production] node_env = ENV.fetch("NODE_ENV") do - rails_env = ENV["RAILS_ENV"] - valid_node_envs.include?(rails_env) ? rails_env : "production" + valid_node_envs.include?(Rails.env) ? Rails.env : "production" end system({ "NODE_ENV" => node_env }, "#{Rails.root}/bin/yarn install --no-progress --frozen-lockfile") end diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb index b6823457c0..6750e01029 100644 --- a/railties/lib/rails/templates/rails/welcome/index.html.erb +++ b/railties/lib/rails/templates/rails/welcome/index.html.erb @@ -66,7 +66,7 @@ <p class="version"> <strong>Rails version:</strong> <%= Rails.version %><br /> - <strong>Ruby version:</strong> <%= RUBY_VERSION %> (<%= RUBY_PLATFORM %>) + <strong>Ruby version:</strong> <%= RUBY_DESCRIPTION %> </p> </section> </div> diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index 32ac27a135..3a1b62d9d1 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -28,7 +28,7 @@ namespace :test do desc "Run tests quickly, but also reset db" task db: %w[db:test:prepare test] - ["models", "helpers", "controllers", "mailers", "integration", "jobs"].each do |name| + ["models", "helpers", "channels", "controllers", "mailers", "integration", "jobs", "mailboxes"].each do |name| task name => "test:prepare" do $: << "test" Rails::TestUnit::Runner.rake_run(["test/#{name}"]) diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 4e4a504c97..519b08746a 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -9,13 +9,13 @@ Gem::Specification.new do |s| s.summary = "Tools for creating, working with, and running Rails applications." s.description = "Rails internals: application bootup, plugins, generators, and rake tasks." - s.required_ruby_version = ">= 2.4.1" + s.required_ruby_version = ">= 2.5.0" s.license = "MIT" 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" @@ -37,7 +37,7 @@ Gem::Specification.new do |s| s.add_dependency "actionpack", version s.add_dependency "rake", ">= 0.8.7" - s.add_dependency "thor", ">= 0.19.0", "< 2.0" + s.add_dependency "thor", ">= 0.20.3", "< 2.0" s.add_dependency "method_source" s.add_development_dependency "actionview", version diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb index d02100d94c..a952d2466b 100644 --- a/railties/test/application/bin_setup_test.rb +++ b/railties/test/application/bin_setup_test.rb @@ -45,6 +45,8 @@ module ApplicationTests output.sub!(/^Resolving dependencies\.\.\.\n/, "") # Suppress Bundler platform warnings from output output.gsub!(/^The dependency .* will be unused .*\.\n/, "") + # Ignore warnings such as `Psych.safe_load is deprecated` + output.gsub!(/^warning:\s.*\n/, "") assert_equal(<<~OUTPUT, output) == Installing dependencies == diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index fa418f564b..b8e167b488 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -331,7 +331,7 @@ module ApplicationTests assert_not_includes Post.instance_methods, :title end - test "eager loads attribute methods in production" do + test "does not eager load attribute methods in production when the schema cache is empty" do app_file "app/models/post.rb", <<-RUBY class Post < ActiveRecord::Base end @@ -354,9 +354,71 @@ module ApplicationTests app "production" + assert_not_includes Post.instance_methods, :title + end + + test "eager loads attribute methods in production when the schema cache is populated" do + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app_file "config/initializers/active_record.rb", <<-RUBY + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + ActiveRecord::Migration.verbose = false + ActiveRecord::Schema.define(version: 1) do + create_table :posts do |t| + t.string :title + end + end + RUBY + + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + app_file "config/initializers/schema_cache.rb", <<-RUBY + ActiveRecord::Base.connection.schema_cache.add("posts") + RUBY + + app "production" + assert_includes Post.instance_methods, :title end + test "does not attempt to eager load attribute methods for models that aren't connected" do + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app_file "config/initializers/active_record.rb", <<-RUBY + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + ActiveRecord::Migration.verbose = false + ActiveRecord::Schema.define(version: 1) do + create_table :posts do |t| + t.string :title + end + end + RUBY + + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + app_file "app/models/comment.rb", <<-RUBY + class Comment < ActiveRecord::Base + establish_connection(adapter: "mysql2", database: "does_not_exist") + end + RUBY + + assert_nothing_raised do + app "production" + end + end + test "initialize an eager loaded, cache classes app" do add_to_config <<-RUBY config.eager_load = true @@ -534,45 +596,30 @@ module ApplicationTests assert_equal "some_value", verifier.verify(message) end - test "application message verifier can be used when the key_generator is ActiveSupport::LegacyKeyGenerator" do + test "application will generate secret_key_base in tmp file if blank in development" do app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.credentials.secret_key_base = nil - Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" RUBY - app "production" + # For test that works even if tmp dir does not exist. + Dir.chdir(app_path) { FileUtils.remove_dir("tmp") } - assert_kind_of ActiveSupport::LegacyKeyGenerator, Rails.application.key_generator - message = app.message_verifier(:sensitive_value).generate("some_value") - assert_equal "some_value", Rails.application.message_verifier(:sensitive_value).verify(message) + app "development" + + assert_not_nil app.secrets.secret_key_base + assert File.exist?(app_path("tmp/development_secret.txt")) end - test "config.secret_token is deprecated" do + test "application will not generate secret_key_base in tmp file if blank in production" do app_file "config/initializers/secret_token.rb", <<-RUBY - Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" + Rails.application.credentials.secret_key_base = nil RUBY - app "production" - - assert_deprecated(/secret_token/) do - app.secrets - end - end - - test "secrets.secret_token is deprecated" do - app_file "config/secrets.yml", <<-YAML - production: - secret_token: "b3c631c314c0bbca50c1b2843150fe33" - YAML - - app "production" - - assert_deprecated(/secret_token/) do - app.secrets + assert_raises ArgumentError do + app "production" end end - test "raises when secret_key_base is blank" do app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.credentials.secret_key_base = nil @@ -594,23 +641,8 @@ module ApplicationTests end end - test "prefer secrets.secret_token over config.secret_token" do - app_file "config/initializers/secret_token.rb", <<-RUBY - Rails.application.config.secret_token = "" - RUBY - app_file "config/secrets.yml", <<-YAML - development: - secret_token: 3b7cd727ee24e8444053437c36cc66c3 - YAML - - app "development" - - assert_equal "3b7cd727ee24e8444053437c36cc66c3", app.secrets.secret_token - end - test "application verifier can build different verifiers" do make_basic_app do |application| - application.credentials.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" application.config.session_store :disabled end @@ -649,22 +681,6 @@ module ApplicationTests assert_equal "3b7cd727ee24e8444053437c36cc66c3", app.secrets.secret_key_base end - test "config.secret_token over-writes a blank secrets.secret_token" do - app_file "config/initializers/secret_token.rb", <<-RUBY - Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" - RUBY - app_file "config/secrets.yml", <<-YAML - development: - secret_key_base: - secret_token: - YAML - - app "development" - - assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.secrets.secret_token - assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.config.secret_token - end - test "custom secrets saved in config/secrets.yml are loaded in app secrets" do app_file "config/secrets.yml", <<-YAML development: @@ -727,19 +743,6 @@ module ApplicationTests assert_equal "iaminallyoursecretkeybase", app.secrets.secret_key_base end - test "uses ActiveSupport::LegacyKeyGenerator as app.key_generator when secrets.secret_key_base is blank" do - app_file "config/initializers/secret_token.rb", <<-RUBY - Rails.application.credentials.secret_key_base = nil - Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" - RUBY - - app "production" - - assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.config.secret_token - assert_nil app.credentials.secret_key_base - assert_kind_of ActiveSupport::LegacyKeyGenerator, app.key_generator - end - test "that nested keys are symbolized the same as parents for hashes more than one level deep" do app_file "config/secrets.yml", <<-YAML development: @@ -1177,6 +1180,38 @@ module ApplicationTests end end + test "autoloaders" do + app "development" + + config = Rails.application.config + 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_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 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 + test "config.action_view.cache_template_loading with cache_classes default" do add_to_config "config.cache_classes = true" @@ -1493,14 +1528,12 @@ module ApplicationTests end test "config.session_store with :active_record_store with activerecord-session_store gem" do - begin - make_basic_app do |application| - ActionDispatch::Session::ActiveRecordStore = Class.new(ActionDispatch::Session::CookieStore) - application.config.session_store :active_record_store - end - ensure - ActionDispatch::Session.send :remove_const, :ActiveRecordStore + make_basic_app do |application| + ActionDispatch::Session::ActiveRecordStore = Class.new(ActionDispatch::Session::CookieStore) + application.config.session_store :active_record_store end + ensure + ActionDispatch::Session.send :remove_const, :ActiveRecordStore end test "config.session_store with :active_record_store without activerecord-session_store gem" do @@ -1663,6 +1696,14 @@ module ApplicationTests assert_kind_of Hash, Rails.application.config.database_configuration end + test "autoload paths do not include asset paths" do + app "development" + ActiveSupport::Dependencies.autoload_paths.each do |path| + assert_not_operator path, :ends_with?, "app/assets" + assert_not_operator path, :ends_with?, "app/javascript" + end + end + test "raises with proper error message if no database configuration found" do FileUtils.rm("#{app_path}/config/database.yml") err = assert_raises RuntimeError do @@ -1728,7 +1769,7 @@ module ApplicationTests assert_equal true, Rails.application.config.action_mailer.show_previews end - test "config_for loads custom configuration from yaml files" do + test "config_for loads custom configuration from yaml accessible as symbol or string" do app_file "config/custom.yml", <<-RUBY development: foo: 'bar' @@ -1740,13 +1781,16 @@ module ApplicationTests app "development" + assert_equal "bar", Rails.application.config.my_custom_config[:foo] assert_equal "bar", Rails.application.config.my_custom_config["foo"] end - test "config_for loads custom configuration from yaml accessible as symbol" do + test "config_for loads nested custom configuration from yaml as symbol keys" do app_file "config/custom.yml", <<-RUBY development: - foo: 'bar' + foo: + bar: + baz: 1 RUBY add_to_config <<-RUBY @@ -1755,13 +1799,15 @@ module ApplicationTests app "development" - assert_equal "bar", Rails.application.config.my_custom_config[:foo] + assert_equal 1, Rails.application.config.my_custom_config[:foo][:bar][:baz] end - test "config_for loads custom configuration from yaml accessible as method" do + test "config_for loads nested custom configuration from yaml with deprecated non-symbol access" do app_file "config/custom.yml", <<-RUBY development: - foo: 'bar' + foo: + bar: + baz: 1 RUBY add_to_config <<-RUBY @@ -1770,15 +1816,39 @@ module ApplicationTests app "development" - assert_equal "bar", Rails.application.config.my_custom_config.foo + assert_deprecated do + assert_equal 1, Rails.application.config.my_custom_config["foo"]["bar"]["baz"] + end end - test "config_for loads nested custom configuration from yaml as symbol keys" do + 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 + - 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: + foo: 0 + bar: + baz: 1 RUBY add_to_config <<-RUBY @@ -1787,7 +1857,95 @@ module ApplicationTests app "development" - assert_equal 1, Rails.application.config.my_custom_config.foo[:bar][:baz] + actual = Rails.application.config.my_custom_config + + assert_equal({ foo: 0, bar: { baz: 1 } }, actual) + assert_equal([ :foo, :bar ], actual.keys) + assert_equal([ 0, baz: 1], actual.values) + assert_equal({ foo: 0, bar: { baz: 1 } }, actual.to_h) + assert_equal(0, actual[:foo]) + assert_equal({ baz: 1 }, actual[:bar]) + end + + test "config_for generates deprecation notice when nested hash methods are called with non-symbols" do + app_file "config/custom.yml", <<-RUBY + development: + foo: + bar: 1 + baz: 2 + qux: + boo: 3 + RUBY + + app "development" + + actual = Rails.application.config_for("custom")[:foo] + + # slice + assert_deprecated do + assert_equal({ bar: 1, baz: 2 }, actual.slice("bar", "baz")) + end + + # except + assert_deprecated do + assert_equal({ qux: { boo: 3 } }, actual.except("bar", "baz")) + end + + # dig + assert_deprecated do + assert_equal(3, actual.dig("qux", "boo")) + end + + # fetch - hit + assert_deprecated do + assert_equal(1, actual.fetch("bar", 0)) + end + + # fetch - miss + assert_deprecated do + assert_equal(0, actual.fetch("does-not-exist", 0)) + end + + # fetch_values + assert_deprecated do + assert_equal([1, 2], actual.fetch_values("bar", "baz")) + end + + # key? - hit + assert_deprecated do + assert(actual.key?("bar")) + end + + # key? - miss + assert_deprecated do + assert_not(actual.key?("does-not-exist")) + end + + # slice! + actual = Rails.application.config_for("custom")[:foo] + + assert_deprecated do + slice = actual.slice!("bar", "baz") + assert_equal({ bar: 1, baz: 2 }, actual) + assert_equal({ qux: { boo: 3 } }, slice) + end + + # extract! + actual = Rails.application.config_for("custom")[:foo] + + assert_deprecated do + extracted = actual.extract!("bar", "baz") + assert_equal({ bar: 1, baz: 2 }, extracted) + assert_equal({ qux: { boo: 3 } }, actual) + end + + # except! + actual = Rails.application.config_for("custom")[:foo] + + assert_deprecated do + actual.except!("bar", "baz") + assert_equal({ qux: { boo: 3 } }, actual) + end end test "config_for uses the Pathname object if it is provided" do @@ -1802,7 +1960,7 @@ module ApplicationTests app "development" - assert_equal "custom key", Rails.application.config.my_custom_config["key"] + assert_equal "custom key", Rails.application.config.my_custom_config[:key] end test "config_for raises an exception if the file does not exist" do @@ -1832,8 +1990,29 @@ module ApplicationTests assert_equal({}, Rails.application.config.my_custom_config) end - test "config_for with empty file returns an empty hash" do + test "config_for implements shared configuration as secrets case found" do app_file "config/custom.yml", <<-RUBY + shared: + foo: :bar + test: + foo: :baz + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app "test" + + assert_equal(:baz, Rails.application.config.my_custom_config[:foo]) + end + + test "config_for implements shared configuration as secrets case not found" do + app_file "config/custom.yml", <<-RUBY + shared: + foo: :bar + test: + foo: :baz RUBY add_to_config <<-RUBY @@ -1842,40 +2021,45 @@ module ApplicationTests app "development" - assert_equal({}, Rails.application.config.my_custom_config) + assert_equal(:bar, Rails.application.config.my_custom_config[:foo]) end - test "default SQLite3Adapter.represent_boolean_as_integer for 5.1 is false" do - remove_from_config '.*config\.load_defaults.*\n' + test "config_for with empty file returns an empty hash" do + app_file "config/custom.yml", <<-RUBY + RUBY - app_file "app/models/post.rb", <<-RUBY - class Post < ActiveRecord::Base - end + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') RUBY app "development" - force_lazy_load_hooks { Post } - assert_not ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + assert_equal({}, Rails.application.config.my_custom_config) end - test "default SQLite3Adapter.represent_boolean_as_integer for new installs is true" do + test "represent_boolean_as_integer is deprecated" do + remove_from_config '.*config\.load_defaults.*\n' + + app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY + Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true + RUBY + app_file "app/models/post.rb", <<-RUBY class Post < ActiveRecord::Base end RUBY app "development" - force_lazy_load_hooks { Post } - - assert ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + assert_deprecated do + force_lazy_load_hooks { Post } + end end - test "represent_boolean_as_integer should be able to set via config.active_record.sqlite3.represent_boolean_as_integer" do + test "represent_boolean_as_integer raises when the value is false" do remove_from_config '.*config\.load_defaults.*\n' app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY - Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true + Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = false RUBY app_file "app/models/post.rb", <<-RUBY @@ -1884,9 +2068,9 @@ module ApplicationTests RUBY app "development" - force_lazy_load_hooks { Post } - - assert ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + assert_raises(RuntimeError) do + force_lazy_load_hooks { Post } + end end test "config_for containing ERB tags should evaluate" do @@ -1901,7 +2085,7 @@ module ApplicationTests app "development" - assert_equal "custom key", Rails.application.config.my_custom_config["key"] + assert_equal "custom key", Rails.application.config.my_custom_config[:key] end test "config_for with syntax error show a more descriptive exception" do @@ -1934,7 +2118,7 @@ module ApplicationTests RUBY require "#{app_path}/config/environment" - assert_equal "unicorn", Rails.application.config.my_custom_config["key"] + assert_equal "unicorn", Rails.application.config.my_custom_config[:key] end test "api_only is false by default" do @@ -2088,7 +2272,9 @@ module ApplicationTests test "ActionView::Template.finalize_compiled_template_methods is true by default" do app "test" - assert_equal true, ActionView::Template.finalize_compiled_template_methods + assert_deprecated do + ActionView::Template.finalize_compiled_template_methods + end end test "ActionView::Template.finalize_compiled_template_methods can be configured via config.action_view.finalize_compiled_template_methods" do @@ -2100,7 +2286,162 @@ module ApplicationTests app "test" - assert_equal false, ActionView::Template.finalize_compiled_template_methods + assert_deprecated do + ActionView::Template.finalize_compiled_template_methods + end + end + + test "ActiveJob::Base.return_false_on_aborted_enqueue is true by default" do + app "development" + + assert_equal true, ActiveJob::Base.return_false_on_aborted_enqueue + end + + test "ActiveJob::Base.return_false_on_aborted_enqueue is false in the 5.x defaults" do + remove_from_config '.*config\.load_defaults.*\n' + add_to_config 'config.load_defaults "5.2"' + + app "development" + + assert_equal false, ActiveJob::Base.return_false_on_aborted_enqueue + end + + test "ActiveJob::Base.return_false_on_aborted_enqueue can be configured in the new framework defaults" do + remove_from_config '.*config\.load_defaults.*\n' + + app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY + Rails.application.config.active_job.return_false_on_aborted_enqueue = true + RUBY + + app "development" + + assert_equal true, ActiveJob::Base.return_false_on_aborted_enqueue + end + + test "ActiveStorage.queues[:analysis] is :active_storage_analysis by default" do + app "development" + + assert_equal :active_storage_analysis, ActiveStorage.queues[:analysis] + end + + test "ActiveStorage.queues[:analysis] is nil without Rails 6 defaults" do + remove_from_config '.*config\.load_defaults.*\n' + + app "development" + + assert_nil ActiveStorage.queues[:analysis] + end + + test "ActiveStorage.queues[:purge] is :active_storage_purge by default" do + app "development" + + assert_equal :active_storage_purge, ActiveStorage.queues[:purge] + end + + test "ActiveStorage.queues[:purge] is nil without Rails 6 defaults" do + remove_from_config '.*config\.load_defaults.*\n' + + app "development" + + assert_nil ActiveStorage.queues[:purge] + end + + test "ActionMailbox.logger is Rails.logger by default" do + app "development" + + assert_equal Rails.logger, ActionMailbox.logger + end + + test "ActionMailbox.logger can be configured" do + app_file "lib/my_logger.rb", <<-RUBY + require "logger" + class MyLogger < ::Logger + end + RUBY + + add_to_config <<-RUBY + require "my_logger" + config.action_mailbox.logger = MyLogger.new(STDOUT) + RUBY + + app "development" + + assert_equal "MyLogger", ActionMailbox.logger.class.name + end + + test "ActionMailbox.incinerate_after is 30.days by default" do + app "development" + + assert_equal 30.days, ActionMailbox.incinerate_after + end + + test "ActionMailbox.incinerate_after can be configured" do + add_to_config <<-RUBY + config.action_mailbox.incinerate_after = 14.days + RUBY + + app "development" + + assert_equal 14.days, ActionMailbox.incinerate_after + end + + test "ActionMailbox.queues[:incineration] is :action_mailbox_incineration by default" do + app "development" + + assert_equal :action_mailbox_incineration, ActionMailbox.queues[:incineration] + end + + test "ActionMailbox.queues[:incineration] can be configured" do + add_to_config <<-RUBY + config.action_mailbox.queues.incineration = :another_queue + RUBY + + app "development" + + assert_equal :another_queue, ActionMailbox.queues[:incineration] + end + + test "ActionMailbox.queues[:routing] is :action_mailbox_routing by default" do + app "development" + + assert_equal :action_mailbox_routing, ActionMailbox.queues[:routing] + end + + test "ActionMailbox.queues[:routing] can be configured" do + add_to_config <<-RUBY + config.action_mailbox.queues.routing = :another_queue + RUBY + + app "development" + + assert_equal :another_queue, ActionMailbox.queues[:routing] + end + + test "ActionMailer::Base.delivery_job is ActionMailer::MailDeliveryJob by default" do + app "development" + + assert_equal ActionMailer::MailDeliveryJob, ActionMailer::Base.delivery_job + end + + test "ActionMailer::Base.delivery_job is ActionMailer::DeliveryJob in the 5.x defaults" do + remove_from_config '.*config\.load_defaults.*\n' + add_to_config 'config.load_defaults "5.2"' + + app "development" + + assert_equal ActionMailer::DeliveryJob, ActionMailer::Base.delivery_job + end + + test "ActionMailer::Base.delivery_job can be configured in the new framework defaults" do + remove_from_config '.*config\.load_defaults.*\n' + + app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY + Rails.application.config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob" + RUBY + + app "development" + + assert_equal ActionMailer::MailDeliveryJob, ActionMailer::Base.delivery_job end test "ActiveRecord::Base.filter_attributes should equal to filter_parameters" do @@ -2130,6 +2471,27 @@ module ApplicationTests MESSAGE end + test "hosts include .localhost in development" do + app "development" + assert_includes Rails.application.config.hosts, ".localhost" + end + + test "disable_sandbox is false by default" do + app "development" + + assert_equal false, Rails.configuration.disable_sandbox + end + + test "disable_sandbox can be overridden" do + add_to_config <<-RUBY + config.disable_sandbox = true + RUBY + + app "development" + + assert Rails.configuration.disable_sandbox + end + private def force_lazy_load_hooks yield # Tasty clarifying sugar, homie! We only need to reference a constant to load it. diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index 29f8b1e3d9..db16f4cc56 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -25,7 +25,7 @@ class ConsoleTest < ActiveSupport::TestCase end def test_app_method_should_return_integration_session - TestHelpers::Rack.send :remove_method, :app + TestHelpers::Rack.remove_method :app load_environment console_session = irb_context.app assert_instance_of ActionDispatch::Integration::Session, console_session @@ -123,13 +123,17 @@ class FullStackConsoleTest < ActiveSupport::TestCase assert_output "> ", @primary end - def spawn_console(options) - Process.spawn( + def spawn_console(options, wait_for_prompt: true) + pid = Process.spawn( "#{app_path}/bin/rails console #{options}", in: @replica, out: @replica, err: @replica ) - assert_output "> ", @primary, 30 + if wait_for_prompt + assert_output "> ", @primary, 30 + end + + pid end def test_sandbox @@ -148,8 +152,19 @@ class FullStackConsoleTest < ActiveSupport::TestCase @primary.puts "quit" end + def test_sandbox_when_sandbox_is_disabled + add_to_config <<-RUBY + config.disable_sandbox = true + RUBY + + output = `#{app_path}/bin/rails console --sandbox` + + assert_includes output, "sandbox mode is disabled" + assert_equal 1, $?.exitstatus + end + def test_environment_option_and_irb_option - spawn_console("test -- --verbose") + spawn_console("-e test -- --verbose") write_prompt "a = 1", "a = 1" write_prompt "puts Rails.env", "puts Rails.env\r\ntest" diff --git a/railties/test/application/credentials_test.rb b/railties/test/application/credentials_test.rb new file mode 100644 index 0000000000..2f6b109b50 --- /dev/null +++ b/railties/test/application/credentials_test.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "env_helpers" + +class Rails::CredentialsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation, EnvHelpers + + setup :build_app + teardown :teardown_app + + test "reads credentials from environment specific path" do + write_credentials_override(:production) + + app("production") + + assert_equal "revealed", Rails.application.credentials.mystery + end + + test "reads credentials from customized path and key" do + write_credentials_override(:staging) + add_to_env_config("production", "config.credentials.content_path = config.root.join('config/credentials/staging.yml.enc')") + add_to_env_config("production", "config.credentials.key_path = config.root.join('config/credentials/staging.key')") + + app("production") + + assert_equal "revealed", Rails.application.credentials.mystery + end + + test "reads credentials using environment variable key" do + write_credentials_override(:production, with_key: false) + + switch_env("RAILS_MASTER_KEY", credentials_key) do + app("production") + + assert_equal "revealed", Rails.application.credentials.mystery + end + end + + private + def write_credentials_override(name, with_key: true) + Dir.chdir(app_path) do + Dir.mkdir "config/credentials" + File.write "config/credentials/#{name}.key", credentials_key if with_key + + # secret_key_base: secret + # mystery: revealed + File.write "config/credentials/#{name}.yml.enc", + "vgvKu4MBepIgZ5VHQMMPwnQNsLlWD9LKmJHu3UA/8yj6x+3fNhz3DwL9brX7UA==--qLdxHP6e34xeTAiI--nrcAsleXuo9NqiEuhntAhw==" + end + end + + def credentials_key + "2117e775dc2024d4f49ddf3aeb585919" + end +end diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 1530ea82d6..3cd4b8fe33 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -230,35 +230,31 @@ module ApplicationTests end test "active record establish_connection uses Rails.env if DATABASE_URL is not set" do - begin - require "#{app_path}/config/environment" - orig_database_url = ENV.delete("DATABASE_URL") - orig_rails_env, Rails.env = Rails.env, "development" - ActiveRecord::Base.establish_connection - assert ActiveRecord::Base.connection - assert_match(/#{ActiveRecord::Base.configurations[Rails.env]['database']}/, ActiveRecord::Base.connection_config[:database]) - ensure - ActiveRecord::Base.remove_connection - ENV["DATABASE_URL"] = orig_database_url if orig_database_url - Rails.env = orig_rails_env if orig_rails_env - end + require "#{app_path}/config/environment" + orig_database_url = ENV.delete("DATABASE_URL") + orig_rails_env, Rails.env = Rails.env, "development" + ActiveRecord::Base.establish_connection + assert ActiveRecord::Base.connection + assert_match(/#{ActiveRecord::Base.configurations[Rails.env]['database']}/, ActiveRecord::Base.connection_config[:database]) + ensure + ActiveRecord::Base.remove_connection + ENV["DATABASE_URL"] = orig_database_url if orig_database_url + Rails.env = orig_rails_env if orig_rails_env end test "active record establish_connection uses DATABASE_URL even if Rails.env is set" do - begin - require "#{app_path}/config/environment" - orig_database_url = ENV.delete("DATABASE_URL") - orig_rails_env, Rails.env = Rails.env, "development" - database_url_db_name = "db/database_url_db.sqlite3" - ENV["DATABASE_URL"] = "sqlite3:#{database_url_db_name}" - ActiveRecord::Base.establish_connection - assert ActiveRecord::Base.connection - assert_match(/#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database]) - ensure - ActiveRecord::Base.remove_connection - ENV["DATABASE_URL"] = orig_database_url if orig_database_url - Rails.env = orig_rails_env if orig_rails_env - end + require "#{app_path}/config/environment" + orig_database_url = ENV.delete("DATABASE_URL") + orig_rails_env, Rails.env = Rails.env, "development" + database_url_db_name = "db/database_url_db.sqlite3" + ENV["DATABASE_URL"] = "sqlite3:#{database_url_db_name}" + ActiveRecord::Base.establish_connection + assert ActiveRecord::Base.connection + assert_match(/#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database]) + ensure + ActiveRecord::Base.remove_connection + ENV["DATABASE_URL"] = orig_database_url if orig_database_url + Rails.env = orig_rails_env if orig_rails_env end test "connections checked out during initialization are returned to the pool" do 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/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb index ba186bda44..fb84276b8a 100644 --- a/railties/test/application/mailer_previews_test.rb +++ b/railties/test/application/mailer_previews_test.rb @@ -85,6 +85,7 @@ module ApplicationTests end test "mailer previews are loaded from a custom preview_path" do + app_dir "lib/mailer_previews" add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'" mailer "notifier", <<-RUBY @@ -254,6 +255,7 @@ module ApplicationTests end test "mailer previews are reloaded from a custom preview_path" do + app_dir "lib/mailer_previews" add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'" app("development") @@ -818,6 +820,7 @@ module ApplicationTests def build_app super app_file "config/routes.rb", "Rails.application.routes.draw do; end" + app_dir "test/mailers/previews" end def mailer(name, contents) diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb index 83cf8a27f7..515b32080e 100644 --- a/railties/test/application/middleware/remote_ip_test.rb +++ b/railties/test/application/middleware/remote_ip_test.rb @@ -12,7 +12,9 @@ module ApplicationTests remote_ip = nil env = Rack::MockRequest.env_for("/").merge(env).merge!( "action_dispatch.show_exceptions" => false, - "action_dispatch.key_generator" => ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33") + "action_dispatch.key_generator" => ActiveSupport::CachingKeyGenerator.new( + ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33", iterations: 1000) + ) ) endpoint = Proc.new do |e| diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb index b25e56b625..479615c133 100644 --- a/railties/test/application/middleware/session_test.rb +++ b/railties/test/application/middleware/session_test.rb @@ -215,8 +215,6 @@ module ApplicationTests RUBY add_to_config <<-RUBY - secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" - # Enable AEAD cookies config.action_dispatch.use_authenticated_cookie_encryption = true RUBY @@ -238,68 +236,6 @@ module ApplicationTests assert_equal 1, encryptor.decrypt_and_verify(last_response.body, purpose: "cookie._myapp_session")["foo"] end - test "session upgrading signature to encryption cookie store upgrades session to encrypted mode" do - app_file "config/routes.rb", <<-RUBY - Rails.application.routes.draw do - get ':controller(/:action)' - end - RUBY - - controller :foo, <<-RUBY - class FooController < ActionController::Base - def write_raw_session - # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1} - cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749" - head :ok - end - - def write_session - session[:foo] = session[:foo] + 1 - head :ok - end - - def read_session - render plain: session[:foo] - end - - def read_encrypted_cookie - render plain: cookies.encrypted[:_myapp_session]['foo'] - end - - def read_raw_cookie - render plain: cookies[:_myapp_session] - end - end - RUBY - - add_to_config <<-RUBY - secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" - - # Enable AEAD cookies - config.action_dispatch.use_authenticated_cookie_encryption = true - RUBY - - require "#{app_path}/config/environment" - - get "/foo/write_raw_session" - get "/foo/read_session" - assert_equal "1", last_response.body - - get "/foo/write_session" - get "/foo/read_session" - assert_equal "2", last_response.body - - get "/foo/read_encrypted_cookie" - assert_equal "2", last_response.body - - cipher = "aes-256-gcm" - secret = app.key_generator.generate_key("authenticated encrypted cookie") - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) - - get "/foo/read_raw_cookie" - assert_equal 2, encryptor.decrypt_and_verify(last_response.body, purpose: "cookie._myapp_session")["foo"] - end - test "session upgrading from AES-CBC-HMAC encryption to AES-GCM encryption" do app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do @@ -370,70 +306,6 @@ module ApplicationTests end end - test "session upgrading legacy signed cookies to new signed cookies" do - app_file "config/routes.rb", <<-RUBY - Rails.application.routes.draw do - get ':controller(/:action)' - end - RUBY - - controller :foo, <<-RUBY - class FooController < ActionController::Base - def write_raw_session - # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1} - cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749" - head :ok - end - - def write_session - session[:foo] = session[:foo] + 1 - head :ok - end - - def read_session - render plain: session[:foo] - end - - def read_signed_cookie - render plain: cookies.signed[:_myapp_session]['foo'] - end - - def read_raw_cookie - render plain: cookies[:_myapp_session] - end - end - RUBY - - add_to_config <<-RUBY - secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" - Rails.application.credentials.secret_key_base = nil - RUBY - - begin - old_rails_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "production" - - require "#{app_path}/config/environment" - - get "/foo/write_raw_session" - get "/foo/read_session" - assert_equal "1", last_response.body - - get "/foo/write_session" - get "/foo/read_session" - assert_equal "2", last_response.body - - get "/foo/read_signed_cookie" - assert_equal "2", last_response.body - - verifier = ActiveSupport::MessageVerifier.new(app.secrets.secret_token) - - get "/foo/read_raw_cookie" - assert_equal 2, verifier.verify(last_response.body, purpose: "cookie._myapp_session")["foo"] - ensure - ENV["RAILS_ENV"] = old_rails_env - end - end - test "calling reset_session on request does not trigger an error for API apps" do add_to_config "config.api_only = true" diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 631f5bac7f..4242daf39a 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -26,6 +26,7 @@ module ApplicationTests assert_equal [ "Webpacker::DevServerProxy", + "ActionDispatch::HostAuthorization", "Rack::Sendfile", "ActionDispatch::Static", "ActionDispatch::Executor", @@ -58,6 +59,7 @@ module ApplicationTests assert_equal [ "Webpacker::DevServerProxy", + "ActionDispatch::HostAuthorization", "Rack::Sendfile", "ActionDispatch::Static", "ActionDispatch::Executor", @@ -140,7 +142,7 @@ module ApplicationTests add_to_config "config.ssl_options = { redirect: { host: 'example.com' } }" boot! - assert_equal [{ redirect: { host: "example.com" } }], Rails.application.middleware[1].args + assert_equal [{ redirect: { host: "example.com" } }], Rails.application.middleware[2].args end test "removing Active Record omits its middleware" do @@ -224,7 +226,7 @@ module ApplicationTests test "insert middleware after" do add_to_config "config.middleware.insert_after Rack::Sendfile, Rack::Config" boot! - assert_equal "Rack::Config", middleware.third + assert_equal "Rack::Config", middleware.fourth end test "unshift middleware" do @@ -236,24 +238,24 @@ module ApplicationTests test "Rails.cache does not respond to middleware" do add_to_config "config.cache_store = :memory_store" boot! - assert_equal "Rack::Runtime", middleware.fifth + assert_equal "Rack::Runtime", middleware[5] end test "Rails.cache does respond to middleware" do boot! - assert_equal "ActiveSupport::Cache::Strategy::LocalCache", middleware.fifth - assert_equal "Rack::Runtime", middleware[5] + assert_equal "ActiveSupport::Cache::Strategy::LocalCache", middleware[5] + assert_equal "Rack::Runtime", middleware[6] end test "insert middleware before" do add_to_config "config.middleware.insert_before Rack::Sendfile, Rack::Config" boot! - assert_equal "Rack::Config", middleware.second + assert_equal "Rack::Config", middleware.third end test "can't change middleware after it's built" do boot! - assert_raise frozen_error_class do + assert_raise FrozenError do app.config.middleware.use Rack::Config end end diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb index d6c81c1fe2..f0f1112f6b 100644 --- a/railties/test/application/multiple_applications_test.rb +++ b/railties/test/application/multiple_applications_test.rb @@ -100,30 +100,6 @@ module ApplicationTests assert_nothing_raised { AppTemplate::Application.new } end - def test_initializers_run_on_different_applications_go_to_the_same_class - application1 = AppTemplate::Application.new - run_count = 0 - - AppTemplate::Application.initializer :init0 do - run_count += 1 - end - - application1.initializer :init1 do - run_count += 1 - end - - AppTemplate::Application.new.initializer :init2 do - run_count += 1 - end - - assert_equal 0, run_count, "Without loading the initializers, the count should be 0" - - # Set config.eager_load to false so that an eager_load warning doesn't pop up - AppTemplate::Application.create { config.eager_load = false }.initialize! - - assert_equal 3, run_count, "There should have been three initializers that incremented the count" - end - def test_consoles_run_on_different_applications_go_to_the_same_class run_count = 0 AppTemplate::Application.console { run_count += 1 } @@ -165,12 +141,12 @@ module ApplicationTests app.config.some_setting = "a_different_setting" assert_equal "a_different_setting", app.config.some_setting, "The configuration's some_setting should be set." - new_config = Rails::Application::Configuration.new("root_of_application") + new_config = Rails::Application::Configuration.new(Pathname.new("root_of_application")) new_config.some_setting = "some_setting_dude" app.config = new_config assert_equal "some_setting_dude", app.config.some_setting, "The configuration's some_setting should have changed." - assert_equal "root_of_application", app.config.root, "The root should have changed to the new config's root." + assert_equal "root_of_application", app.config.root.to_s, "The root should have changed to the new config's root." assert_equal new_config, app.config, "The application's config should have changed to the new config." end end diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 039987ac8c..a1e237fa7b 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 @@ -52,27 +53,73 @@ module ApplicationTests test "db:create and db:drop respect environment setting" do app_file "config/database.yml", <<-YAML + <% 1 %> 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) + db_create_and_drop("db/development.sqlite3", environment_loaded: false) + end + + test "db:create and db:drop don't raise errors when loading YAML with multiline ERB" do + app_file "config/database.yml", <<-YAML + development: + database: <%= + Rails.application.config.database + %> + adapter: sqlite3 + YAML + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.database = "db/development.sqlite3" 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 + + test "db:create and db:drop don't raise errors when loading YAML containing conditional statements in ERB" do + app_file "config/database.yml", <<-YAML + development: + <% if Rails.application.config.database %> + database: <%= Rails.application.config.database %> + <% else %> + database: db/default.sqlite3 + <% end %> + adapter: sqlite3 + YAML + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.database = "db/development.sqlite3" + end + RUBY + + db_create_and_drop("db/development.sqlite3", environment_loaded: false) + end + + test "db:create and db:drop don't raise errors when loading YAML containing multiple ERB statements on the same line" do + app_file "config/database.yml", <<-YAML + development: + database: <% if Rails.application.config.database %><%= Rails.application.config.database %><% else %>db/default.sqlite3<% end %> + adapter: sqlite3 + YAML + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.database = "db/development.sqlite3" + end + RUBY + + db_create_and_drop("db/development.sqlite3", environment_loaded: false) end def with_database_existing @@ -93,6 +140,8 @@ module ApplicationTests def with_bad_permissions Dir.chdir(app_path) do + skip "Can't avoid permissions as root" if Process.uid.zero? + set_database_url FileUtils.chmod("-w", "db") yield @@ -137,6 +186,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" @@ -177,9 +279,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 @@ -199,8 +302,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 @@ -321,13 +425,12 @@ module ApplicationTests end test "db:setup loads schema and seeds database" do - begin - @old_rails_env = ENV["RAILS_ENV"] - @old_rack_env = ENV["RACK_ENV"] - ENV.delete "RAILS_ENV" - ENV.delete "RACK_ENV" + @old_rails_env = ENV["RAILS_ENV"] + @old_rack_env = ENV["RACK_ENV"] + ENV.delete "RAILS_ENV" + ENV.delete "RACK_ENV" - app_file "db/schema.rb", <<-RUBY + app_file "db/schema.rb", <<-RUBY ActiveRecord::Schema.define(version: "1") do create_table :users do |t| t.string :name @@ -335,16 +438,15 @@ module ApplicationTests end RUBY - app_file "db/seeds.rb", <<-RUBY - puts ActiveRecord::Base.connection_config[:database] - RUBY + app_file "db/seeds.rb", <<-RUBY + puts ActiveRecord::Base.connection_config[:database] + RUBY - database_path = rails("db:setup") - assert_equal "development.sqlite3", File.basename(database_path.strip) - ensure - ENV["RAILS_ENV"] = @old_rails_env - ENV["RACK_ENV"] = @old_rack_env - end + database_path = rails("db:setup") + assert_equal "development.sqlite3", File.basename(database_path.strip) + ensure + ENV["RAILS_ENV"] = @old_rails_env + ENV["RACK_ENV"] = @old_rack_env end test "db:setup sets ar_internal_metadata" do @@ -385,6 +487,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 52f4a5fa3c..d676e7486e 100644 --- a/railties/test/application/rake/multi_dbs_test.rb +++ b/railties/test/application/rake/multi_dbs_test.rb @@ -113,7 +113,7 @@ module ApplicationTests end end - def db_migrate_namespaced(namespace, expected_database) + def db_migrate_namespaced(namespace) Dir.chdir(app_path) do generate_models_for_animals output = rails("db:migrate:#{namespace}") @@ -125,7 +125,7 @@ module ApplicationTests end end - def db_migrate_status_namespaced(namespace, expected_database) + def db_migrate_status_namespaced(namespace) Dir.chdir(app_path) do generate_models_for_animals output = rails("db:migrate:status:#{namespace}") @@ -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 @@ -199,7 +200,7 @@ module ApplicationTests test "db:migrate:namespace works" do require "#{app_path}/config/environment" ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config| - db_migrate_namespaced db_config.spec_name, db_config.config["database"] + db_migrate_namespaced db_config.spec_name end end @@ -211,8 +212,8 @@ module ApplicationTests test "db:migrate:status:namespace works" do require "#{app_path}/config/environment" ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config| - db_migrate_namespaced db_config.spec_name, db_config.config["database"] - db_migrate_status_namespaced db_config.spec_name, db_config.config["database"] + db_migrate_namespaced db_config.spec_name + db_migrate_status_namespaced db_config.spec_name end end diff --git a/railties/test/application/rake/routes_test.rb b/railties/test/application/rake/routes_test.rb index 2c23ff4679..9879d1f047 100644 --- a/railties/test/application/rake/routes_test.rb +++ b/railties/test/application/rake/routes_test.rb @@ -6,6 +6,7 @@ module ApplicationTests module RakeTests class RakeRoutesTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation + setup :build_app teardown :teardown_app @@ -17,13 +18,28 @@ module ApplicationTests RUBY assert_equal <<~MESSAGE, run_rake_routes - Prefix Verb URI Pattern Controller#Action - cart GET /cart(.:format) cart#show - rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show -rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show - rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show -update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update - rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create + Prefix Verb URI Pattern Controller#Action + cart GET /cart(.:format) cart#show + rails_amazon_inbound_emails POST /rails/action_mailbox/amazon/inbound_emails(.:format) action_mailbox/ingresses/amazon/inbound_emails#create + rails_mandrill_inbound_emails POST /rails/action_mailbox/mandrill/inbound_emails(.:format) action_mailbox/ingresses/mandrill/inbound_emails#create + rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create + rails_relay_inbound_emails POST /rails/action_mailbox/relay/inbound_emails(.:format) action_mailbox/ingresses/relay/inbound_emails#create + rails_sendgrid_inbound_emails POST /rails/action_mailbox/sendgrid/inbound_emails(.:format) action_mailbox/ingresses/sendgrid/inbound_emails#create + rails_mailgun_inbound_emails POST /rails/action_mailbox/mailgun/inbound_emails/mime(.:format) action_mailbox/ingresses/mailgun/inbound_emails#create + rails_conductor_inbound_emails GET /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#index + POST /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#create + new_rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/new(.:format) rails/conductor/action_mailbox/inbound_emails#new + edit_rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/:id/edit(.:format) rails/conductor/action_mailbox/inbound_emails#edit + rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#show + PATCH /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#update + PUT /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#update + DELETE /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#destroy + rails_conductor_inbound_email_reroute POST /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format) rails/conductor/action_mailbox/reroutes#create + rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show + rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show + rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show + update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update + rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create MESSAGE end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index c38b91ef03..fe56e3d076 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -145,8 +145,8 @@ module ApplicationTests # loading a specific fixture rails "db:fixtures:load", "FIXTURES=products" - assert_equal 2, ::AppTemplate::Application::Product.count - assert_equal 0, ::AppTemplate::Application::User.count + assert_equal 2, Product.count + assert_equal 0, User.count end def test_loading_only_yml_fixtures @@ -160,7 +160,10 @@ module ApplicationTests def test_scaffold_tests_pass_by_default rails "generate", "scaffold", "user", "username:string", "password:string" - with_rails_env("test") { rails("db:migrate") } + with_rails_env("test") do + rails("db:migrate") + rails("webpacker:compile") + end output = rails("test") assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output) diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb index 8f5f48c281..dfb9540093 100644 --- a/railties/test/application/runner_test.rb +++ b/railties/test/application/runner_test.rb @@ -105,6 +105,14 @@ module ApplicationTests assert_match "development", rails("runner", "puts Rails.env") end + def test_environment_option + assert_match "production", rails("runner", "-e", "production", "puts Rails.env") + end + + def test_environment_option_is_properly_expanded + assert_match "production", rails("runner", "-e", "prod", "puts Rails.env") + end + def test_runner_detects_syntax_errors output = rails("runner", "puts 'hello world", allow_failure: true) assert_not_predicate $?, :success? diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb index ab9e910aed..5fe1b4e6e7 100644 --- a/railties/test/application/server_test.rb +++ b/railties/test/application/server_test.rb @@ -18,20 +18,6 @@ module ApplicationTests teardown_app end - test "deprecate support of older `config.ru`" do - remove_file "config.ru" - app_file "config.ru", <<-RUBY - require_relative 'config/environment' - run AppTemplate::Application - RUBY - - server = Rails::Server.new(config: "#{app_path}/config.ru") - server.app - - log = File.read(Rails.application.config.paths["log"].first) - assert_match(/DEPRECATION WARNING: Using `Rails::Application` subclass to start the server is deprecated/, log) - end - test "restart rails server with custom pid file path" do skip "PTY unavailable" unless available_pty? @@ -43,14 +29,14 @@ module ApplicationTests primary, replica = PTY.open pid = nil - begin - pid = Process.spawn("#{app_path}/bin/rails server -P tmp/dummy.pid", in: replica, out: replica, err: replica) + Bundler.with_original_env do + pid = Process.spawn("bin/rails server -b localhost -P tmp/dummy.pid", chdir: app_path, in: replica, out: replica, err: replica) assert_output("Listening", primary) rails("restart") assert_output("Restarting", primary) - assert_output("Inherited", primary) + assert_output("tcp://localhost:3000", primary) ensure kill(pid) if pid end diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index 5c34b205c9..0bdd2b314d 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -16,7 +16,7 @@ module ApplicationTests teardown_app end - def test_run_via_backwardscompatibility + def test_run_via_backwards_compatibility require "minitest/rails_plugin" assert_nothing_raised do @@ -98,6 +98,17 @@ module ApplicationTests end end + def test_run_channels + create_test_file :channels, "foo_channel" + create_test_file :channels, "bar_channel" + + rails("test:channels").tap do |output| + assert_match "FooChannelTest", output + assert_match "BarChannelTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + def test_run_controllers create_test_file :controllers, "foo_controller" create_test_file :controllers, "bar_controller" @@ -131,6 +142,18 @@ module ApplicationTests end end + def test_run_mailboxes + create_test_file :mailboxes, "foo_mailbox" + create_test_file :mailboxes, "bar_mailbox" + create_test_file :models, "foo" + + rails("test:mailboxes").tap do |output| + assert_match "FooMailboxTest", output + assert_match "BarMailboxTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + def test_run_functionals create_test_file :mailers, "foo_mailer" create_test_file :controllers, "bar_controller" @@ -155,11 +178,11 @@ module ApplicationTests end def test_run_all_suites - suites = [:models, :helpers, :unit, :controllers, :mailers, :functional, :integration, :jobs] + suites = [:models, :helpers, :unit, :channels, :controllers, :mailers, :functional, :integration, :jobs, :mailboxes] suites.each { |suite| create_test_file suite, "foo_#{suite}" } run_test_command("") .tap do |output| suites.each { |suite| assert_match "Foo#{suite.to_s.camelize}Test", output } - assert_match "8 runs, 8 assertions, 0 failures", output + assert_match "10 runs, 10 assertions, 0 failures", output end end @@ -523,6 +546,8 @@ module ApplicationTests end def test_run_in_parallel_with_processes + exercise_parallelization_regardless_of_machine_core_count(with: :processes) + file_name = create_parallel_processes_test_file app_file "db/schema.rb", <<-RUBY @@ -540,11 +565,7 @@ module ApplicationTests end def test_run_in_parallel_with_threads - app_path("/test/test_helper.rb") do |file_name| - file = File.read(file_name) - file.sub!(/parallelize\(([^\)]*)\)/, "parallelize(\\1, with: :threads)") - File.write(file_name, file) - end + exercise_parallelization_regardless_of_machine_core_count(with: :threads) file_name = create_parallel_threads_test_file @@ -562,6 +583,49 @@ module ApplicationTests assert_no_match "create_table(:users)", output end + def test_run_in_parallel_with_unmarshable_exception + exercise_parallelization_regardless_of_machine_core_count(with: :processes) + + file = app_file "test/fail_test.rb", <<-RUBY + require "test_helper" + class FailTest < ActiveSupport::TestCase + class BadError < StandardError + def initialize + super + @proc = ->{ } + end + end + + test "fail" do + raise BadError + assert true + end + end + RUBY + + output = run_test_command(file) + + assert_match "DRb::DRbRemoteError: FailTest::BadError", output + assert_match "1 runs, 0 assertions, 0 failures, 1 errors", output + end + + def test_run_in_parallel_with_unknown_object + exercise_parallelization_regardless_of_machine_core_count(with: :processes) + + create_scaffold + + app_file "config/environments/test.rb", <<-RUBY + Rails.application.configure do + config.action_controller.allow_forgery_protection = true + config.action_dispatch.show_exceptions = false + end + RUBY + + output = run_test_command("-n test_should_create_user") + + assert_match "ActionController::InvalidAuthenticityToken", output + end + def test_raise_error_when_specified_file_does_not_exist error = capture(:stderr) { run_test_command("test/not_exists.rb", stderr: true) } assert_match(%r{cannot load such file.+test/not_exists\.rb}, error) @@ -678,6 +742,7 @@ module ApplicationTests def test_reset_sessions_before_rollback_on_system_tests app_file "test/system/reset_session_before_rollback_test.rb", <<-RUBY require "application_system_test_case" + require "selenium/webdriver" class ResetSessionBeforeRollbackTest < ApplicationSystemTestCase def teardown_fixtures @@ -703,6 +768,32 @@ module ApplicationTests end end + def test_reset_sessions_on_failed_system_test_screenshot + app_file "test/system/reset_sessions_on_failed_system_test_screenshot_test.rb", <<~RUBY + require "application_system_test_case" + require "selenium/webdriver" + + class ResetSessionsOnFailedSystemTestScreenshotTest < ApplicationSystemTestCase + ActionDispatch::SystemTestCase.class_eval do + def take_failed_screenshot + raise Capybara::CapybaraError + end + end + + Capybara.instance_eval do + def reset_sessions! + puts "Capybara.reset_sessions! called" + end + end + + test "dummy" do + end + end + RUBY + output = run_test_command("test/system/reset_sessions_on_failed_system_test_screenshot_test.rb") + assert_match "Capybara.reset_sessions! called", output + end + def test_system_tests_are_not_run_with_the_default_test_command app_file "test/system/dummy_test.rb", <<-RUBY require "application_system_test_case" @@ -737,6 +828,7 @@ module ApplicationTests def test_system_tests_are_run_through_rake_test_when_given_in_TEST app_file "test/system/dummy_test.rb", <<-RUBY require "application_system_test_case" + require "selenium/webdriver" class DummyTest < ApplicationSystemTestCase test "something" do @@ -903,6 +995,14 @@ module ApplicationTests RUBY end + def exercise_parallelization_regardless_of_machine_core_count(with:) + app_path("test/test_helper.rb") do |file_name| + file = File.read(file_name) + file.sub!(/parallelize\(([^\)]*)\)/, "parallelize(workers: 2, with: :#{with})") + File.write(file_name, file) + end + end + def create_env_test app_file "test/unit/env_test.rb", <<-RUBY require 'test_helper' 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/url_generation_test.rb b/railties/test/application/url_generation_test.rb index f22b9fda3d..dc737089f6 100644 --- a/railties/test/application/url_generation_test.rb +++ b/railties/test/application/url_generation_test.rb @@ -19,6 +19,8 @@ module ApplicationTests config.session_store :cookie_store, key: "_myapp_session" config.active_support.deprecation = :log config.eager_load = false + config.hosts << proc { true } + config.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" end Rails.application.initialize! diff --git a/railties/test/application/zeitwerk_integration_test.rb b/railties/test/application/zeitwerk_integration_test.rb new file mode 100644 index 0000000000..c82b37d07d --- /dev/null +++ b/railties/test/application/zeitwerk_integration_test.rb @@ -0,0 +1,262 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "active_support/dependencies/zeitwerk_integration" + +class ZeitwerkIntegrationTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def boot(env = "development") + app(env) + end + + def teardown + teardown_app + end + + def deps + ActiveSupport::Dependencies + end + + def decorated? + deps.singleton_class < deps::ZeitwerkIntegration::Decorations + end + + test "ActiveSupport::Dependencies is decorated by default" do + boot + + assert decorated? + 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 + add_to_config "config.autoloader = :classic" + boot + + assert_not decorated? + 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 + app_file "app/models/admin/user.rb", "class Admin::User; end" + boot + + assert_same Admin::User, deps.constantize("Admin::User") + end + + test "constantize raises if the constant is unknown" do + boot + + assert_raises(NameError) { deps.constantize("Admin") } + end + + test "safe_constantize returns the value stored in the constant" do + app_file "app/models/admin/user.rb", "class Admin::User; end" + boot + + assert_same Admin::User, deps.safe_constantize("Admin::User") + end + + test "safe_constantize returns nil for unknown constants" do + boot + + assert_nil deps.safe_constantize("Admin") + end + + test "autoloaded_constants returns autoloaded constant paths" do + app_file "app/models/admin/user.rb", "class Admin::User; end" + app_file "app/models/post.rb", "class Post; end" + boot + + assert Admin::User + assert_equal ["Admin", "Admin::User"], deps.autoloaded_constants + end + + test "autoloaded? says if a constant has been autoloaded" do + app_file "app/models/user.rb", "class User; end" + app_file "app/models/post.rb", "class Post; end" + boot + + assert Post + assert deps.autoloaded?("Post") + assert deps.autoloaded?(Post) + assert_not deps.autoloaded?("User") + end + + test "eager loading loads the application code" do + $zeitwerk_integration_test_user = false + $zeitwerk_integration_test_post = false + + app_file "app/models/user.rb", "class User; end; $zeitwerk_integration_test_user = true" + app_file "app/models/post.rb", "class Post; end; $zeitwerk_integration_test_post = true" + boot("production") + + assert $zeitwerk_integration_test_user + assert $zeitwerk_integration_test_post + end + + test "eager loading loads anything managed by Zeitwerk" do + $zeitwerk_integration_test_user = false + app_file "app/models/user.rb", "class User; end; $zeitwerk_integration_test_user = true" + + $zeitwerk_integration_test_extras = false + app_dir "extras" + app_file "extras/webhook_hacks.rb", "WebhookHacks = 1; $zeitwerk_integration_test_extras = true" + + require "zeitwerk" + autoloader = Zeitwerk::Loader.new + autoloader.push_dir("#{app_path}/extras") + autoloader.setup + + boot("production") + + assert $zeitwerk_integration_test_user + assert $zeitwerk_integration_test_extras + end + + test "autoload_paths are set as root dirs of main, and in the same order" do + boot + + existing_autoload_paths = deps.autoload_paths.select { |dir| File.directory?(dir) } + assert_equal existing_autoload_paths, Rails.autoloaders.main.dirs + end + + test "autoload_once_paths go to the once autoloader, and in the same order" do + extras = %w(e1 e2 e3) + extras.each do |extra| + app_dir extra + add_to_config %(config.autoload_once_paths << "\#{Rails.root}/#{extra}") + end + + boot + + extras = extras.map { |extra| "#{app_path}/#{extra}" } + extras.each do |extra| + assert_not_includes Rails.autoloaders.main.dirs, extra + end + assert_equal extras, Rails.autoloaders.once.dirs + end + + test "clear reloads the main autoloader, and does not reload the once one" do + boot + + $zeitwerk_integration_reload_test = [] + + main_autoloader = Rails.autoloaders.main + def main_autoloader.reload + $zeitwerk_integration_reload_test << :main_autoloader + super + end + + once_autoloader = Rails.autoloaders.once + def once_autoloader.reload + $zeitwerk_integration_reload_test << :once_autoloader + super + end + + ActiveSupport::Dependencies.clear + + 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/command/base_test.rb b/railties/test/command/base_test.rb index a49ae8aae7..9132c8b4af 100644 --- a/railties/test/command/base_test.rb +++ b/railties/test/command/base_test.rb @@ -4,10 +4,12 @@ require "abstract_unit" require "rails/command" require "rails/commands/generate/generate_command" require "rails/commands/secrets/secrets_command" +require "rails/commands/db/system/change/change_command" class Rails::Command::BaseTest < ActiveSupport::TestCase test "printing commands" do assert_equal %w(generate), Rails::Command::GenerateCommand.printing_commands assert_equal %w(secrets:setup secrets:edit secrets:show), Rails::Command::SecretsCommand.printing_commands + assert_equal %w(db:system:change), Rails::Command::Db::System::ChangeCommand.printing_commands end end diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb index 0b2fe204f8..f6df2b694a 100644 --- a/railties/test/commands/console_test.rb +++ b/railties/test/commands/console_test.rb @@ -94,28 +94,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase assert_match(/\sspecial-production\s/, output) end - def test_rails_env_is_production_when_first_argument_is_p - assert_deprecated do - start ["p"] - assert_match(/\sproduction\s/, output) - end - end - - def test_rails_env_is_test_when_first_argument_is_t - assert_deprecated do - start ["t"] - assert_match(/\stest\s/, output) - end - end - - def test_rails_env_is_development_when_argument_is_d - assert_deprecated do - start ["d"] - assert_match(/\sdevelopment\s/, output) - end - end - - def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present + def test_rails_env_is_dev_when_environment_option_is_dev_and_dev_env_is_present Rails::Command::ConsoleCommand.class_eval do alias_method :old_environments, :available_environments @@ -124,9 +103,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase end end - assert_deprecated do - assert_match("dev", parse_arguments(["dev"])[:environment]) - end + assert_match("dev", parse_arguments(["-e", "dev"])[:environment]) ensure Rails::Command::ConsoleCommand.class_eval do undef_method :available_environments @@ -152,7 +129,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase def build_app(console) mocked_console = Class.new do attr_accessor :sandbox - attr_reader :console + attr_reader :console, :disable_sandbox def initialize(console) @console = console diff --git a/railties/test/commands/credentials_test.rb b/railties/test/commands/credentials_test.rb index 7842b0db61..0ee36081c0 100644 --- a/railties/test/commands/credentials_test.rb +++ b/railties/test/commands/credentials_test.rb @@ -63,11 +63,36 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase end end + test "edit command properly expands environment option" do + assert_match(/access_key_id: 123/, run_edit_command(environment: "prod")) + Dir.chdir(app_path) do + assert File.exist?("config/credentials/production.key") + assert File.exist?("config/credentials/production.yml.enc") + end + end + + test "edit command does not raise when an initializer tries to access non-existent credentials" do + app_file "config/initializers/raise_when_loaded.rb", <<-RUBY + Rails.application.credentials.missing_key! + RUBY + + assert_match(/access_key_id: 123/, run_edit_command(environment: "qa")) + end + + test "edit command generates template file when the file does not exist" do + FileUtils.rm("#{app_path}/config/credentials.yml.enc") + run_edit_command + + output = run_show_command + assert_match(/access_key_id: 123/, output) + assert_match(/secret_key_base/, output) + end + test "show credentials" do assert_match(/access_key_id: 123/, run_show_command) end - test "show command raise error when require_master_key is specified and key does not exist" do + test "show command raises error when require_master_key is specified and key does not exist" do remove_file "config/master.key" add_to_config "config.require_master_key = true" @@ -87,6 +112,14 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase assert_match(/access_key_id: 123/, run_show_command(environment: "production")) end + test "show command properly expands environment option" do + run_edit_command(environment: "production") + + output = run_show_command(environment: "prod") + assert_match(/access_key_id: 123/, output) + assert_no_match(/secret_key_base/, output) + end + private def run_edit_command(editor: "cat", environment: nil, **options) switch_env("EDITOR", editor) do diff --git a/railties/test/commands/db_system_change_test.rb b/railties/test/commands/db_system_change_test.rb new file mode 100644 index 0000000000..2ff45a7878 --- /dev/null +++ b/railties/test/commands/db_system_change_test.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "rails/command" +require "rails/commands/db/system/change/change_command" + +class Rails::Command::Db::System::ChangeCommandTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + setup { build_app } + + teardown { teardown_app } + + test "change to existing database" do + change_database(to: "sqlite3") + + output = change_database(to: "sqlite3") + + assert_match "identical config/database.yml", output + assert_match "gsub Gemfile", output + end + + test "change to invalid database" do + output = change_database(to: "invalid-db") + + assert_match <<~MSG.squish, output + Invalid value for --to option. + Supported preconfigurations are: + mysql, postgresql, sqlite3, oracle, frontbase, + ibm_db, sqlserver, jdbcmysql, jdbcsqlite3, + jdbcpostgresql, jdbc. + MSG + end + + test "change to postgresql" do + output = change_database(to: "postgresql") + + assert_match "force config/database.yml", output + assert_match "gsub Gemfile", output + end + + test "change to mysql" do + output = change_database(to: "mysql") + + assert_match "force config/database.yml", output + assert_match "gsub Gemfile", output + end + + test "change to sqlite3" do + change_database(to: "postgresql") + output = change_database(to: "sqlite3") + + assert_match "force config/database.yml", output + assert_match "gsub Gemfile", output + end + + private + def change_database(to:, **options) + args = ["--to", to] + rails "db:system:change", args, **options + end +end diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb index 5e3eca6585..65f6916acb 100644 --- a/railties/test/commands/dbconsole_test.rb +++ b/railties/test/commands/dbconsole_test.rb @@ -99,28 +99,12 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase ENV["RACK_ENV"] = nil end - def test_rails_env_is_development_when_argument_is_dev - assert_deprecated do - stub_available_environments([ "development", "test" ]) do - assert_match("development", parse_arguments([ "dev" ])[:environment]) - end - end - end - def test_rails_env_is_development_when_environment_option_is_dev stub_available_environments([ "development", "test" ]) do assert_match("development", parse_arguments([ "-e", "dev" ])[:environment]) end end - def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present - assert_deprecated do - stub_available_environments([ "dev" ]) do - assert_match("dev", parse_arguments([ "dev" ])[:environment]) - end - end - end - def test_mysql start(adapter: "mysql2", database: "db") assert_not aborted @@ -128,9 +112,9 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase end def test_mysql_full - start(adapter: "mysql2", database: "db", host: "locahost", port: 1234, socket: "socket", username: "user", password: "qwerty", encoding: "UTF-8") + start(adapter: "mysql2", database: "db", host: "localhost", port: 1234, socket: "socket", username: "user", password: "qwerty", encoding: "UTF-8") assert_not aborted - assert_equal [%w[mysql mysql5], "--host=locahost", "--port=1234", "--socket=socket", "--user=user", "--default-character-set=UTF-8", "-p", "db"], dbconsole.find_cmd_and_exec_args + assert_equal [%w[mysql mysql5], "--host=localhost", "--port=1234", "--socket=socket", "--user=user", "--default-character-set=UTF-8", "-p", "db"], dbconsole.find_cmd_and_exec_args end def test_mysql_include_password @@ -265,14 +249,14 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase stdout = capture(:stdout) do Rails::Command.invoke(:dbconsole, ["-h"]) end - assert_match(/rails dbconsole \[environment\]/, stdout) + assert_match(/rails dbconsole \[options\]/, stdout) end def test_print_help_long stdout = capture(:stdout) do Rails::Command.invoke(:dbconsole, ["--help"]) end - assert_match(/rails dbconsole \[environment\]/, stdout) + assert_match(/rails dbconsole \[options\]/, stdout) end attr_reader :aborted, :output @@ -308,11 +292,9 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase def capture_abort @aborted = false @output = capture(:stderr) do - begin - yield - rescue SystemExit - @aborted = true - end + yield + rescue SystemExit + @aborted = true end end diff --git a/railties/test/commands/initializers_test.rb b/railties/test/commands/initializers_test.rb index bdfbb3021c..793365ef3d 100644 --- a/railties/test/commands/initializers_test.rb +++ b/railties/test/commands/initializers_test.rb @@ -25,8 +25,24 @@ class Rails::Command::InitializersTest < ActiveSupport::TestCase assert final_output.include?("set_added_test_module") end + + test "prints out initializers only specified in environment option" do + add_to_config <<-RUBY + initializer(:set_added_development_module) { } if Rails.env.development? + initializer(:set_added_production_module) { } if Rails.env.production? + RUBY + + output = run_initializers_command.split("\n") + assert_includes output, "AppTemplate::Application.set_added_development_module" + assert_not_includes output, "AppTemplate::Application.set_added_production_module" + + output = run_initializers_command(["-e", "production"]).split("\n") + assert_not_includes output, "AppTemplate::Application.set_added_development_module" + assert_includes output, "AppTemplate::Application.set_added_production_module" + end + private - def run_initializers_command - rails "initializers" + def run_initializers_command(args = []) + rails "initializers", args end end diff --git a/railties/test/commands/routes_test.rb b/railties/test/commands/routes_test.rb index 693e532c5b..b4f927060e 100644 --- a/railties/test/commands/routes_test.rb +++ b/railties/test/commands/routes_test.rb @@ -17,29 +17,28 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase end RUBY - expected_post_output = [" Prefix Verb URI Pattern Controller#Action", - " new_post GET /post/new(.:format) posts#new", - "edit_post GET /post/edit(.:format) posts#edit", - " post GET /post(.:format) posts#show", - " PATCH /post(.:format) posts#update", - " PUT /post(.:format) posts#update", - " DELETE /post(.:format) posts#destroy", - " POST /post(.:format) posts#create\n"].join("\n") - - output = run_routes_command(["-c", "PostController"]) - assert_equal expected_post_output, output - - expected_perm_output = [" Prefix Verb URI Pattern Controller#Action", - " new_user_permission GET /user_permission/new(.:format) user_permissions#new", - "edit_user_permission GET /user_permission/edit(.:format) user_permissions#edit", - " user_permission GET /user_permission(.:format) user_permissions#show", - " PATCH /user_permission(.:format) user_permissions#update", - " PUT /user_permission(.:format) user_permissions#update", - " DELETE /user_permission(.:format) user_permissions#destroy", - " POST /user_permission(.:format) user_permissions#create\n"].join("\n") - - output = run_routes_command(["-c", "UserPermissionController"]) - assert_equal expected_perm_output, output + assert_equal <<~OUTPUT, run_routes_command([ "-c", "PostController" ]) + Prefix Verb URI Pattern Controller#Action + new_post GET /post/new(.:format) posts#new + edit_post GET /post/edit(.:format) posts#edit + post GET /post(.:format) posts#show + PATCH /post(.:format) posts#update + PUT /post(.:format) posts#update + DELETE /post(.:format) posts#destroy + POST /post(.:format) posts#create + rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create + OUTPUT + + assert_equal <<~OUTPUT, run_routes_command([ "-c", "UserPermissionController" ]) + Prefix Verb URI Pattern Controller#Action + new_user_permission GET /user_permission/new(.:format) user_permissions#new + edit_user_permission GET /user_permission/edit(.:format) user_permissions#edit + user_permission GET /user_permission(.:format) user_permissions#show + PATCH /user_permission(.:format) user_permissions#update + PUT /user_permission(.:format) user_permissions#update + DELETE /user_permission(.:format) user_permissions#destroy + POST /user_permission(.:format) user_permissions#create + OUTPUT end test "rails routes with global search key" do @@ -51,25 +50,33 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase end RUBY - output = run_routes_command(["-g", "show"]) - assert_equal <<~MESSAGE, output - Prefix Verb URI Pattern Controller#Action - cart GET /cart(.:format) cart#show - rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show - rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show - rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show + assert_equal <<~MESSAGE, run_routes_command([ "-g", "show" ]) + Prefix Verb URI Pattern Controller#Action + cart GET /cart(.:format) cart#show + rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#show + rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show + rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show + rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show MESSAGE - output = run_routes_command(["-g", "POST"]) - assert_equal <<~MESSAGE, output - Prefix Verb URI Pattern Controller#Action - POST /cart(.:format) cart#create - rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create + assert_equal <<~MESSAGE, run_routes_command([ "-g", "POST" ]) + Prefix Verb URI Pattern Controller#Action + POST /cart(.:format) cart#create + rails_amazon_inbound_emails POST /rails/action_mailbox/amazon/inbound_emails(.:format) action_mailbox/ingresses/amazon/inbound_emails#create + rails_mandrill_inbound_emails POST /rails/action_mailbox/mandrill/inbound_emails(.:format) action_mailbox/ingresses/mandrill/inbound_emails#create + rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create + rails_relay_inbound_emails POST /rails/action_mailbox/relay/inbound_emails(.:format) action_mailbox/ingresses/relay/inbound_emails#create + rails_sendgrid_inbound_emails POST /rails/action_mailbox/sendgrid/inbound_emails(.:format) action_mailbox/ingresses/sendgrid/inbound_emails#create + rails_mailgun_inbound_emails POST /rails/action_mailbox/mailgun/inbound_emails/mime(.:format) action_mailbox/ingresses/mailgun/inbound_emails#create + POST /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#create + rails_conductor_inbound_email_reroute POST /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format) rails/conductor/action_mailbox/reroutes#create + rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create MESSAGE - output = run_routes_command(["-g", "basketballs"]) - assert_equal " Prefix Verb URI Pattern Controller#Action\n" \ - "basketballs GET /basketballs(.:format) basketball#index\n", output + assert_equal <<~MESSAGE, run_routes_command([ "-g", "basketballs" ]) + Prefix Verb URI Pattern Controller#Action + basketballs GET /basketballs(.:format) basketball#index + MESSAGE end test "rails routes with controller search key" do @@ -113,35 +120,42 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase end RUBY - expected_post_output = [" Prefix Verb URI Pattern Controller#Action", - " new_admin_post GET /admin/post/new(.:format) admin/posts#new", - "edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit", - " admin_post GET /admin/post(.:format) admin/posts#show", - " PATCH /admin/post(.:format) admin/posts#update", - " PUT /admin/post(.:format) admin/posts#update", - " DELETE /admin/post(.:format) admin/posts#destroy", - " POST /admin/post(.:format) admin/posts#create\n"].join("\n") - - output = run_routes_command(["-c", "Admin::PostController"]) - assert_equal expected_post_output, output - - output = run_routes_command(["-c", "PostController"]) - assert_equal expected_post_output, output - - expected_perm_output = [" Prefix Verb URI Pattern Controller#Action", - " new_admin_user_permission GET /admin/user_permission/new(.:format) admin/user_permissions#new", - "edit_admin_user_permission GET /admin/user_permission/edit(.:format) admin/user_permissions#edit", - " admin_user_permission GET /admin/user_permission(.:format) admin/user_permissions#show", - " PATCH /admin/user_permission(.:format) admin/user_permissions#update", - " PUT /admin/user_permission(.:format) admin/user_permissions#update", - " DELETE /admin/user_permission(.:format) admin/user_permissions#destroy", - " POST /admin/user_permission(.:format) admin/user_permissions#create\n"].join("\n") - - output = run_routes_command(["-c", "Admin::UserPermissionController"]) - assert_equal expected_perm_output, output - - output = run_routes_command(["-c", "UserPermissionController"]) - assert_equal expected_perm_output, output + assert_equal <<~OUTPUT, run_routes_command([ "-c", "Admin::PostController" ]) + Prefix Verb URI Pattern Controller#Action + new_admin_post GET /admin/post/new(.:format) admin/posts#new + edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit + admin_post GET /admin/post(.:format) admin/posts#show + PATCH /admin/post(.:format) admin/posts#update + PUT /admin/post(.:format) admin/posts#update + DELETE /admin/post(.:format) admin/posts#destroy + POST /admin/post(.:format) admin/posts#create + OUTPUT + + assert_equal <<~OUTPUT, run_routes_command([ "-c", "PostController" ]) + Prefix Verb URI Pattern Controller#Action + new_admin_post GET /admin/post/new(.:format) admin/posts#new + edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit + admin_post GET /admin/post(.:format) admin/posts#show + PATCH /admin/post(.:format) admin/posts#update + PUT /admin/post(.:format) admin/posts#update + DELETE /admin/post(.:format) admin/posts#destroy + POST /admin/post(.:format) admin/posts#create + rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create + OUTPUT + + expected_permission_output = <<~OUTPUT + Prefix Verb URI Pattern Controller#Action + new_admin_user_permission GET /admin/user_permission/new(.:format) admin/user_permissions#new + edit_admin_user_permission GET /admin/user_permission/edit(.:format) admin/user_permissions#edit + admin_user_permission GET /admin/user_permission(.:format) admin/user_permissions#show + PATCH /admin/user_permission(.:format) admin/user_permissions#update + PUT /admin/user_permission(.:format) admin/user_permissions#update + DELETE /admin/user_permission(.:format) admin/user_permissions#destroy + POST /admin/user_permission(.:format) admin/user_permissions#create + OUTPUT + + assert_equal expected_permission_output, run_routes_command([ "-c", "Admin::UserPermissionController" ]) + assert_equal expected_permission_output, run_routes_command([ "-c", "UserPermissionController" ]) end test "rails routes displays message when no routes are defined" do @@ -151,63 +165,151 @@ class Rails::Command::RoutesTest < ActiveSupport::TestCase RUBY assert_equal <<~MESSAGE, run_routes_command - Prefix Verb URI Pattern Controller#Action - rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show - rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show - rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show - update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update - rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create + Prefix Verb URI Pattern Controller#Action + rails_amazon_inbound_emails POST /rails/action_mailbox/amazon/inbound_emails(.:format) action_mailbox/ingresses/amazon/inbound_emails#create + rails_mandrill_inbound_emails POST /rails/action_mailbox/mandrill/inbound_emails(.:format) action_mailbox/ingresses/mandrill/inbound_emails#create + rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create + rails_relay_inbound_emails POST /rails/action_mailbox/relay/inbound_emails(.:format) action_mailbox/ingresses/relay/inbound_emails#create + rails_sendgrid_inbound_emails POST /rails/action_mailbox/sendgrid/inbound_emails(.:format) action_mailbox/ingresses/sendgrid/inbound_emails#create + rails_mailgun_inbound_emails POST /rails/action_mailbox/mailgun/inbound_emails/mime(.:format) action_mailbox/ingresses/mailgun/inbound_emails#create + rails_conductor_inbound_emails GET /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#index + POST /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#create + new_rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/new(.:format) rails/conductor/action_mailbox/inbound_emails#new + edit_rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/:id/edit(.:format) rails/conductor/action_mailbox/inbound_emails#edit + rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#show + PATCH /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#update + PUT /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#update + DELETE /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#destroy + rails_conductor_inbound_email_reroute POST /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format) rails/conductor/action_mailbox/reroutes#create + rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show + rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show + rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show + update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update + rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create MESSAGE end test "rails routes with expanded option" do - begin - previous_console_winsize = IO.console.winsize - IO.console.winsize = [0, 27] + previous_console_winsize = IO.console.winsize + IO.console.winsize = [0, 27] - app_file "config/routes.rb", <<-RUBY - Rails.application.routes.draw do - get '/cart', to: 'cart#show' - end - RUBY - - output = run_routes_command(["--expanded"]) - - assert_equal <<~MESSAGE, output - --[ Route 1 ]-------------- - Prefix | cart - Verb | GET - URI | /cart(.:format) - Controller#Action | cart#show - --[ Route 2 ]-------------- - Prefix | rails_service_blob - Verb | GET - URI | /rails/active_storage/blobs/:signed_id/*filename(.:format) - Controller#Action | active_storage/blobs#show - --[ Route 3 ]-------------- - Prefix | rails_blob_representation - Verb | GET - URI | /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) - Controller#Action | active_storage/representations#show - --[ Route 4 ]-------------- - Prefix | rails_disk_service - Verb | GET - URI | /rails/active_storage/disk/:encoded_key/*filename(.:format) - Controller#Action | active_storage/disk#show - --[ Route 5 ]-------------- - Prefix | update_rails_disk_service - Verb | PUT - URI | /rails/active_storage/disk/:encoded_token(.:format) - Controller#Action | active_storage/disk#update - --[ Route 6 ]-------------- - Prefix | rails_direct_uploads - Verb | POST - URI | /rails/active_storage/direct_uploads(.:format) - Controller#Action | active_storage/direct_uploads#create - MESSAGE - ensure - IO.console.winsize = previous_console_winsize - end + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/cart', to: 'cart#show' + end + RUBY + + # rubocop:disable Layout/TrailingWhitespace + assert_equal <<~MESSAGE, run_routes_command([ "--expanded" ]) + --[ Route 1 ]-------------- + Prefix | cart + Verb | GET + URI | /cart(.:format) + Controller#Action | cart#show + --[ Route 2 ]-------------- + Prefix | rails_amazon_inbound_emails + Verb | POST + URI | /rails/action_mailbox/amazon/inbound_emails(.:format) + Controller#Action | action_mailbox/ingresses/amazon/inbound_emails#create + --[ Route 3 ]-------------- + Prefix | rails_mandrill_inbound_emails + Verb | POST + URI | /rails/action_mailbox/mandrill/inbound_emails(.:format) + Controller#Action | action_mailbox/ingresses/mandrill/inbound_emails#create + --[ Route 4 ]-------------- + Prefix | rails_postmark_inbound_emails + Verb | POST + URI | /rails/action_mailbox/postmark/inbound_emails(.:format) + Controller#Action | action_mailbox/ingresses/postmark/inbound_emails#create + --[ Route 5 ]-------------- + Prefix | rails_relay_inbound_emails + Verb | POST + URI | /rails/action_mailbox/relay/inbound_emails(.:format) + Controller#Action | action_mailbox/ingresses/relay/inbound_emails#create + --[ Route 6 ]-------------- + Prefix | rails_sendgrid_inbound_emails + Verb | POST + URI | /rails/action_mailbox/sendgrid/inbound_emails(.:format) + Controller#Action | action_mailbox/ingresses/sendgrid/inbound_emails#create + --[ Route 7 ]-------------- + Prefix | rails_mailgun_inbound_emails + Verb | POST + URI | /rails/action_mailbox/mailgun/inbound_emails/mime(.:format) + Controller#Action | action_mailbox/ingresses/mailgun/inbound_emails#create + --[ Route 8 ]-------------- + Prefix | rails_conductor_inbound_emails + Verb | GET + URI | /rails/conductor/action_mailbox/inbound_emails(.:format) + Controller#Action | rails/conductor/action_mailbox/inbound_emails#index + --[ Route 9 ]-------------- + Prefix | + Verb | POST + URI | /rails/conductor/action_mailbox/inbound_emails(.:format) + Controller#Action | rails/conductor/action_mailbox/inbound_emails#create + --[ Route 10 ]------------- + Prefix | new_rails_conductor_inbound_email + Verb | GET + URI | /rails/conductor/action_mailbox/inbound_emails/new(.:format) + Controller#Action | rails/conductor/action_mailbox/inbound_emails#new + --[ Route 11 ]------------- + Prefix | edit_rails_conductor_inbound_email + Verb | GET + URI | /rails/conductor/action_mailbox/inbound_emails/:id/edit(.:format) + Controller#Action | rails/conductor/action_mailbox/inbound_emails#edit + --[ Route 12 ]------------- + Prefix | rails_conductor_inbound_email + Verb | GET + URI | /rails/conductor/action_mailbox/inbound_emails/:id(.:format) + Controller#Action | rails/conductor/action_mailbox/inbound_emails#show + --[ Route 13 ]------------- + Prefix | + Verb | PATCH + URI | /rails/conductor/action_mailbox/inbound_emails/:id(.:format) + Controller#Action | rails/conductor/action_mailbox/inbound_emails#update + --[ Route 14 ]------------- + Prefix | + Verb | PUT + URI | /rails/conductor/action_mailbox/inbound_emails/:id(.:format) + Controller#Action | rails/conductor/action_mailbox/inbound_emails#update + --[ Route 15 ]------------- + Prefix | + Verb | DELETE + URI | /rails/conductor/action_mailbox/inbound_emails/:id(.:format) + Controller#Action | rails/conductor/action_mailbox/inbound_emails#destroy + --[ Route 16 ]------------- + Prefix | rails_conductor_inbound_email_reroute + Verb | POST + URI | /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format) + Controller#Action | rails/conductor/action_mailbox/reroutes#create + --[ Route 17 ]------------- + Prefix | rails_service_blob + Verb | GET + URI | /rails/active_storage/blobs/:signed_id/*filename(.:format) + Controller#Action | active_storage/blobs#show + --[ Route 18 ]------------- + Prefix | rails_blob_representation + Verb | GET + URI | /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) + Controller#Action | active_storage/representations#show + --[ Route 19 ]------------- + Prefix | rails_disk_service + Verb | GET + URI | /rails/active_storage/disk/:encoded_key/*filename(.:format) + Controller#Action | active_storage/disk#show + --[ Route 20 ]------------- + Prefix | update_rails_disk_service + Verb | PUT + URI | /rails/active_storage/disk/:encoded_token(.:format) + Controller#Action | active_storage/disk#update + --[ Route 21 ]------------- + Prefix | rails_direct_uploads + Verb | POST + URI | /rails/active_storage/direct_uploads(.:format) + Controller#Action | active_storage/direct_uploads#create + MESSAGE + # rubocop:enable Layout/TrailingWhitespace + ensure + IO.console.winsize = previous_console_winsize end private diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb index e5b1da6ea4..b78370a233 100644 --- a/railties/test/commands/server_test.rb +++ b/railties/test/commands/server_test.rb @@ -22,6 +22,12 @@ class Rails::Command::ServerCommandTest < ActiveSupport::TestCase assert_nil options[:server] end + def test_environment_option_is_properly_expanded + args = ["-e", "prod"] + options = parse_arguments(args) + assert_equal "production", options[:environment] + end + def test_explicit_using_option args = ["-u", "thin"] options = parse_arguments(args) @@ -32,6 +38,12 @@ class Rails::Command::ServerCommandTest < ActiveSupport::TestCase assert_match(/Could not find server "tin". Maybe you meant "thin"?/, run_command("--using", "tin")) end + def test_using_server_mistype_without_suggestion + output = run_command("--using", "t") + assert_match(/Could not find server "t"/, output) + assert_no_match(/Maybe you meant/, output) + end + def test_using_positional_argument_deprecation assert_match(/DEPRECATION WARNING/, run_command("tin")) end @@ -225,10 +237,10 @@ class Rails::Command::ServerCommandTest < ActiveSupport::TestCase end def test_records_user_supplied_options - server_options = parse_arguments(["-p", 3001]) + server_options = parse_arguments(["-p", "3001"]) assert_equal [:Port], server_options[:user_supplied_options] - server_options = parse_arguments(["--port", 3001]) + server_options = parse_arguments(["--port", "3001"]) assert_equal [:Port], server_options[:user_supplied_options] server_options = parse_arguments(["-p3001", "-C", "--binding", "127.0.0.1"]) @@ -279,6 +291,8 @@ class Rails::Command::ServerCommandTest < ActiveSupport::TestCase end def parse_arguments(args = []) - Rails::Command::ServerCommand.new([], args).server_options + command = Rails::Command::ServerCommand.new([], args) + command.send(:extract_environment_option_from_argument) + command.server_options end end diff --git a/railties/test/credentials_test.rb b/railties/test/credentials_test.rb deleted file mode 100644 index 03370e0fc7..0000000000 --- a/railties/test/credentials_test.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require "isolation/abstract_unit" - -class Rails::CredentialsTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation - - setup :build_app - teardown :teardown_app - - test "reads credentials from environment specific path" do - with_credentials do |content, key| - Dir.chdir(app_path) do - Dir.mkdir("config/credentials") - File.write("config/credentials/production.yml.enc", content) - File.write("config/credentials/production.key", key) - end - - app("production") - - assert_equal "revealed", Rails.application.credentials.mystery - end - end - - test "reads credentials from customized path and key" do - with_credentials do |content, key| - Dir.chdir(app_path) do - Dir.mkdir("config/credentials") - File.write("config/credentials/staging.yml.enc", content) - File.write("config/credentials/staging.key", key) - end - - add_to_env_config("production", "config.credentials.content_path = config.root.join('config/credentials/staging.yml.enc')") - add_to_env_config("production", "config.credentials.key_path = config.root.join('config/credentials/staging.key')") - app("production") - - assert_equal "revealed", Rails.application.credentials.mystery - end - end - - private - def with_credentials - key = "2117e775dc2024d4f49ddf3aeb585919" - # secret_key_base: secret - # mystery: revealed - content = "vgvKu4MBepIgZ5VHQMMPwnQNsLlWD9LKmJHu3UA/8yj6x+3fNhz3DwL9brX7UA==--qLdxHP6e34xeTAiI--nrcAsleXuo9NqiEuhntAhw==" - yield(content, key) - end -end diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 6d53230eab..44d4e92256 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -303,12 +303,30 @@ class ActionsTest < Rails::Generators::TestCase end def test_generate_should_run_script_generate_with_argument_and_options - assert_called_with(generator, :run_ruby_script, ["bin/rails generate model MyModel", verbose: false]) do - action :generate, "model", "MyModel" + run_generator + action :generate, "model", "MyModel" + assert_file "app/models/my_model.rb", /MyModel/ + end + + def test_generate_aborts_when_subprocess_fails_if_requested + run_generator + content = capture(:stderr) do + assert_raises SystemExit do + action :generate, "model", "MyModel:ADsad", abort_on_failure: true + action :generate, "model", "MyModel" + end end + assert_match(/wrong constant name MyModel:aDsad/, content) + assert_no_file "app/models/my_model.rb" end - def test_rails_should_run_rake_command_with_default_env + def test_generate_should_run_command_without_env + assert_called_with(generator, :run, ["rails generate model MyModel name:string", verbose: false]) do + action :generate, "model", "MyModel", "name:string" + end + end + + def test_rake_should_run_rake_command_with_default_env assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=development", verbose: false]) do with_rails_env nil do action :rake, "log:clear" @@ -316,13 +334,13 @@ class ActionsTest < Rails::Generators::TestCase end end - def test_rails_with_env_option_should_run_rake_command_in_env + def test_rake_with_env_option_should_run_rake_command_in_env assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=production", verbose: false]) do action :rake, "log:clear", env: "production" end end - test "rails command with RAILS_ENV variable should run rake command in env" do + test "rake with RAILS_ENV variable should run rake command in env" do assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=production", verbose: false]) do with_rails_env "production" do action :rake, "log:clear" @@ -338,7 +356,7 @@ class ActionsTest < Rails::Generators::TestCase end end - test "rails command with sudo option should run rake command with sudo" do + test "rake with sudo option should run rake command with sudo" do assert_called_with(generator, :run, ["sudo rake log:clear RAILS_ENV=development", verbose: false]) do with_rails_env nil do action :rake, "log:clear", sudo: true @@ -400,15 +418,6 @@ class ActionsTest < Rails::Generators::TestCase end end - def test_capify_should_run_the_capify_command - content = capture(:stderr) do - assert_called_with(generator, :run, ["capify .", verbose: false]) do - action :capify! - end - end - assert_match(/DEPRECATION WARNING: `capify!` is deprecated/, content) - end - def test_route_should_add_data_to_the_routes_block_in_config_routes run_generator route_command = "route '/login', controller: 'sessions', action: 'new'" diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 4f2894e71e..4b9878187b 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -14,9 +14,9 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase super Kernel.silence_warnings do - Thor::Base.shell.send(:attr_accessor, :always_force) + Thor::Base.shell.attr_accessor :always_force @shell = Thor::Base.shell.new - @shell.send(:always_force=, true) + @shell.always_force = true end end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 32d00f3157..d5a3599f67 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -81,6 +81,7 @@ DEFAULT_APP_FILES = %w( test/test_helper.rb test/fixtures test/fixtures/files + test/channels/application_cable/connection_test.rb test/controllers test/models test/helpers @@ -198,7 +199,7 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_new_application_use_json_serialzier + def test_new_application_use_json_serializer run_generator assert_file("config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/) @@ -363,6 +364,8 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "#{app_root}/config/environments/production.rb" do |content| assert_no_match(/config\.action_cable/, content) end + + assert_no_file "#{app_root}/test/channels/application_cable/connection_test.rb" end def test_app_update_does_not_generate_bootsnap_contents_when_skip_bootsnap_is_given @@ -435,6 +438,36 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_file "#{app_root}/config/storage.yml" end + def test_generator_skips_action_mailbox_when_skip_action_mailbox_is_given + run_generator [destination_root, "--skip-action-mailbox"] + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_mailbox\/engine["']/ + end + + def test_generator_skips_action_mailbox_when_skip_active_record_is_given + run_generator [destination_root, "--skip-active-record"] + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_mailbox\/engine["']/ + end + + def test_generator_skips_action_mailbox_when_skip_active_storage_is_given + run_generator [destination_root, "--skip-active-storage"] + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_mailbox\/engine["']/ + end + + def test_generator_skips_action_text_when_skip_action_text_is_given + run_generator [destination_root, "--skip-action-text"] + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_text\/engine["']/ + end + + def test_generator_skips_action_text_when_skip_active_record_is_given + run_generator [destination_root, "--skip-active-record"] + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_text\/engine["']/ + end + + def test_generator_skips_action_text_when_skip_active_storage_is_given + run_generator [destination_root, "--skip-active-storage"] + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_text\/engine["']/ + end + def test_app_update_does_not_change_config_target_version run_generator @@ -493,7 +526,7 @@ class AppGeneratorTest < Rails::Generators::TestCase if defined?(JRUBY_VERSION) assert_gem "activerecord-jdbcsqlite3-adapter" else - assert_gem "sqlite3" + assert_gem "sqlite3", "'~> 1.3', '>= 1.3.6'" end end @@ -584,7 +617,7 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_gem "capybara" assert_no_gem "selenium-webdriver" - assert_no_gem "chromedriver-helper" + assert_no_gem "webdrivers" assert_no_directory("test") end @@ -593,7 +626,7 @@ class AppGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--skip-system-test"] assert_no_gem "capybara" assert_no_gem "selenium-webdriver" - assert_no_gem "chromedriver-helper" + assert_no_gem "webdrivers" assert_directory("test") @@ -743,6 +776,24 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_bundler_command_called("install") end + def test_generation_use_original_bundle_environment + generator([destination_root], skip_webpack_install: true) + + mock_original_env = -> do + { "BUNDLE_RUBYONRAILS__ORG" => "user:pass" } + end + + ensure_environment_is_set = -> *_args do + assert_equal "user:pass", ENV["BUNDLE_RUBYONRAILS__ORG"] + end + + Bundler.stub :original_env, mock_original_env do + generator.stub :exec_bundle_command, ensure_environment_is_set do + quietly { generator.invoke_all } + end + end + end + def test_dev_option generator([destination_root], dev: true, skip_webpack_install: true) @@ -1059,7 +1110,7 @@ class AppGeneratorTest < Rails::Generators::TestCase end def assert_bundler_command_called(target_command) - command_check = -> command do + command_check = -> (command, env = {}) do @command_called ||= 0 case command diff --git a/railties/test/generators/channel_generator_test.rb b/railties/test/generators/channel_generator_test.rb index 1cb8465539..1a25422c3c 100644 --- a/railties/test/generators/channel_generator_test.rb +++ b/railties/test/generators/channel_generator_test.rb @@ -67,12 +67,23 @@ class ChannelGeneratorTest < Rails::Generators::TestCase assert_file "app/javascript/channels/consumer.js" end + def test_invokes_default_test_framework + run_generator %w(chat -t=test_unit) + + assert_file "test/channels/chat_channel_test.rb" do |test| + assert_match(/class ChatChannelTest < ActionCable::Channel::TestCase/, test) + assert_match(/# test "subscribes" do/, test) + assert_match(/# assert subscription.confirmed\?/, test) + end + end + def test_channel_on_revoke run_generator ["chat"] run_generator ["chat"], behavior: :revoke assert_no_file "app/channels/chat_channel.rb" assert_no_file "app/javascript/channels/chat_channel.js" + assert_no_file "test/channels/chat_channel_test.rb" assert_file "app/channels/application_cable/channel.rb" assert_file "app/channels/application_cable/connection.rb" @@ -88,5 +99,8 @@ class ChannelGeneratorTest < Rails::Generators::TestCase assert_no_file "app/javascript/channels/chat_channel_channel.js" assert_file "app/javascript/channels/chat_channel.js" + + assert_no_file "test/channels/chat_channel_channel_test.rb" + assert_file "test/channels/chat_channel_test.rb" end end diff --git a/railties/test/generators/db_system_change_generator_test.rb b/railties/test/generators/db_system_change_generator_test.rb new file mode 100644 index 0000000000..d3d27b616a --- /dev/null +++ b/railties/test/generators/db_system_change_generator_test.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require "generators/generators_test_helper" +require "rails/generators/rails/db/system/change/change_generator" + +module Rails + module Generators + module Db + module System + class ChangeGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + + setup do + copy_gemfile( + GemfileEntry.new("sqlite3", nil, "Use sqlite3 as the database for Active Record") + ) + end + + test "change to invalid database" do + output = capture(:stderr) do + run_generator ["--to", "invalid-db"] + end + + assert_match <<~MSG.squish, output + Invalid value for --to option. + Supported preconfigurations are: + mysql, postgresql, sqlite3, oracle, frontbase, + ibm_db, sqlserver, jdbcmysql, jdbcsqlite3, + jdbcpostgresql, jdbc. + MSG + end + + test "change to postgresql" do + run_generator ["--to", "postgresql"] + + assert_file("config/database.yml") do |content| + assert_match "adapter: postgresql", content + assert_match "database: test_app", content + end + + assert_file("Gemfile") do |content| + assert_match "# Use pg as the database for Active Record", content + assert_match "gem 'pg', '>= 0.18', '< 2.0'", content + end + end + + test "change to mysql" do + run_generator ["--to", "mysql"] + + 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 + + test "change to sqlite3" do + run_generator ["--to", "sqlite3"] + + assert_file("config/database.yml") do |content| + assert_match "adapter: sqlite3", content + assert_match "db/development.sqlite3", content + end + + assert_file("Gemfile") do |content| + assert_match "# Use sqlite3 as the database for Active Record", 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 + end + end + end +end diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 25d5dba1d8..8b42cb83db 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -30,6 +30,12 @@ module GeneratorsTestHelper include ActiveSupport::Testing::Stream include ActiveSupport::Testing::MethodCallAssertions + GemfileEntry = Struct.new(:name, :version, :comment, :options, :commented_out) do + def initialize(name, version, comment, options = {}, commented_out = false) + super + end + end + def self.included(base) base.class_eval do destination File.join(Rails.root, "tmp") @@ -63,4 +69,38 @@ module GeneratorsTestHelper FileUtils.mkdir_p(destination) FileUtils.cp routes, File.join(destination, "routes.rb") end + + def copy_gemfile(*gemfile_entries) + locals = gemfile_locals.merge(gemfile_entries: gemfile_entries) + gemfile = File.expand_path("../../lib/rails/generators/rails/app/templates/Gemfile.tt", __dir__) + gemfile = evaluate_template(gemfile, locals) + destination = File.join(destination_root) + File.write File.join(destination, "Gemfile"), gemfile + end + + def evaluate_template(file, locals = {}) + erb = if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+ + ERB.new(File.read(file), trim_mode: "-", eoutvar: "@output_buffer") + else + ERB.new(File.read(file), nil, "-", "@output_buffer") + end + context = Class.new do + locals.each do |local, value| + class_attribute local, default: value + end + end + erb.result(context.new.instance_eval("binding")) + end + + private + def gemfile_locals + { + skip_active_storage: true, + depend_on_bootsnap: false, + depend_on_listen: false, + spring_install: false, + depends_on_system_test: false, + options: ActiveSupport::OrderedOptions.new, + } + end end diff --git a/railties/test/generators/helper_generator_test.rb b/railties/test/generators/helper_generator_test.rb index 4cdb6adf82..192839799e 100644 --- a/railties/test/generators/helper_generator_test.rb +++ b/railties/test/generators/helper_generator_test.rb @@ -30,12 +30,17 @@ class HelperGeneratorTest < Rails::Generators::TestCase require "#{destination_root}/app/helpers/products_helper" assert_nothing_raised do - begin - run_generator ["admin::products"] - ensure - # cleanup - Object.send(:remove_const, :ProductsHelper) - end + run_generator ["admin::products"] + ensure + # cleanup + Object.send(:remove_const, :ProductsHelper) end end + + def test_helper_suffix_is_not_duplicated + run_generator %w(products_helper) + + assert_no_file "app/helpers/products_helper_helper.rb" + assert_file "app/helpers/products_helper.rb" + end end diff --git a/railties/test/generators/integration_test_generator_test.rb b/railties/test/generators/integration_test_generator_test.rb index 82791f1a27..2ec4895096 100644 --- a/railties/test/generators/integration_test_generator_test.rb +++ b/railties/test/generators/integration_test_generator_test.rb @@ -15,4 +15,11 @@ class IntegrationTestGeneratorTest < Rails::Generators::TestCase run_generator %w(iguchi/integration) assert_file "test/integration/iguchi/integration_test.rb", /class Iguchi::IntegrationTest < ActionDispatch::IntegrationTest/ end + + def test_test_suffix_is_not_duplicated + run_generator %w(integration_test) + + assert_no_file "test/integration/integration_test_test.rb" + assert_file "test/integration/integration_test.rb" + end end diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index ddac6e1a1e..099e06c4d3 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -119,7 +119,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase assert_match(/haml \[not found\]/, content) end - def test_mailer_with_namedspaced_mailer + def test_mailer_with_namespaced_mailer run_generator ["Farm::Animal", "moos"] assert_file "app/mailers/farm/animal_mailer.rb" do |mailer| assert_match(/class Farm::AnimalMailer < ApplicationMailer/, mailer) diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index 5812cbdfc9..acc5fc3b25 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -265,6 +265,17 @@ class MigrationGeneratorTest < Rails::Generators::TestCase end end + def test_database_puts_migrations_in_configured_folder_with_aliases + with_secondary_database_configuration do + run_generator ["create_books", "--db=secondary"] + assert_migration "db/secondary_migrate/create_books.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_table :books/, change) + end + end + end + end + def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove_or_create migration = "delete_books" run_generator [migration, "title:string", "content:text"] diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index b06db6dd8a..bdb430369e 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -104,7 +104,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase ActiveRecord::Base.pluralize_table_names = true end - def test_migration_with_namespaces_in_model_name_without_plurization + def test_migration_with_namespaces_in_model_name_without_pluralization ActiveRecord::Base.pluralize_table_names = false run_generator ["Gallery::Image"] assert_migration "db/migrate/create_gallery_image", /class CreateGalleryImage < ActiveRecord::Migration\[[0-9.]+\]/ @@ -403,6 +403,17 @@ class ModelGeneratorTest < Rails::Generators::TestCase end end + def test_database_puts_migrations_in_configured_folder_with_aliases + with_secondary_database_configuration do + run_generator ["account", "--db=secondary"] + assert_migration "db/secondary_migrate/create_accounts.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_table :accounts/, change) + end + end + end + end + def test_required_belongs_to_adds_required_association run_generator ["account", "supplier:references{required}"] diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 66286fc554..f45464f8d0 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -442,7 +442,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase end end - def test_dummy_appplication_skip_listen_by_default + def test_dummy_application_skip_listen_by_default run_generator assert_file "test/dummy/config/environments/development.rb" do |contents| @@ -712,38 +712,6 @@ class PluginGeneratorTest < Rails::Generators::TestCase Object.send(:remove_const, "ENGINE_ROOT") end - def test_after_bundle_callback - path = "http://example.org/rails_template" - template = +%{ after_bundle { run "echo ran after_bundle" } } - template.instance_eval "def read; self; end" # Make the string respond to read - - check_open = -> *args do - assert_equal [ path, "Accept" => "application/x-thor-template" ], args - template - end - - sequence = ["echo ran after_bundle"] - @sequence_step ||= 0 - ensure_bundler_first = -> command do - assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}" - @sequence_step += 1 - end - - content = nil - generator([destination_root], template: path).stub(:open, check_open, template) do - generator.stub(:bundle_command, ensure_bundler_first) do - generator.stub(:run, ensure_bundler_first) do - silence_stream($stdout) do - content = capture(:stderr) { generator.invoke_all } - end - end - end - end - - assert_equal 1, @sequence_step - assert_match(/DEPRECATION WARNING: `after_bundle` is deprecated/, content) - end - private def action(*args, &block) diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 3cffdddd38..715ad938f4 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -5,7 +5,7 @@ require "rails/generators/rails/scaffold/scaffold_generator" class ScaffoldGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper - arguments %w(product_line title:string product:belongs_to user:references) + arguments %w(product_line title:string approved:boolean product:belongs_to user:references) setup :copy_routes @@ -17,6 +17,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "test/models/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/ assert_file "test/fixtures/product_lines.yml" assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product/ + assert_migration "db/migrate/create_product_lines.rb", /boolean :approved/ assert_migration "db/migrate/create_product_lines.rb", /references :user/ # Route @@ -60,8 +61,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "test/controllers/product_lines_controller_test.rb" do |test| assert_match(/class ProductLinesControllerTest < ActionDispatch::IntegrationTest/, test) - assert_match(/post product_lines_url, params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test) - assert_match(/patch product_line_url\(@product_line\), params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test) + assert_match(/post product_lines_url, params: \{ product_line: \{ approved: @product_line\.approved, product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test) + assert_match(/patch product_line_url\(@product_line\), params: \{ product_line: \{ approved: @product_line\.approved, product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test) end # System tests @@ -69,6 +70,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_match(/class ProductLinesTest < ApplicationSystemTestCase/, test) assert_match(/visit product_lines_url/, test) assert_match(/fill_in "Title", with: @product_line\.title/, test) + assert_match(/check "Approved" if @product_line\.approved/, test) assert_match(/assert_text "Product line was successfully updated"/, test) end @@ -477,6 +479,14 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end end + def test_scaffold_generator_database_with_aliases + with_secondary_database_configuration do + run_generator ["posts", "--db=secondary"] + + assert_migration "db/secondary_migrate/create_posts.rb" + end + end + def test_scaffold_generator_password_digest run_generator ["user", "name", "password:digest"] diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index 2dda856f25..26ce487c5f 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -10,9 +10,9 @@ module SharedGeneratorTests Rails::Generators::AppGenerator.instance_variable_set("@desc", nil) Kernel.silence_warnings do - Thor::Base.shell.send(:attr_accessor, :always_force) + Thor::Base.shell.attr_accessor :always_force @shell = Thor::Base.shell.new - @shell.send(:always_force=, true) + @shell.always_force = true end end @@ -127,6 +127,8 @@ module SharedGeneratorTests "--skip-active-record", "--skip-active-storage", "--skip-action-mailer", + "--skip-action-mailbox", + "--skip-action-text", "--skip-action-cable", "--skip-sprockets" ] @@ -138,6 +140,10 @@ module SharedGeneratorTests assert_file "#{application_path}/config/application.rb", /^# require\s+["']active_storage\/engine["']/ assert_file "#{application_path}/config/application.rb", /^require\s+["']action_controller\/railtie["']/ assert_file "#{application_path}/config/application.rb", /^# require\s+["']action_mailer\/railtie["']/ + unless generator_class.name == "Rails::Generators::PluginGenerator" + assert_file "#{application_path}/config/application.rb", /^# require\s+["']action_mailbox\/engine["']/ + assert_file "#{application_path}/config/application.rb", /^# require\s+["']action_text\/engine["']/ + end assert_file "#{application_path}/config/application.rb", /^require\s+["']action_view\/railtie["']/ assert_file "#{application_path}/config/application.rb", /^# require\s+["']action_cable\/engine["']/ assert_file "#{application_path}/config/application.rb", /^# require\s+["']sprockets\/railtie["']/ @@ -200,7 +206,7 @@ module SharedGeneratorTests unless generator_class.name == "Rails::Generators::PluginGenerator" assert_file "#{application_path}/app/javascript/packs/application.js" do |content| - assert_match(/^import \* as ActiveStorage from "activestorage"\nActiveStorage.start\(\)/, content) + assert_match(/^require\("@rails\/activestorage"\)\.start\(\)/, content) end end @@ -261,7 +267,7 @@ module SharedGeneratorTests assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']active_storage\/engine["']/ assert_file "#{application_path}/app/javascript/packs/application.js" do |content| - assert_no_match(/^import * as ActiveStorage from "activestorage"\nActiveStorage.start\(\)/, content) + assert_no_match(/^require\("@rails\/activestorage"\)\.start\(\)/, content) end assert_file "#{application_path}/config/environments/development.rb" do |content| @@ -340,11 +346,16 @@ module SharedGeneratorTests run_generator assert_file "#{application_path}/package.json", /dependencies/ assert_file "#{application_path}/bin/yarn" + assert_file "#{application_path}/config/initializers/assets.rb", /node_modules/ end def test_generator_for_yarn_skipped run_generator([destination_root, "--skip-javascript"]) assert_no_file "#{application_path}/package.json" assert_no_file "#{application_path}/bin/yarn" + + assert_file "#{application_path}/config/initializers/assets.rb" do |content| + assert_no_match(/node_modules/, content) + end end end diff --git a/railties/test/generators/system_test_generator_test.rb b/railties/test/generators/system_test_generator_test.rb index efa70a050b..5742ba444d 100644 --- a/railties/test/generators/system_test_generator_test.rb +++ b/railties/test/generators/system_test_generator_test.rb @@ -16,4 +16,18 @@ class SystemTestGeneratorTest < Rails::Generators::TestCase run_generator %w(admin/user) assert_file "test/system/admin/users_test.rb", /class Admin::UsersTest < ApplicationSystemTestCase/ end + + def test_test_name_is_pluralized + run_generator %w(user) + + assert_no_file "test/system/user_test.rb" + assert_file "test/system/users_test.rb" + end + + def test_test_suffix_is_not_duplicated + run_generator %w(users_test) + + assert_no_file "test/system/users_test_test.rb" + assert_file "test/system/users_test.rb" + end end diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index f98c1f78f7..abdc04a8d3 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -28,6 +28,7 @@ class GeneratorsTest < Rails::Generators::TestCase output = capture(:stdout) { Rails::Generators.invoke name } assert_match "Could not find generator '#{name}'", output assert_match "`rails generate --help`", output + assert_no_match "Maybe you meant", output end def test_generator_suggestions diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index d4eed69a87..3fcfaa9623 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -123,6 +123,8 @@ module TestHelpers adapter: sqlite3 pool: 5 timeout: 5000 + variables: + statement_timeout: 1000 development: primary: <<: *default @@ -197,6 +199,7 @@ module TestHelpers end add_to_config <<-RUBY + config.hosts << proc { true } config.eager_load = false config.session_store :cookie_store, key: "_myapp_session" config.active_support.deprecation = :log @@ -220,10 +223,12 @@ module TestHelpers @app = Class.new(Rails::Application) do def self.name; "RailtiesTestApp"; end end + @app.config.hosts << proc { true } @app.config.eager_load = false @app.config.session_store :cookie_store, key: "_myapp_session" @app.config.active_support.deprecation = :log @app.config.log_level = :info + @app.secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" yield @app if block_given? @app.initialize! @@ -419,6 +424,10 @@ module TestHelpers file_name end + def app_dir(path) + FileUtils.mkdir_p("#{app_path}/#{path}") + end + def remove_file(path) FileUtils.rm_rf "#{app_path}/#{path}" end @@ -428,7 +437,7 @@ module TestHelpers end def use_frameworks(arr) - to_remove = [:actionmailer, :activerecord, :activestorage, :activejob] - arr + to_remove = [:actionmailer, :activerecord, :activestorage, :activejob, :actionmailbox] - arr if to_remove.include?(:activerecord) remove_from_config "config.active_record.*" @@ -452,18 +461,21 @@ 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 - - def frozen_error_class - Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError - end end # Create a scope and build a fixture rails app @@ -474,21 +486,24 @@ Module.new do FileUtils.rm_rf(app_template_path) FileUtils.mkdir_p(app_template_path) - Dir.chdir "#{RAILS_FRAMEWORK_ROOT}/actionview" do - `yarn build` - end - - `#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-gemfile --skip-listen --no-rc` + `#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-bundle --skip-listen --no-rc --skip-webpack-install` File.open("#{app_template_path}/config/boot.rb", "w") do |f| f.puts "require 'rails/all'" end - Dir.chdir(app_template_path) { `yarn add https://github.com/rails/webpacker.git` } # Use the latest version. + unless File.exist?("#{RAILS_FRAMEWORK_ROOT}/actionview/lib/assets/compiled/rails-ujs.js") + Dir.chdir("#{RAILS_FRAMEWORK_ROOT}/actionview") { `yarn build` } + end - # Manually install `webpack` as bin symlinks are not created for subdependencies - # in workspaces. See https://github.com/yarnpkg/yarn/issues/4964 - Dir.chdir(app_template_path) { `yarn add webpack@4.17.1 --tilde` } - Dir.chdir(app_template_path) { `yarn add webpack-cli` } + 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") + FileUtils.ln_s("#{assets_path}/node_modules", "#{app_template_path}/node_modules") + FileUtils.chdir(app_template_path) { `bin/rails webpacker:binstubs` } # 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. @@ -506,6 +521,8 @@ Module.new do require "action_view" require "active_storage" require "action_cable" + require "action_mailbox" + require "action_text" require "sprockets" require "action_view/helpers" diff --git a/railties/test/isolation/assets/config/webpack/development.js b/railties/test/isolation/assets/config/webpack/development.js new file mode 100644 index 0000000000..395290f431 --- /dev/null +++ b/railties/test/isolation/assets/config/webpack/development.js @@ -0,0 +1,3 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development' +const { environment } = require('@rails/webpacker') +module.exports = environment.toWebpackConfig() diff --git a/railties/test/isolation/assets/config/webpack/production.js b/railties/test/isolation/assets/config/webpack/production.js new file mode 100644 index 0000000000..d064a6a7fb --- /dev/null +++ b/railties/test/isolation/assets/config/webpack/production.js @@ -0,0 +1,3 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'production' +const { environment } = require('@rails/webpacker') +module.exports = environment.toWebpackConfig() diff --git a/railties/test/isolation/assets/config/webpack/test.js b/railties/test/isolation/assets/config/webpack/test.js new file mode 100644 index 0000000000..395290f431 --- /dev/null +++ b/railties/test/isolation/assets/config/webpack/test.js @@ -0,0 +1,3 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development' +const { environment } = require('@rails/webpacker') +module.exports = environment.toWebpackConfig() diff --git a/railties/test/isolation/assets/config/webpacker.yml b/railties/test/isolation/assets/config/webpacker.yml new file mode 100644 index 0000000000..0b1f43a407 --- /dev/null +++ b/railties/test/isolation/assets/config/webpacker.yml @@ -0,0 +1,8 @@ +default: &default + check_yarn_integrity: false +development: + <<: *default +test: + <<: *default +production: + <<: *default diff --git a/railties/test/isolation/assets/package.json b/railties/test/isolation/assets/package.json new file mode 100644 index 0000000000..7c34450fe0 --- /dev/null +++ b/railties/test/isolation/assets/package.json @@ -0,0 +1,11 @@ +{ + "name": "dummy", + "private": true, + "dependencies": { + "@rails/actioncable": "file:../../../../actioncable", + "@rails/activestorage": "file:../../../../activestorage", + "@rails/ujs": "file:../../../../actionview", + "@rails/webpacker": "https://github.com/rails/webpacker.git", + "turbolinks": "^5.2.0" + } +} diff --git a/railties/test/path_generation_test.rb b/railties/test/path_generation_test.rb index 849b183b37..0c1ee259b0 100644 --- a/railties/test/path_generation_test.rb +++ b/railties/test/path_generation_test.rb @@ -66,7 +66,7 @@ class PathGenerationTest < ActiveSupport::TestCase super app = self @routes = TestSet.new ->(c) { app.controller = c } - secrets.secret_token = "foo" + secrets.secret_key_base = "foo" end def app; routes; end } diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb index 6ab68f8333..185a2f9f09 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -10,6 +10,7 @@ end class InfoControllerTest < ActionController::TestCase tests Rails::InfoController + Rails.application.config.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" def setup Rails.application.routes.draw do @@ -50,11 +51,6 @@ class InfoControllerTest < ActionController::TestCase assert_select "table" end - test "info controller renders json with properties" do - get :properties, format: :json - assert_equal Rails::Info.to_json, response.body - end - test "info controller renders with routes" do get :routes assert_response :success diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb index d167a86e56..50522c1be6 100644 --- a/railties/test/rails_info_test.rb +++ b/railties/test/rails_info_test.rb @@ -43,18 +43,6 @@ class InfoTest < ActiveSupport::TestCase end end - def test_json_includes_middleware - Rails::Info.module_eval do - property "Middleware", ["Rack::Lock", "Rack::Static"] - end - - hash = JSON.parse(Rails::Info.to_json) - assert_includes hash.keys, "Middleware" - properties.value_for("Middleware").each do |value| - assert_includes hash["Middleware"], value - end - end - private def properties Rails::Info.properties diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 4ac8f8d741..69f6e34d58 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -87,11 +87,10 @@ module RailtiesTest end RUBY + restrict_frameworks boot_rails Dir.chdir(app_path) do - # Install Active Storage migration file first so as not to affect test. - `bundle exec rake active_storage:install` output = `bundle exec rake bukkits:install:migrations` ["CreateUsers", "AddLastNameToUsers", "CreateSessions"].each do |migration_name| @@ -174,11 +173,10 @@ module RailtiesTest class CreateKeys < ActiveRecord::Migration::Current; end RUBY + restrict_frameworks boot_rails Dir.chdir(app_path) do - # Install Active Storage migration file first so as not to affect test. - `bundle exec rake active_storage:install` output = `bundle exec rake railties:install:migrations`.split("\n") assert_match(/Copied migration \d+_create_users\.core_engine\.rb from core_engine/, output.first) @@ -706,25 +704,27 @@ YAML RUBY @plugin.write "app/controllers/bukkits/foo_controller.rb", <<-RUBY - class Bukkits::FooController < ActionController::Base - def index - render inline: "<%= help_the_engine %>" - end + module Bukkits + class FooController < ActionController::Base + def index + render inline: "<%= help_the_engine %>" + end - def show - render plain: foo_path - end + def show + render plain: foo_path + end - def from_app - render inline: "<%= (self.respond_to?(:bar_path) || self.respond_to?(:something)) %>" - end + def from_app + render inline: "<%= (self.respond_to?(:bar_path) || self.respond_to?(:something)) %>" + end - def routes_helpers_in_view - render inline: "<%= foo_path %>, <%= main_app.bar_path %>" - end + def routes_helpers_in_view + render inline: "<%= foo_path %>, <%= main_app.bar_path %>" + end - def polymorphic_path_without_namespace - render plain: polymorphic_path(Post.new) + def polymorphic_path_without_namespace + render plain: polymorphic_path(Post.new) + end end end RUBY @@ -879,6 +879,18 @@ YAML assert Bukkits::Engine.config.bukkits_seeds_loaded end + test "jobs are ran inline while loading seeds" do + app_file "db/seeds.rb", <<-RUBY + Rails.application.config.seed_queue_adapter = ActiveJob::Base.queue_adapter + RUBY + + boot_rails + Rails.application.load_seed + + assert_instance_of ActiveJob::QueueAdapters::InlineAdapter, Rails.application.config.seed_queue_adapter + assert_instance_of ActiveJob::QueueAdapters::AsyncAdapter, ActiveJob::Base.queue_adapter + end + test "skips nonexistent seed data" do FileUtils.rm "#{app_path}/db/seeds.rb" boot_rails @@ -1497,5 +1509,25 @@ YAML def app Rails.application end + + # Restrict frameworks to load in order to avoid engine frameworks affect tests. + def restrict_frameworks + remove_from_config("require 'rails/all'") + remove_from_config("require_relative 'boot'") + remove_from_env_config("development", "config.active_storage.*") + frameworks = <<~RUBY + require "rails" + require "active_model/railtie" + require "active_job/railtie" + require "active_record/railtie" + require "action_controller/railtie" + require "action_mailer/railtie" + require "action_view/railtie" + require "sprockets/railtie" + require "rails/test_unit/railtie" + RUBY + environment = File.read("#{app_path}/config/application.rb") + File.open("#{app_path}/config/application.rb", "w") { |f| f.puts frameworks + "\n" + environment } + end end end diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index 48f0fbc80f..9755823e51 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -232,7 +232,7 @@ module ApplicationTests get "/someone/blog/generate_application_route" assert_equal "/", last_response.body - get "/somone/blog/application_route_in_view" + get "/someone/blog/application_route_in_view" assert_equal "/", last_response.body # test generating engine's route from other engine @@ -258,8 +258,8 @@ module ApplicationTests assert_equal "http://example.org/anonymous/blog/posts/44", last_response.body # test that correct path is generated for the same polymorphic_path call in an engine - get "/somone/blog/engine_polymorphic_path" - assert_equal "/somone/blog/posts/44", last_response.body + get "/someone/blog/engine_polymorphic_path" + assert_equal "/someone/blog/posts/44", last_response.body # and in an application get "/application_polymorphic_path" diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index 7c3d1e3759..b9725ca0ad 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -25,12 +25,10 @@ module RailtiesTest end test "Railtie provides railtie_name" do - begin - class ::FooBarBaz < Rails::Railtie ; end - assert_equal "foo_bar_baz", FooBarBaz.railtie_name - ensure - Object.send(:remove_const, :"FooBarBaz") - end + class ::FooBarBaz < Rails::Railtie ; end + assert_equal "foo_bar_baz", FooBarBaz.railtie_name + ensure + Object.send(:remove_const, :"FooBarBaz") end test "railtie_name can be set manually" do @@ -203,14 +201,12 @@ module RailtiesTest end test "we can change our environment if we want to" do - begin - original_env = Rails.env - Rails.env = "foo" - assert_equal("foo", Rails.env) - ensure - Rails.env = original_env - assert_equal(original_env, Rails.env) - end + original_env = Rails.env + Rails.env = "foo" + assert_equal("foo", Rails.env) + ensure + Rails.env = original_env + assert_equal(original_env, Rails.env) end end end diff --git a/railties/test/secrets_test.rb b/railties/test/secrets_test.rb index 06877bc76a..133d851819 100644 --- a/railties/test/secrets_test.rb +++ b/railties/test/secrets_test.rb @@ -35,15 +35,13 @@ class Rails::SecretsTest < ActiveSupport::TestCase test "reading with ENV variable" do run_secrets_generator do - begin - old_key = ENV["RAILS_MASTER_KEY"] - ENV["RAILS_MASTER_KEY"] = IO.binread("config/secrets.yml.key").strip - FileUtils.rm("config/secrets.yml.key") - - assert_match "# production:\n# external_api_key:", Rails::Secrets.read - ensure - ENV["RAILS_MASTER_KEY"] = old_key - end + old_key = ENV["RAILS_MASTER_KEY"] + ENV["RAILS_MASTER_KEY"] = IO.binread("config/secrets.yml.key").strip + FileUtils.rm("config/secrets.yml.key") + + assert_match "# production:\n# external_api_key:", Rails::Secrets.read + ensure + ENV["RAILS_MASTER_KEY"] = old_key end end |