aboutsummaryrefslogtreecommitdiffstats
path: root/railties/lib
diff options
context:
space:
mode:
Diffstat (limited to 'railties/lib')
-rw-r--r--railties/lib/minitest/rails_plugin.rb2
-rw-r--r--railties/lib/rails/app_updater.rb3
-rw-r--r--railties/lib/rails/application.rb2
-rw-r--r--railties/lib/rails/application/configuration.rb14
-rw-r--r--railties/lib/rails/application_controller.rb11
-rw-r--r--railties/lib/rails/command.rb1
-rw-r--r--railties/lib/rails/command/behavior.rb40
-rw-r--r--railties/lib/rails/command/spellchecker.rb58
-rw-r--r--railties/lib/rails/commands/dbconsole/dbconsole_command.rb2
-rw-r--r--railties/lib/rails/commands/routes/routes_command.rb42
-rw-r--r--railties/lib/rails/commands/server/server_command.rb105
-rw-r--r--railties/lib/rails/generators.rb11
-rw-r--r--railties/lib/rails/generators/app_base.rb4
-rw-r--r--railties/lib/rails/generators/erb/mailer/mailer_generator.rb2
-rw-r--r--railties/lib/rails/generators/migration.rb7
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb12
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore.tt5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/ruby-version.tt2
-rw-r--r--railties/lib/rails/generators/rails/controller/controller_generator.rb12
-rw-r--r--railties/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb1
-rw-r--r--railties/lib/rails/generators/test_unit/job/job_generator.rb5
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb2
-rw-r--r--railties/lib/rails/rack/logger.rb6
-rw-r--r--railties/lib/rails/ruby_version_check.rb2
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb243
-rw-r--r--railties/lib/rails/tasks/annotations.rake6
-rw-r--r--railties/lib/rails/tasks/framework.rake2
-rw-r--r--railties/lib/rails/tasks/yarn.rake2
-rw-r--r--railties/lib/rails/templates/rails/mailers/email.html.erb17
34 files changed, 388 insertions, 255 deletions
diff --git a/railties/lib/minitest/rails_plugin.rb b/railties/lib/minitest/rails_plugin.rb
index 7193abbc33..6486fa1798 100644
--- a/railties/lib/minitest/rails_plugin.rb
+++ b/railties/lib/minitest/rails_plugin.rb
@@ -13,7 +13,7 @@ module Minitest
end
def self.plugin_rails_options(opts, options)
- Rails::TestUnit::Runner.attach_before_load_options(opts)
+ ::Rails::TestUnit::Runner.attach_before_load_options(opts)
opts.on("-b", "--backtrace", "Show the complete backtrace") do
options[:full_backtrace] = true
diff --git a/railties/lib/rails/app_updater.rb b/railties/lib/rails/app_updater.rb
index a076d082d5..a243968a39 100644
--- a/railties/lib/rails/app_updater.rb
+++ b/railties/lib/rails/app_updater.rb
@@ -21,12 +21,15 @@ module Rails
private
def generator_options
options = { api: !!Rails.application.config.api_only, update: true }
+ options[:skip_yarn] = !File.exist?(Rails.root.join("bin", "yarn"))
options[:skip_active_record] = !defined?(ActiveRecord::Railtie)
options[:skip_active_storage] = !defined?(ActiveStorage::Engine) || !defined?(ActiveRecord::Railtie)
options[:skip_action_mailer] = !defined?(ActionMailer::Railtie)
options[:skip_action_cable] = !defined?(ActionCable::Engine)
options[:skip_sprockets] = !defined?(Sprockets::Railtie)
options[:skip_puma] = !defined?(Puma)
+ options[:skip_bootsnap] = !defined?(Bootsnap)
+ options[:skip_spring] = !defined?(Spring)
options
end
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index a9dee10981..e346d5cc3a 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -427,7 +427,7 @@ module Rails
# the correct place to store it is in the encrypted credentials file.
def secret_key_base
if Rails.env.test? || Rails.env.development?
- Digest::MD5.hexdigest self.class.name
+ secrets.secret_key_base || Digest::MD5.hexdigest(self.class.name)
else
validate_secret_key_base(
ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 912faed3e4..bba573499d 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -166,6 +166,18 @@ module Rails
end
end
+ # Loads the database YAML without evaluating ERB. People seem to
+ # write ERB that makes the database configuration depend on
+ # Rails configuration. But we want Rails configuration (specifically
+ # `rake` and `rails` tasks) to be generated based on information in
+ # the database yaml, so we need a method that loads the database
+ # yaml *without* the context of the Rails application.
+ def load_database_yaml # :nodoc:
+ path = paths["config/database"].existent.first
+ return {} unless path
+ YAML.load_file(path.to_s)
+ end
+
# Loads and returns the entire raw configuration of database from
# values stored in <tt>config/database.yml</tt>.
def database_configuration
@@ -241,7 +253,7 @@ module Rails
end
def annotations
- SourceAnnotationExtractor::Annotation
+ Rails::SourceAnnotationExtractor::Annotation
end
def content_security_policy(&block)
diff --git a/railties/lib/rails/application_controller.rb b/railties/lib/rails/application_controller.rb
index fa8793d81a..b3fe822218 100644
--- a/railties/lib/rails/application_controller.rb
+++ b/railties/lib/rails/application_controller.rb
@@ -4,6 +4,13 @@ class Rails::ApplicationController < ActionController::Base # :nodoc:
self.view_paths = File.expand_path("templates", __dir__)
layout "application"
+ before_action :disable_content_security_policy_nonce!
+
+ content_security_policy do |policy|
+ policy.script_src :unsafe_inline
+ policy.style_src :unsafe_inline
+ end
+
private
def require_local!
@@ -15,4 +22,8 @@ class Rails::ApplicationController < ActionController::Base # :nodoc:
def local_request?
Rails.application.config.consider_all_requests_local || request.local?
end
+
+ def disable_content_security_policy_nonce!
+ request.content_security_policy_nonce_generator = nil
+ end
end
diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb
index 078e9f937f..6d99ac9936 100644
--- a/railties/lib/rails/command.rb
+++ b/railties/lib/rails/command.rb
@@ -11,6 +11,7 @@ module Rails
module Command
extend ActiveSupport::Autoload
+ autoload :Spellchecker
autoload :Behavior
autoload :Base
diff --git a/railties/lib/rails/command/behavior.rb b/railties/lib/rails/command/behavior.rb
index 7a6dd28e1a..718e2d9ab2 100644
--- a/railties/lib/rails/command/behavior.rb
+++ b/railties/lib/rails/command/behavior.rb
@@ -19,46 +19,6 @@ module Rails
end
private
-
- # This code is based directly on the Text gem implementation.
- # Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher.
- #
- # Returns a value representing the "cost" of transforming str1 into str2.
- def levenshtein_distance(str1, str2) # :doc:
- s = str1
- t = str2
- n = s.length
- m = t.length
-
- return m if (0 == n)
- return n if (0 == m)
-
- d = (0..m).to_a
- x = nil
-
- # avoid duplicating an enumerable object in the loop
- str2_codepoint_enumerable = str2.each_codepoint
-
- str1.each_codepoint.with_index do |char1, i|
- e = i + 1
-
- str2_codepoint_enumerable.with_index do |char2, j|
- cost = (char1 == char2) ? 0 : 1
- x = [
- d[j + 1] + 1, # insertion
- e + 1, # deletion
- d[j] + cost # substitution
- ].min
- d[j] = e
- e = x
- end
-
- d[m] = x
- end
-
- x
- end
-
# Prints a list of generators.
def print_list(base, namespaces)
return if namespaces.empty?
diff --git a/railties/lib/rails/command/spellchecker.rb b/railties/lib/rails/command/spellchecker.rb
new file mode 100644
index 0000000000..04485097fa
--- /dev/null
+++ b/railties/lib/rails/command/spellchecker.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module Rails
+ module Command
+ module Spellchecker # :nodoc:
+ class << self
+ def suggest(word, from:)
+ if defined?(DidYouMean::SpellChecker)
+ DidYouMean::SpellChecker.new(dictionary: from.map(&:to_s)).correct(word).first
+ else
+ from.sort_by { |w| levenshtein_distance(word, w) }.first
+ end
+ end
+
+ private
+
+ # This code is based directly on the Text gem implementation.
+ # Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher.
+ #
+ # Returns a value representing the "cost" of transforming str1 into str2.
+ def levenshtein_distance(str1, str2) # :doc:
+ s = str1
+ t = str2
+ n = s.length
+ m = t.length
+
+ return m if (0 == n)
+ return n if (0 == m)
+
+ d = (0..m).to_a
+ x = nil
+
+ # avoid duplicating an enumerable object in the loop
+ str2_codepoint_enumerable = str2.each_codepoint
+
+ str1.each_codepoint.with_index do |char1, i|
+ e = i + 1
+
+ str2_codepoint_enumerable.with_index do |char2, j|
+ cost = (char1 == char2) ? 0 : 1
+ x = [
+ d[j + 1] + 1, # insertion
+ e + 1, # deletion
+ d[j] + cost # substitution
+ ].min
+ d[j] = e
+ e = x
+ end
+
+ d[m] = x
+ end
+
+ x
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
index 8df548b5de..806b7de6d6 100644
--- a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
+++ b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
@@ -97,7 +97,7 @@ module Rails
elsif configurations[environment].blank? && configurations[connection].blank?
raise ActiveRecord::AdapterNotSpecified, "'#{environment}' database is not configured. Available configuration: #{configurations.inspect}"
else
- configurations[environment].presence || configurations[connection]
+ configurations[connection] || configurations[environment].presence
end
end
end
diff --git a/railties/lib/rails/commands/routes/routes_command.rb b/railties/lib/rails/commands/routes/routes_command.rb
index c4f3717095..b592a5212f 100644
--- a/railties/lib/rails/commands/routes/routes_command.rb
+++ b/railties/lib/rails/commands/routes/routes_command.rb
@@ -5,45 +5,33 @@ require "rails/command"
module Rails
module Command
class RoutesCommand < Base # :nodoc:
- class_option :controller, aliases: "-c", type: :string, desc: "Specifies the controller."
- class_option :grep_pattern, aliases: "-g", type: :string, desc: "Specifies grep pattern."
- class_option :expanded_format, aliases: "--expanded", type: :string, desc: "Turn on expanded format mode."
-
- no_commands do
- def help
- say "Usage: Print out all defined routes in match order, with names."
- say ""
- say "Target specific controller with -c option, or grep routes using -g option"
- say "Use expanded format with --expanded option"
- say ""
- end
- end
+ class_option :controller, aliases: "-c", desc: "Filter by a specific controller, e.g. PostsController or Admin::PostsController."
+ class_option :grep, aliases: "-g", desc: "Grep routes by a specific pattern."
+ class_option :expanded, type: :boolean, aliases: "-E", desc: "Print routes expanded vertically with parts explained."
def perform(*)
require_application_and_environment!
require "action_dispatch/routing/inspector"
- all_routes = Rails.application.routes.routes
- inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
-
- if options.has_key?("expanded_format")
- say inspector.format(ActionDispatch::Routing::ConsoleFormatter::Expanded.new, routes_filter)
- else
- say inspector.format(ActionDispatch::Routing::ConsoleFormatter::Sheet.new, routes_filter)
- end
+ say inspector.format(formatter, routes_filter)
end
private
+ def inspector
+ ActionDispatch::Routing::RoutesInspector.new(Rails.application.routes.routes)
+ end
- def routes_filter
- if options.has_key?("controller")
- { controller: options["controller"] }
- elsif options.has_key?("grep_pattern")
- options["grep_pattern"]
+ def formatter
+ if options.key?("expanded")
+ ActionDispatch::Routing::ConsoleFormatter::Expanded.new
else
- nil
+ ActionDispatch::Routing::ConsoleFormatter::Sheet.new
end
end
+
+ def routes_filter
+ options.symbolize_keys.slice(:controller, :grep)
+ end
end
end
end
diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb
index e546fe3e4b..77b6c1f65d 100644
--- a/railties/lib/rails/commands/server/server_command.rb
+++ b/railties/lib/rails/commands/server/server_command.rb
@@ -43,18 +43,22 @@ module Rails
ENV["RAILS_ENV"] ||= options[:environment]
end
- def start
- print_boot_information
+ def start(after_stop_callback = nil)
trap(:INT) { exit }
create_tmp_directories
setup_dev_caching
log_to_stdout if options[:log_stdout]
- super
+ super()
ensure
- # The '-h' option calls exit before @options is set.
- # If we call 'options' with it unset, we get double help banners.
- puts "Exiting" unless @options && options[:daemonize]
+ after_stop_callback.call if after_stop_callback
+ end
+
+ def serveable? # :nodoc:
+ server
+ true
+ rescue LoadError, NameError
+ false
end
def middleware
@@ -65,6 +69,10 @@ module Rails
super.merge(@default_options)
end
+ def served_url
+ "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}" unless use_puma?
+ end
+
private
def setup_dev_caching
if options[:environment] == "development"
@@ -72,13 +80,6 @@ module Rails
end
end
- def print_boot_information
- url = "on #{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}" unless use_puma?
- puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
- puts "=> Rails #{Rails.version} application starting in #{Rails.env} #{url}"
- puts "=> Run `rails server -h` for more startup options"
- end
-
def create_tmp_directories
%w(cache pids sockets).each do |dir_to_make|
FileUtils.mkdir_p(File.join(Rails.root, "tmp", dir_to_make))
@@ -108,9 +109,15 @@ module Rails
module Command
class ServerCommand < Base # :nodoc:
+ # 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)
+
DEFAULT_PORT = 3000
DEFAULT_PID_PATH = "tmp/pids/server.pid".freeze
+ argument :using, optional: true
+
class_option :port, aliases: "-p", type: :numeric,
desc: "Runs Rails on the specified port - defaults to 3000.", banner: :port
class_option :binding, aliases: "-b", type: :string,
@@ -122,6 +129,8 @@ module Rails
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,
desc: "Specifies the PID file."
class_option "dev-caching", aliases: "-C", type: :boolean, default: nil,
@@ -132,19 +141,27 @@ module Rails
def initialize(args = [], local_options = {}, config = {})
@original_options = local_options
super
- @server = self.args.shift
+ @using = deprecated_positional_rack_server(using) || options[:using]
@log_stdout = options[:daemon].blank? && (options[:environment] || Rails.env) == "development"
end
def perform
set_application_directory!
prepare_restart
+
Rails::Server.new(server_options).tap do |server|
# Require application after server sets environment to propagate
# the --environment option.
require APP_PATH
Dir.chdir(Rails.application.root)
- server.start
+
+ if server.serveable?
+ print_boot_information(server.server, server.served_url)
+ after_stop_callback = -> { say "Exiting" unless options[:daemon] }
+ server.start(after_stop_callback)
+ else
+ say rack_server_suggestion(using)
+ end
end
end
@@ -152,7 +169,7 @@ module Rails
def server_options
{
user_supplied_options: user_supplied_options,
- server: @server,
+ server: using,
log_stdout: @log_stdout,
Port: port,
Host: host,
@@ -202,7 +219,7 @@ module Rails
user_supplied_options << name
end
end
- user_supplied_options << :Host if ENV["HOST"]
+ user_supplied_options << :Host if ENV["HOST"] || ENV["BINDING"]
user_supplied_options << :Port if ENV["PORT"]
user_supplied_options.uniq
end
@@ -217,7 +234,17 @@ module Rails
options[:binding]
else
default_host = environment == "development" ? "localhost" : "0.0.0.0"
- ENV.fetch("HOST", default_host)
+
+ 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.
+ MSG
+
+ return ENV["HOST"]
+ end
+
+ ENV.fetch("BINDING", default_host)
end
end
@@ -226,7 +253,7 @@ module Rails
end
def restart_command
- "bin/rails server #{@server} #{@original_options.join(" ")} --restart"
+ "bin/rails server #{using} #{@original_options.join(" ")} --restart"
end
def early_hints
@@ -238,12 +265,50 @@ module Rails
end
def self.banner(*)
- "rails server [puma, thin etc] [options]"
+ "rails server [thin/puma/webrick] [options]"
end
def prepare_restart
FileUtils.rm_f(options[:pid]) if options[:restart]
end
+
+ def deprecated_positional_rack_server(value)
+ if value
+ 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.
+ MSG
+ value
+ end
+ end
+
+ def rack_server_suggestion(server)
+ if server.in?(RACK_SERVERS)
+ <<~MSG
+ Could not load server "#{server}". Maybe you need to the add it to the Gemfile?
+
+ gem "#{server}"
+
+ Run `rails server --help` for more options.
+ MSG
+ else
+ suggestions = Rails::Command::Spellchecker.suggest(server, from: RACK_SERVERS)
+
+ <<~MSG
+ Could not find server "#{server}". Maybe you meant #{suggestions.inspect}?
+ Run `rails server --help` for more options.
+ MSG
+ end
+ end
+
+ def print_boot_information(server, url)
+ say <<~MSG
+ => Booting #{ActiveSupport::Inflector.demodulize(server)}
+ => Rails #{Rails.version} application starting in #{Rails.env} #{url}
+ => Run `rails server --help` for more startup options
+ MSG
+ end
end
end
end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 6a7175c02b..f8460bd4ee 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -276,12 +276,11 @@ module Rails
klass.start(args, config)
else
options = sorted_groups.flat_map(&:last)
- suggestions = options.sort_by { |suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3)
- suggestions.map! { |s| "'#{s}'" }
- msg = "Could not find generator '#{namespace}'. ".dup
- msg << "Maybe you meant #{ suggestions[0...-1].join(', ')} or #{suggestions[-1]}\n"
- msg << "Run `rails generate --help` for more options."
- puts msg
+ suggestion = Rails::Command::Spellchecker.suggest(namespace.to_s, from: options)
+ puts <<~MSG
+ Could not find generator '#{namespace}'. Maybe you meant #{suggestion.inspect}?
+ Run `rails generate --help` for more options.
+ MSG
end
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index e1889979d7..f51542f3ec 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -299,7 +299,7 @@ module Rails
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 "mysql" then ["mysql2", [">= 0.4.4", "< 0.6.0"]]
when "postgresql" then ["pg", [">= 0.18", "< 2.0"]]
when "oracle" then ["activerecord-oracle_enhanced-adapter", nil]
when "frontbase" then ["ruby-frontbase", nil]
@@ -440,7 +440,7 @@ module Rails
end
def depend_on_bootsnap?
- !options[:skip_bootsnap] && !options[:dev]
+ !options[:skip_bootsnap] && !options[:dev] && !defined?(JRUBY_VERSION)
end
def os_supports_listen_out_of_the_box?
diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
index e2ea66415f..997602cb8c 100644
--- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
@@ -35,7 +35,7 @@ module Erb # :nodoc:
end
def file_name
- @_file_name ||= super.gsub(/_mailer/i, "")
+ @_file_name ||= super.sub(/_mailer\z/i, "")
end
end
end
diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb
index 1cbccfe461..5081060895 100644
--- a/railties/lib/rails/generators/migration.rb
+++ b/railties/lib/rails/generators/migration.rb
@@ -63,7 +63,12 @@ module Rails
numbered_destination = File.join(dir, ["%migration_number%", base].join("_"))
create_migration numbered_destination, nil, config do
- ERB.new(::File.binread(source), nil, "-", "@output_buffer").result(context)
+ match = ERB.version.match(/\Aerb\.rb \[(?<version>[^ ]+) /)
+ if match && match[:version] >= "2.2.0" # 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)
+ end
end
end
end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 395ac7ef2f..34067240d7 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -95,11 +95,9 @@ module Rails
end
def bin_when_updating
- bin_yarn_exist = File.exist?("bin/yarn")
-
bin
- if options[:api] && !bin_yarn_exist
+ if options[:skip_yarn]
remove_file "bin/yarn"
end
end
@@ -146,6 +144,10 @@ module Rails
template "config/storage.yml"
end
+ if options[:skip_sprockets] && !assets_config_exist
+ remove_file "config/initializers/assets.rb"
+ end
+
unless rack_cors_config_exist
remove_file "config/initializers/cors.rb"
end
@@ -155,10 +157,6 @@ module Rails
remove_file "config/initializers/cookies_serializer.rb"
end
- unless assets_config_exist
- remove_file "config/initializers/assets.rb"
- end
-
unless csp_config_exist
remove_file "config/initializers/content_security_policy.rb"
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
index 23bb89f4ce..1567333023 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
@@ -23,7 +23,7 @@ ruby <%= "'#{RUBY_VERSION}'" -%>
<% unless skip_active_storage? -%>
# Use ActiveStorage variant
-# gem 'mini_magick', '~> 4.8'
+# gem 'image_processing', '~> 1.2'
<% end -%>
# Use Capistrano for deployment
@@ -69,7 +69,7 @@ end
<%- if depends_on_system_test? -%>
group :test do
# Adds support for Capybara system testing and selenium driver
- gem 'capybara', '~> 2.15'
+ gem 'capybara', '>= 2.15'
gem 'selenium-webdriver'
# Easy installation and use of chromedriver to run system tests with Chrome
gem 'chromedriver-helper'
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 a87649b50f..3807c8a9aa 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
@@ -28,7 +28,7 @@ Rails.application.configure do
end
<%- unless skip_active_storage? -%>
- # Store uploaded files on the local file system (see config/storage.yml for options)
+ # Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
<%- end -%>
<%- unless options.skip_action_mailer? -%>
@@ -60,7 +60,7 @@ Rails.application.configure do
config.assets.quiet = true
<%- end -%>
- # Raises error for missing translations
+ # Raises error for missing translations.
# config.action_view.raise_on_missing_translations = true
# Use an evented file watcher to asynchronously detect changes in source code,
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 926326b5bb..d646694477 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
@@ -43,12 +43,12 @@ Rails.application.configure do
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
<%- unless skip_active_storage? -%>
- # Store uploaded files on the local file system (see config/storage.yml for options)
+ # Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
<%- end -%>
<%- unless options[:skip_action_cable] -%>
- # Mount Action Cable outside main process or domain
+ # Mount Action Cable outside main process or domain.
# config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable'
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
@@ -67,7 +67,7 @@ Rails.application.configure do
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
- # Use a real queuing backend for Active Job (and separate queues per environment)
+ # Use a real queuing backend for Active Job (and separate queues per environment).
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "<%= app_name %>_#{Rails.env}"
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 ff4c89219a..82f2a8aebe 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
@@ -29,7 +29,7 @@ Rails.application.configure do
config.action_controller.allow_forgery_protection = false
<%- unless skip_active_storage? -%>
- # Store uploaded files on the local file system in a temporary directory
+ # Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test
<%- end -%>
@@ -45,6 +45,9 @@ Rails.application.configure do
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
- # Raises error for missing translations
+ # 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/storage.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt
index 1c0cde0b09..7207c75086 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt
@@ -24,7 +24,6 @@ local:
# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
# microsoft:
# service: AzureStorage
-# path: your_azure_storage_path
# storage_account_name: your_account_name
# storage_access_key: <%%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
# container: your_container_name
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore.tt b/railties/lib/rails/generators/rails/app/templates/gitignore.tt
index 2cd8335aba..4e114fb1d9 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore.tt
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore.tt
@@ -24,8 +24,11 @@
<% unless skip_active_storage? -%>
# Ignore uploaded files in development
/storage/*
-
+<% if keeps? -%>
+!/storage/.keep
<% end -%>
+<% end -%>
+
<% unless options.skip_yarn? -%>
/node_modules
/yarn-error.log
diff --git a/railties/lib/rails/generators/rails/app/templates/ruby-version.tt b/railties/lib/rails/generators/rails/app/templates/ruby-version.tt
index c444f33b0f..19f0d7f202 100644
--- a/railties/lib/rails/generators/rails/app/templates/ruby-version.tt
+++ b/railties/lib/rails/generators/rails/app/templates/ruby-version.tt
@@ -1 +1 @@
-<%= RUBY_VERSION -%>
+<%= "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" -%>
diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb
index 6e2495d45f..eb75e7e661 100644
--- a/railties/lib/rails/generators/rails/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb
@@ -20,10 +20,20 @@ module Rails
route generate_routing_code
end
- hook_for :template_engine, :test_framework, :helper, :assets
+ hook_for :template_engine, :test_framework, :helper, :assets do |generator|
+ invoke generator, [ remove_possible_suffix(name), actions ]
+ end
private
+ def file_name
+ @_file_name ||= remove_possible_suffix(super)
+ end
+
+ def remove_possible_suffix(name)
+ name.sub(/_?controller$/i, "")
+ end
+
# This method creates nested route entry for namespaced resources.
# For eg. rails g controller foo/bar/baz index show
# Will generate -
diff --git a/railties/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb b/railties/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb
index 90068c678d..e2359e9ded 100644
--- a/railties/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb
+++ b/railties/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb
@@ -27,6 +27,7 @@ module Rails
def add_key_file_silently(key_path, key = nil)
create_file key_path, key || ActiveSupport::EncryptedFile.generate_key
+ key_path.chmod 0600
end
def ignore_key_file(key_path, ignore: key_ignore(key_path))
diff --git a/railties/lib/rails/generators/test_unit/job/job_generator.rb b/railties/lib/rails/generators/test_unit/job/job_generator.rb
index a99ce914c0..1dae3cb6a5 100644
--- a/railties/lib/rails/generators/test_unit/job/job_generator.rb
+++ b/railties/lib/rails/generators/test_unit/job/job_generator.rb
@@ -10,6 +10,11 @@ module TestUnit # :nodoc:
def create_test_file
template "unit_test.rb", File.join("test/jobs", class_path, "#{file_name}_job_test.rb")
end
+
+ private
+ def file_name
+ @_file_name ||= super.sub(/_job\z/i, "")
+ end
end
end
end
diff --git a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
index 610d47a729..ab8331f31c 100644
--- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
@@ -21,7 +21,7 @@ module TestUnit # :nodoc:
private
def file_name
- @_file_name ||= super.gsub(/_mailer/i, "")
+ @_file_name ||= super.sub(/_mailer\z/i, "")
end
end
end
diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb
index ec5212ee76..4ea7e40319 100644
--- a/railties/lib/rails/rack/logger.rb
+++ b/railties/lib/rails/rack/logger.rb
@@ -35,9 +35,9 @@ module Rails
instrumenter = ActiveSupport::Notifications.instrumenter
instrumenter.start "request.action_dispatch", request: request
logger.info { started_request_message(request) }
- resp = @app.call(env)
- resp[2] = ::Rack::BodyProxy.new(resp[2]) { finish(request) }
- resp
+ status, headers, body = @app.call(env)
+ body = ::Rack::BodyProxy.new(body) { finish(request) }
+ [status, headers, body]
rescue Exception
finish(request)
raise
diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb
index f8d3311156..b2d44d9b8e 100644
--- a/railties/lib/rails/ruby_version_check.rb
+++ b/railties/lib/rails/ruby_version_check.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-if RUBY_VERSION < "2.4.1" && RUBY_ENGINE == "ruby"
+if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.4.1") && RUBY_ENGINE == "ruby"
desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
abort <<-end_message
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index 1db6c98537..7257aaeaae 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -1,141 +1,150 @@
# frozen_string_literal: true
-# Implements the logic behind the rake tasks for annotations like
-#
-# rails notes
-# rails notes:optimize
-#
-# and friends. See <tt>rails -T notes</tt> and <tt>railties/lib/rails/tasks/annotations.rake</tt>.
-#
-# Annotation objects are triplets <tt>:line</tt>, <tt>:tag</tt>, <tt>:text</tt> that
-# represent the line where the annotation lives, its tag, and its text. Note
-# the filename is not stored.
-#
-# Annotations are looked for in comments and modulus whitespace they have to
-# start with the tag optionally followed by a colon. Everything up to the end
-# of the line (or closing ERB comment tag) is considered to be their text.
-class SourceAnnotationExtractor
- Annotation = Struct.new(:line, :tag, :text) do
- def self.directories
- @@directories ||= %w(app config db lib test) + (ENV["SOURCE_ANNOTATION_DIRECTORIES"] || "").split(",")
- end
+require "active_support/deprecation"
- # Registers additional directories to be included
- # SourceAnnotationExtractor::Annotation.register_directories("spec", "another")
- def self.register_directories(*dirs)
- directories.push(*dirs)
- end
+# Remove this deprecated class in the next minor version
+#:nodoc:
+SourceAnnotationExtractor = ActiveSupport::Deprecation::DeprecatedConstantProxy.
+ new("SourceAnnotationExtractor", "Rails::SourceAnnotationExtractor")
- def self.extensions
- @@extensions ||= {}
- end
+module Rails
+ # Implements the logic behind the rake tasks for annotations like
+ #
+ # rails notes
+ # rails notes:optimize
+ #
+ # and friends. See <tt>rails -T notes</tt> and <tt>railties/lib/rails/tasks/annotations.rake</tt>.
+ #
+ # Annotation objects are triplets <tt>:line</tt>, <tt>:tag</tt>, <tt>:text</tt> that
+ # represent the line where the annotation lives, its tag, and its text. Note
+ # the filename is not stored.
+ #
+ # Annotations are looked for in comments and modulus whitespace they have to
+ # start with the tag optionally followed by a colon. Everything up to the end
+ # of the line (or closing ERB comment tag) is considered to be their text.
+ class SourceAnnotationExtractor
+ class Annotation < Struct.new(:line, :tag, :text)
+ def self.directories
+ @@directories ||= %w(app config db lib test) + (ENV["SOURCE_ANNOTATION_DIRECTORIES"] || "").split(",")
+ end
- # Registers new Annotations File Extensions
- # SourceAnnotationExtractor::Annotation.register_extensions("css", "scss", "sass", "less", "js") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ }
- def self.register_extensions(*exts, &block)
- extensions[/\.(#{exts.join("|")})$/] = block
- end
+ # Registers additional directories to be included
+ # Rails::SourceAnnotationExtractor::Annotation.register_directories("spec", "another")
+ def self.register_directories(*dirs)
+ directories.push(*dirs)
+ end
- register_extensions("builder", "rb", "rake", "yml", "yaml", "ruby") { |tag| /#\s*(#{tag}):?\s*(.*)$/ }
- register_extensions("css", "js") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ }
- register_extensions("erb") { |tag| /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/ }
+ def self.extensions
+ @@extensions ||= {}
+ end
- # Returns a representation of the annotation that looks like this:
+ # Registers new Annotations File Extensions
+ # Rails::SourceAnnotationExtractor::Annotation.register_extensions("css", "scss", "sass", "less", "js") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ }
+ def self.register_extensions(*exts, &block)
+ extensions[/\.(#{exts.join("|")})$/] = block
+ end
+
+ register_extensions("builder", "rb", "rake", "yml", "yaml", "ruby") { |tag| /#\s*(#{tag}):?\s*(.*)$/ }
+ register_extensions("css", "js") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ }
+ register_extensions("erb") { |tag| /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/ }
+
+ # Returns a representation of the annotation that looks like this:
+ #
+ # [126] [TODO] This algorithm is simple and clearly correct, make it faster.
+ #
+ # If +options+ has a flag <tt>:tag</tt> the tag is shown as in the example above.
+ # Otherwise the string contains just line and text.
+ def to_s(options = {})
+ s = "[#{line.to_s.rjust(options[:indent])}] ".dup
+ s << "[#{tag}] " if options[:tag]
+ s << text
+ end
+ end
+
+ # Prints all annotations with tag +tag+ under the root directories +app+,
+ # +config+, +db+, +lib+, and +test+ (recursively).
#
- # [126] [TODO] This algorithm is simple and clearly correct, make it faster.
+ # Additional directories may be added using a comma-delimited list set using
+ # <tt>ENV['SOURCE_ANNOTATION_DIRECTORIES']</tt>.
#
- # If +options+ has a flag <tt>:tag</tt> the tag is shown as in the example above.
- # Otherwise the string contains just line and text.
- def to_s(options = {})
- s = "[#{line.to_s.rjust(options[:indent])}] ".dup
- s << "[#{tag}] " if options[:tag]
- s << text
+ # Directories may also be explicitly set using the <tt>:dirs</tt> key in +options+.
+ #
+ # Rails::SourceAnnotationExtractor.enumerate 'TODO|FIXME', dirs: %w(app lib), tag: true
+ #
+ # If +options+ has a <tt>:tag</tt> flag, it will be passed to each annotation's +to_s+.
+ #
+ # See <tt>#find_in</tt> for a list of file extensions that will be taken into account.
+ #
+ # This class method is the single entry point for the rake tasks.
+ def self.enumerate(tag, options = {})
+ extractor = new(tag)
+ dirs = options.delete(:dirs) || Annotation.directories
+ extractor.display(extractor.find(dirs), options)
end
- end
- # Prints all annotations with tag +tag+ under the root directories +app+,
- # +config+, +db+, +lib+, and +test+ (recursively).
- #
- # Additional directories may be added using a comma-delimited list set using
- # <tt>ENV['SOURCE_ANNOTATION_DIRECTORIES']</tt>.
- #
- # Directories may also be explicitly set using the <tt>:dirs</tt> key in +options+.
- #
- # SourceAnnotationExtractor.enumerate 'TODO|FIXME', dirs: %w(app lib), tag: true
- #
- # If +options+ has a <tt>:tag</tt> flag, it will be passed to each annotation's +to_s+.
- #
- # See <tt>#find_in</tt> for a list of file extensions that will be taken into account.
- #
- # This class method is the single entry point for the rake tasks.
- def self.enumerate(tag, options = {})
- extractor = new(tag)
- dirs = options.delete(:dirs) || Annotation.directories
- extractor.display(extractor.find(dirs), options)
- end
-
- attr_reader :tag
-
- def initialize(tag)
- @tag = tag
- end
+ attr_reader :tag
- # Returns a hash that maps filenames under +dirs+ (recursively) to arrays
- # with their annotations.
- def find(dirs)
- dirs.inject({}) { |h, dir| h.update(find_in(dir)) }
- end
+ def initialize(tag)
+ @tag = tag
+ end
- # Returns a hash that maps filenames under +dir+ (recursively) to arrays
- # with their annotations. Only files with annotations are included. Files
- # with extension +.builder+, +.rb+, +.rake+, +.yml+, +.yaml+, +.ruby+,
- # +.css+, +.js+ and +.erb+ are taken into account.
- def find_in(dir)
- results = {}
-
- Dir.glob("#{dir}/*") do |item|
- next if File.basename(item)[0] == ?.
-
- if File.directory?(item)
- results.update(find_in(item))
- else
- extension = Annotation.extensions.detect do |regexp, _block|
- regexp.match(item)
- end
+ # Returns a hash that maps filenames under +dirs+ (recursively) to arrays
+ # with their annotations.
+ def find(dirs)
+ dirs.inject({}) { |h, dir| h.update(find_in(dir)) }
+ end
- if extension
- pattern = extension.last.call(tag)
- results.update(extract_annotations_from(item, pattern)) if pattern
+ # Returns a hash that maps filenames under +dir+ (recursively) to arrays
+ # with their annotations. Files with extensions registered in
+ # <tt>Rails::SourceAnnotationExtractor::Annotation.extensions</tt> are
+ # taken into account. Only files with annotations are included.
+ def find_in(dir)
+ results = {}
+
+ Dir.glob("#{dir}/*") do |item|
+ next if File.basename(item)[0] == ?.
+
+ if File.directory?(item)
+ results.update(find_in(item))
+ else
+ extension = Annotation.extensions.detect do |regexp, _block|
+ regexp.match(item)
+ end
+
+ if extension
+ pattern = extension.last.call(tag)
+ results.update(extract_annotations_from(item, pattern)) if pattern
+ end
end
end
- end
- results
- end
+ results
+ end
- # If +file+ is the filename of a file that contains annotations this method returns
- # a hash with a single entry that maps +file+ to an array of its annotations.
- # Otherwise it returns an empty hash.
- def extract_annotations_from(file, pattern)
- lineno = 0
- result = File.readlines(file, encoding: Encoding::BINARY).inject([]) do |list, line|
- lineno += 1
- next list unless line =~ pattern
- list << Annotation.new(lineno, $1, $2)
+ # If +file+ is the filename of a file that contains annotations this method returns
+ # a hash with a single entry that maps +file+ to an array of its annotations.
+ # Otherwise it returns an empty hash.
+ def extract_annotations_from(file, pattern)
+ lineno = 0
+ result = File.readlines(file, encoding: Encoding::BINARY).inject([]) do |list, line|
+ lineno += 1
+ next list unless line =~ pattern
+ list << Annotation.new(lineno, $1, $2)
+ end
+ result.empty? ? {} : { file => result }
end
- result.empty? ? {} : { file => result }
- end
- # Prints the mapping from filenames to annotations in +results+ ordered by filename.
- # The +options+ hash is passed to each annotation's +to_s+.
- def display(results, options = {})
- options[:indent] = results.flat_map { |f, a| a.map(&:line) }.max.to_s.size
- results.keys.sort.each do |file|
- puts "#{file}:"
- results[file].each do |note|
- puts " * #{note.to_s(options)}"
+ # Prints the mapping from filenames to annotations in +results+ ordered by filename.
+ # The +options+ hash is passed to each annotation's +to_s+.
+ def display(results, options = {})
+ options[:indent] = results.flat_map { |f, a| a.map(&:line) }.max.to_s.size
+ results.keys.sort.each do |file|
+ puts "#{file}:"
+ results[file].each do |note|
+ puts " * #{note.to_s(options)}"
+ end
+ puts
end
- puts
end
end
end
diff --git a/railties/lib/rails/tasks/annotations.rake b/railties/lib/rails/tasks/annotations.rake
index 93bc66e2db..60bcdc5e1b 100644
--- a/railties/lib/rails/tasks/annotations.rake
+++ b/railties/lib/rails/tasks/annotations.rake
@@ -4,19 +4,19 @@ require "rails/source_annotation_extractor"
desc "Enumerate all annotations (use notes:optimize, :fixme, :todo for focus)"
task :notes do
- SourceAnnotationExtractor.enumerate "OPTIMIZE|FIXME|TODO", tag: true
+ Rails::SourceAnnotationExtractor.enumerate "OPTIMIZE|FIXME|TODO", tag: true
end
namespace :notes do
["OPTIMIZE", "FIXME", "TODO"].each do |annotation|
# desc "Enumerate all #{annotation} annotations"
task annotation.downcase.intern do
- SourceAnnotationExtractor.enumerate annotation
+ Rails::SourceAnnotationExtractor.enumerate annotation
end
end
desc "Enumerate a custom annotation, specify with ANNOTATION=CUSTOM"
task :custom do
- SourceAnnotationExtractor.enumerate ENV["ANNOTATION"]
+ Rails::SourceAnnotationExtractor.enumerate ENV["ANNOTATION"]
end
end
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 7dfcd14bd0..1a3711c446 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -40,7 +40,7 @@ namespace :app do
namespace :update do
require "rails/app_updater"
- # desc "Update config/boot.rb from your current rails install"
+ # desc "Update config files from your current rails install"
task :configs do
Rails::AppUpdater.invoke_from_app_generator :create_boot_file
Rails::AppUpdater.invoke_from_app_generator :update_config_files
diff --git a/railties/lib/rails/tasks/yarn.rake b/railties/lib/rails/tasks/yarn.rake
index 48da7ffc51..10703a1808 100644
--- a/railties/lib/rails/tasks/yarn.rake
+++ b/railties/lib/rails/tasks/yarn.rake
@@ -3,7 +3,7 @@
namespace :yarn do
desc "Install all JavaScript dependencies as specified via Yarn"
task :install do
- system("./bin/yarn install --no-progress --production")
+ system("./bin/yarn install --no-progress --frozen-lockfile --production")
end
end
diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb
index 2a41c29602..e46364ba8a 100644
--- a/railties/lib/rails/templates/rails/mailers/email.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/email.html.erb
@@ -98,7 +98,7 @@
<dt>Format:</dt>
<% if @email.multipart? %>
<dd>
- <select id="part" onchange="refreshBody();">
+ <select id="part" onchange="refreshBody(false);">
<option <%= request.format == Mime[:html] ? 'selected' : '' %> value="<%= part_query('text/html') %>">View as HTML email</option>
<option <%= request.format == Mime[:text] ? 'selected' : '' %> value="<%= part_query('text/plain') %>">View as plain-text email</option>
</select>
@@ -110,7 +110,7 @@
<% if I18n.available_locales.count > 1 %>
<dt>Locale:</dt>
<dd>
- <select id="locale" onchange="refreshBody();">
+ <select id="locale" onchange="refreshBody(true);">
<% I18n.available_locales.each do |locale| %>
<option <%= I18n.locale == locale ? 'selected' : '' %> value="<%= locale_query(locale) %>"><%= locale %></option>
<% end %>
@@ -130,7 +130,7 @@
<% end %>
<script>
- function refreshBody() {
+ function refreshBody(reload) {
var part_select = document.querySelector('select#part');
var locale_select = document.querySelector('select#locale');
var iframe = document.getElementsByName('messageBody')[0];
@@ -146,10 +146,13 @@
}
iframe.contentWindow.location = fresh_location;
- if (history.replaceState) {
- var url = location.pathname.replace(/\.(txt|html)$/, '');
- var format = /html/.test(part_param) ? '.html' : '.txt';
- var state_to_replace = locale_param ? (url + format + '?' + locale_param) : (url + format);
+ var url = location.pathname.replace(/\.(txt|html)$/, '');
+ var format = /html/.test(part_param) ? '.html' : '.txt';
+ var state_to_replace = locale_param ? (url + format + '?' + locale_param) : (url + format);
+
+ if (reload) {
+ location.href = state_to_replace;
+ } else if (history.replaceState) {
window.history.replaceState({}, '', state_to_replace);
}
}