diff options
Diffstat (limited to 'railties/lib/rails')
49 files changed, 603 insertions, 149 deletions
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 5a924ab8e6..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" @@ -230,8 +231,8 @@ module Rails config = YAML.load(ERB.new(yaml.read).result) || {} config = (config["shared"] || {}).merge(config[env] || {}) - ActiveSupport::OrderedOptions.new.tap do |config_as_ordered_options| - config_as_ordered_options.update(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}" @@ -408,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 @@ -580,6 +582,22 @@ module Rails 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 @@ -590,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/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb index e3c0759f95..50685a4d7a 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -19,14 +19,14 @@ module Rails initializer :set_eager_load, group: :all do if config.eager_load.nil? - warn <<-INFO -config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly: + warn <<~INFO + config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly: - * development - set it to false - * test - set it to false (unless you use a tool that preloads your test environment) - * production - set it to true + * development - set it to false + * test - set it to false (unless you use a tool that preloads your test environment) + * production - set it to true -INFO + INFO config.eager_load = config.cache_classes end end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index b7838f7e32..0b758dd3dd 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -18,9 +18,10 @@ module Rails :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, :add_autoload_paths_to_load_path - attr_reader :encoding, :api_only, :loaded_config_version + attr_reader :encoding, :api_only, :loaded_config_version, :autoloader def initialize(*) super @@ -64,6 +65,9 @@ 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 + @add_autoload_paths_to_load_path = true end def load_defaults(target_version) @@ -117,6 +121,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 @@ -137,6 +143,12 @@ module Rails active_storage.queues.analysis = :active_storage_analysis active_storage.queues.purge = :active_storage_purge end + + if respond_to?(:active_record) + active_record.collection_cache_versioning = true + end + when "6.1" + load_defaults "6.0" else raise "Unknown version #{target_version.to_s.inspect}" end @@ -181,6 +193,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 @@ -267,6 +299,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 diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index 193cc59f3a..9800b19274 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -49,6 +49,7 @@ module Rails middleware.use ::Rails::Rack::Logger, config.log_tags middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format + middleware.use ::ActionDispatch::ActionableExceptions unless config.cache_classes middleware.use ::ActionDispatch::Reloader, app.reloader 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..109c560c80 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/array/conversions" + module Rails class Application module Finisher @@ -21,6 +24,47 @@ module Rails end end + # This will become an error if/when we remove classic mode. The plan is + # autoloaders won't be configured up to this point in the finisher, so + # constants just won't be found, raising regular NameError exceptions. + initializer :warn_if_autoloaded, before: :let_zeitwerk_take_over do + next if config.cache_classes + next if ActiveSupport::Dependencies.autoloaded_constants.empty? + + autoloaded = ActiveSupport::Dependencies.autoloaded_constants + constants = "constant".pluralize(autoloaded.size) + enum = autoloaded.to_sentence + have = autoloaded.size == 1 ? "has" : "have" + these = autoloaded.size == 1 ? "This" : "These" + example = autoloaded.first + example_klass = example.constantize.class + + ActiveSupport::DescendantsTracker.clear + ActiveSupport::Dependencies.clear + + ActiveSupport::Deprecation.warn(<<~WARNING) + Initialization autoloaded the #{constants} #{enum}. + + Being able to do this is deprecated. Autoloading during initialization is going + to be an error condition in future versions of Rails. + + Reloading does not reboot the application, and therefore code executed during + initialization does not run again. So, if you reload #{example}, for example, + the expected changes won't be reflected in that stale #{example_klass} object. + + #{these} autoloaded #{constants} #{have} been unloaded. + + Please, check the "Autoloading and Reloading Constants" guide for solutions. + WARNING + end + + initializer :let_zeitwerk_take_over do + if config.autoloader == :zeitwerk + require "active_support/dependencies/zeitwerk_integration" + ActiveSupport::Dependencies::ZeitwerkIntegration.take_over(enable_reloading: !config.cache_classes) + end + end + initializer :add_builtin_route do |app| if Rails.env.development? app.routes.prepend do @@ -66,6 +110,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/command/behavior.rb b/railties/lib/rails/command/behavior.rb index 7f32b04cf1..7fb2a99e67 100644 --- a/railties/lib/rails/command/behavior.rb +++ b/railties/lib/rails/command/behavior.rb @@ -71,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 fdc5ee92d9..9945fd1430 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,16 +9,18 @@ module Rails extend ActiveSupport::Concern included do - class_option :environment, aliases: "-e", type: :string, - desc: "Specifies the environment to run this console under (test/development/production)." + no_commands do + class_attribute :environment_desc, default: "Specifies the environment to run this #{self.command_name} under (test/development/production)." + end + class_option :environment, aliases: "-e", type: :string, desc: environment_desc end private - def extract_environment_option_from_argument + 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 f7268a64d1..c8d3fb9eda 100644 --- a/railties/lib/rails/commands/credentials/USAGE +++ b/railties/lib/rails/commands/credentials/USAGE @@ -42,7 +42,7 @@ from leaking. === Environment Specific Credentials The `credentials` command supports passing an `--environment` option to create an -environment specific override. That override will takes precedence over the +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 diff --git a/railties/lib/rails/commands/credentials/credentials_command.rb b/railties/lib/rails/commands/credentials/credentials_command.rb index 54ccd97506..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,6 +21,7 @@ module Rails end def edit + extract_environment_option_from_argument(default_environment: nil) require_application! ensure_editor_available(command: "bin/rails credentials:edit") || (return) @@ -37,6 +39,7 @@ module Rails end def show + extract_environment_option_from_argument(default_environment: nil) require_application! say credentials.read.presence || missing_credentials_message @@ -53,7 +56,11 @@ module Rails end def ensure_credentials_have_been_added - encrypted_file_generator.add_encrypted_file_silently(content_path, key_path) + 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_credentials_in_system_editor @@ -93,6 +100,13 @@ module Rails Rails::Generators::EncryptedFileGenerator.new end + + def credentials_generator + require "rails/generators" + require "rails/generators/rails/credentials/credentials_generator" + + Rails::Generators::CredentialsGenerator.new + 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 0fac7d34a0..72f3235ce3 100644 --- a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb +++ b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "active_support/deprecation" +require "active_support/core_ext/string/filters" require "rails/command/environment_argument" module Rails @@ -89,15 +91,15 @@ module Rails def config @config ||= begin - # We need to check whether the user passed the connection the + # We need to check whether the user passed the database the # first time around to show a consistent error message to people # relying on 2-level database configuration. - if @options["connection"] && configurations[connection].blank? - raise ActiveRecord::AdapterNotSpecified, "'#{connection}' connection is not configured. Available configuration: #{configurations.inspect}" - elsif configurations[environment].blank? && configurations[connection].blank? + if @options["database"] && configurations[database].blank? + raise ActiveRecord::AdapterNotSpecified, "'#{database}' database is not configured. Available configuration: #{configurations.inspect}" + elsif configurations[environment].blank? && configurations[database].blank? raise ActiveRecord::AdapterNotSpecified, "'#{environment}' database is not configured. Available configuration: #{configurations.inspect}" else - configurations[connection] || configurations[environment].presence + configurations[database] || configurations[environment].presence end end end @@ -106,8 +108,8 @@ module Rails Rails.respond_to?(:env) ? Rails.env : Rails::Command.environment end - def connection - @options.fetch(:connection, "primary") + def database + @options.fetch(:database, "primary") end private @@ -156,12 +158,22 @@ module Rails class_option :connection, aliases: "-c", type: :string, desc: "Specifies the connection to use." + class_option :database, aliases: "--db", type: :string, + desc: "Specifies the database to use." + def perform extract_environment_option_from_argument # RAILS_ENV needs to be set before config/application is required. ENV["RAILS_ENV"] = options[:environment] + if options["connection"] + ActiveSupport::Deprecation.warn(<<-MSG.squish) + `connection` option is deprecated and will be removed in Rails 6.1. Please use `database` option instead. + MSG + options["database"] = options["connection"] + end + require_application_and_environment! Rails::DBConsole.start(options) end diff --git a/railties/lib/rails/commands/dev/dev_command.rb b/railties/lib/rails/commands/dev/dev_command.rb index a3f02f3172..9b2cb2b04a 100644 --- a/railties/lib/rails/commands/dev/dev_command.rb +++ b/railties/lib/rails/commands/dev/dev_command.rb @@ -5,8 +5,10 @@ require "rails/dev_caching" module Rails module Command class DevCommand < Base # :nodoc: - def help - say "rails dev:cache # Toggle development mode caching on/off." + no_commands do + def help + say "rails dev:cache # Toggle development mode caching on/off." + end end def cache 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/notes/notes_command.rb b/railties/lib/rails/commands/notes/notes_command.rb index 64b339b3cd..94cf183855 100644 --- a/railties/lib/rails/commands/notes/notes_command.rb +++ b/railties/lib/rails/commands/notes/notes_command.rb @@ -5,7 +5,7 @@ require "rails/source_annotation_extractor" module Rails module Command class NotesCommand < Base # :nodoc: - class_option :annotations, aliases: "-a", desc: "Filter by specific annotations, e.g. Foobar TODO", type: :array, default: %w(OPTIMIZE FIXME TODO) + class_option :annotations, aliases: "-a", desc: "Filter by specific annotations, e.g. Foobar TODO", type: :array, default: Rails::SourceAnnotationExtractor::Annotation.tags def perform(*) require_application_and_environment! 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 47c3f05bb3..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 @@ -91,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) @@ -109,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, @@ -130,6 +131,7 @@ module Rails end def perform + extract_environment_option_from_argument set_application_directory! prepare_restart @@ -221,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"] @@ -255,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 @@ -264,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. diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index d6c329b581..d1b8c7803f 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -230,7 +230,7 @@ module Rails # # If +MyEngine+ is isolated, The routes above will point to # <tt>MyEngine::ArticlesController</tt>. You also don't need to use longer - # url helpers like +my_engine_articles_path+. Instead, you should simply use + # URL helpers like +my_engine_articles_path+. Instead, you should simply use # +articles_path+, like you would do with your main application. # # To make this behavior consistent with other parts of the framework, @@ -238,7 +238,7 @@ module Rails # normal Rails app, when you use a namespaced model such as # <tt>Namespace::Article</tt>, <tt>ActiveModel::Naming</tt> will generate # names with the prefix "namespace". In an isolated engine, the prefix will - # be omitted in url helpers and form fields, for convenience. + # be omitted in URL helpers and form fields, for convenience. # # polymorphic_url(MyEngine::Article.new) # # => "articles_path" # not "my_engine_articles_path" @@ -286,11 +286,11 @@ module Rails # Note that the <tt>:as</tt> option given to mount takes the <tt>engine_name</tt> as default, so most of the time # you can simply omit it. # - # Finally, if you want to generate a url to an engine's route using + # Finally, if you want to generate a URL to an engine's route using # <tt>polymorphic_url</tt>, you also need to pass the engine helper. Let's # say that you want to create a form pointing to one of the engine's routes. # All you need to do is pass the helper as the first element in array with - # attributes for url: + # attributes for URL: # # form_for([my_engine, @user]) # @@ -469,9 +469,10 @@ module Rails self end - # Eager load the application by loading all ruby - # files inside eager_load paths. def eager_load! + # Already done by Zeitwerk::Loader.eager_load_all in the finisher. + return if Rails.autoloaders.zeitwerk_enabled? + 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 @@ -532,9 +533,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 @@ -549,12 +550,17 @@ module Rails # Blog::Engine.load_seed def load_seed seed_file = paths["db/seeds.rb"].existent.first - with_inline_jobs { load(seed_file) } if seed_file + return unless seed_file + + if config.try(:active_job)&.queue_adapter == :async + with_inline_jobs { load(seed_file) } + else + load(seed_file) + end end - # Add configured load paths to Ruby's load path, and remove duplicate entries. - initializer :set_load_path, before: :bootstrap_hook do - _all_load_paths.reverse_each do |path| + initializer :set_load_path, before: :bootstrap_hook do |app| + _all_load_paths(app.config.add_autoload_paths_to_load_path).reverse_each do |path| $LOAD_PATH.unshift(path) if File.directory?(path) end $LOAD_PATH.uniq! @@ -569,12 +575,15 @@ module Rails ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths) ActiveSupport::Dependencies.autoload_once_paths.unshift(*_all_autoload_once_paths) - # Freeze so future modifications will fail rather than do nothing mysteriously config.autoload_paths.freeze - config.eager_load_paths.freeze config.autoload_once_paths.freeze end + initializer :set_eager_load_paths, before: :bootstrap_hook do + ActiveSupport::Dependencies._eager_load_paths.merge(config.eager_load_paths) + config.eager_load_paths.freeze + end + initializer :add_routing_paths do |app| routing_paths = paths["config/routes.rb"].existent @@ -699,8 +708,12 @@ module Rails @_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq end - def _all_load_paths - @_all_load_paths ||= (config.paths.load_paths + _all_autoload_paths).uniq + def _all_load_paths(add_autoload_paths_to_load_path) + @_all_load_paths ||= begin + load_paths = config.paths.load_paths + load_paths += _all_autoload_paths if add_autoload_paths_to_load_path + load_paths.uniq + end end def build_request(env) diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb index 82df56559c..a6b3ccf8f8 100644 --- a/railties/lib/rails/gem_version.rb +++ b/railties/lib/rails/gem_version.rb @@ -8,9 +8,9 @@ module Rails module VERSION MAJOR = 6 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta1" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index b835b3f3fd..0be00d5151 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -35,6 +35,8 @@ module Rails rails: { actions: "-a", orm: "-o", + javascripts: "-j", + javascript_engine: "-je", resource_controller: "-c", scaffold_controller: "-c", stylesheets: "-y", diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 0eed552042..8782a85391 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -307,7 +307,7 @@ module Rails def assets_gemfile_entry return [] if options[:skip_sprockets] - GemfileEntry.version("sass-rails", "~> 5.0", "Use SCSS for stylesheets") + GemfileEntry.version("sass-rails", ">= 5", "Use SCSS for stylesheets") end def webpacker_gemfile_entry @@ -316,7 +316,7 @@ module Rails if options.dev? || options.edge? GemfileEntry.github "webpacker", "rails/webpacker", nil, "Use development version of Webpacker" else - GemfileEntry.version "webpacker", ">= 4.0.0.rc.3", "Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker" + GemfileEntry.version "webpacker", "~> 4.0", "Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker" end end diff --git a/railties/lib/rails/generators/app_name.rb b/railties/lib/rails/generators/app_name.rb index c4f71694d8..5bb735c4e8 100644 --- a/railties/lib/rails/generators/app_name.rb +++ b/railties/lib/rails/generators/app_name.rb @@ -7,11 +7,11 @@ module Rails private def app_name - @app_name ||= original_app_name.tr("-", "_") + @app_name ||= original_app_name.tr('\\', "").tr("-. ", "_") end def original_app_name - @original_app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr('\\', "").tr(". ", "_") + @original_app_name ||= defined_app_const_base? ? defined_app_name : File.basename(destination_root) end def defined_app_name diff --git a/railties/lib/rails/generators/database.rb b/railties/lib/rails/generators/database.rb index 18fc7be3ff..cc6e7b50e5 100644 --- a/railties/lib/rails/generators/database.rb +++ b/railties/lib/rails/generators/database.rb @@ -15,7 +15,7 @@ module Rails 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 "sqlite3" then ["sqlite3", ["~> 1.4"]] when "oracle" then ["activerecord-oracle_enhanced-adapter", nil] when "frontbase" then ["ruby-frontbase", nil] when "sqlserver" then ["activerecord-sqlserver-adapter", nil] 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..1dddc3d698 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 %> @@ -21,6 +21,9 @@ <div class="field"> <%%= form.label :password_confirmation %> <%%= form.password_field :password_confirmation %> +<% elsif attribute.attachments? -%> + <%%= form.label :<%= attribute.column_name %> %> + <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, multiple: true %> <% else -%> <%%= form.label :<%= attribute.column_name %> %> <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %> %> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt index 7deba07926..d3f996188c 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt +++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt @@ -3,7 +3,15 @@ <% attributes.reject(&:password_digest?).each do |attribute| -%> <p> <strong><%= attribute.human_name %>:</strong> +<% if attribute.attachment? -%> + <%%= link_to @<%= singular_table_name %>.<%= attribute.column_name %>.filename, @<%= singular_table_name %>.<%= attribute.column_name %> if @<%= singular_table_name %>.<%= attribute.column_name %>.attached? %> +<% elsif attribute.attachments? -%> + <%% @<%= singular_table_name %>.<%= attribute.column_name %>.each do |<%= attribute.singular_name %>| %> + <div><%%= link_to <%= attribute.singular_name %>.filename, <%= attribute.singular_name %> %></div> + <%% end %> +<% else -%> <%%= @<%= singular_table_name %>.<%= attribute.column_name %> %> +<% end -%> </p> <% end -%> diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index a8f7729fd3..1a80e71eae 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "active_support/time" +require "active_support/deprecation" module Rails module Generators @@ -51,6 +52,12 @@ module Rails type = $1 provided_options = $2.split(/[,.-]/) options = Hash[provided_options.map { |opt| [opt.to_sym, true] }] + + if options[:required] + ActiveSupport::Deprecation.warn("Passing {required} option has no effect on the model generator. It will be removed in Rails 6.1.\n") + options.delete(:required) + end + return type, options else return type, {} @@ -68,13 +75,15 @@ module Rails def field_type @field_type ||= case type - when :integer then :number_field - when :float, :decimal then :text_field - when :time then :time_select - when :datetime, :timestamp then :datetime_select - when :date then :date_select - when :text then :text_area - when :boolean then :check_box + when :integer then :number_field + when :float, :decimal then :text_field + when :time then :time_select + when :datetime, :timestamp then :datetime_select + when :date then :date_select + when :text then :text_area + when :rich_text then :rich_text_area + when :boolean then :check_box + when :attachment, :attachments then :file_field else :text_field end @@ -90,7 +99,9 @@ module Rails when :string then name == "type" ? "" : "MyString" when :text then "MyText" when :boolean then false - when :references, :belongs_to then nil + when :references, :belongs_to, + :attachment, :attachments, + :rich_text then nil else "" end @@ -133,7 +144,7 @@ module Rails end def required? - attr_options[:required] + reference? && Rails.application.config.active_record.belongs_to_required_by_default end def has_index? @@ -152,6 +163,22 @@ module Rails type == :token end + def rich_text? + type == :rich_text + end + + def attachment? + type == :attachment + end + + def attachments? + type == :attachments + end + + def virtual? + rich_text? || attachment? || attachments? + end + def inject_options (+"").tap { |s| options_for_migration.each { |k, v| s << ", #{k}: #{v.inspect}" } } end @@ -163,7 +190,6 @@ module Rails def options_for_migration @attr_options.dup.tap do |options| if required? - options.delete(:required) options[:null] = false end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt index d39b5d311f..cf5462f7dc 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt @@ -28,7 +28,7 @@ ruby <%= "'#{RUBY_VERSION}'" -%> <% if depend_on_bootsnap? -%> # Reduces boot times through caching; required in config/boot.rb -gem 'bootsnap', '>= 1.1.0', require: false +gem 'bootsnap', '>= 1.4.4', require: false <%- end -%> <%- if options.api? -%> @@ -69,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/packs/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt index 908487d500..e67e742263 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/javascript/packs/application.js.tt @@ -13,3 +13,11 @@ require("@rails/activestorage").start() <%- unless options[:skip_action_cable] -%> require("channels") <%- end -%> + + +// Uncomment to copy all static images under ../images to the output folder and reference +// them with the image_pack_tag helper in views (e.g <%%= image_pack_tag 'rails.png' %>) +// or the `imagePath` JavaScript helper below. +// +// const images = require.context('../images', true) +// const imagePath = (name) => images(name, true) diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt index 3f73bae3da..5928deb6aa 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt +++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt @@ -8,7 +8,8 @@ def system!(*args) end FileUtils.chdir APP_ROOT do - # This script is a starting point to setup your application. + # This script is a way to setup or update your development environment automatically. + # This script is idempotent, so that you can run it at anytime and get an expectable outcome. # Add necessary setup steps to this file. puts '== Installing dependencies ==' @@ -27,7 +28,7 @@ FileUtils.chdir APP_ROOT do # end puts "\n== Preparing database ==" - system! 'bin/rails db:setup' + system! 'bin/rails db:prepare' <% end -%> puts "\n== Removing old logs and tempfiles ==" diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update.tt b/railties/lib/rails/generators/rails/app/templates/bin/update.tt deleted file mode 100644 index 03b77d0d46..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/bin/update.tt +++ /dev/null @@ -1,33 +0,0 @@ -require 'fileutils' - -# path to your application root. -APP_ROOT = File.expand_path('..', __dir__) - -def system!(*args) - system(*args) || abort("\n== Command #{args} failed ==") -end - -FileUtils.chdir APP_ROOT do - # This script is a way to update your development environment automatically. - # Add necessary update steps to this file. - - puts '== Installing dependencies ==' - system! 'gem install bundler --conservative' - system('bundle check') || system!('bundle install') -<% unless options.skip_javascript? -%> - - # Install JavaScript dependencies - # system('bin/yarn') -<% end -%> -<% unless options.skip_active_record? -%> - - puts "\n== Updating database ==" - system! 'rails db:migrate' -<% end -%> - - puts "\n== Removing old logs and tempfiles ==" - system! 'rails log:clear tmp:clear' - - puts "\n== Restarting application server ==" - system! 'rails restart' -end 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 2887bc2d67..404ab8b5de 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 @@ -15,9 +15,11 @@ Rails.application.configure do # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. if Rails.root.join('tmp', 'caching-dev.txt').exist? + <%- unless options.api? -%> config.action_controller.perform_caching = true config.action_controller.enable_fragment_cache_logging = true + <%- end -%> config.cache_store = :memory_store config.public_file_server.headers = { 'Cache-Control' => "public, max-age=#{2.days.to_i}" 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 ed1cf09eeb..08d437a0be 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 @@ -12,7 +12,9 @@ Rails.application.configure do # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false + <%- unless options.api? -%> config.action_controller.perform_caching = true + <%- end -%> # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 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 63ed3fa952..c66e349442 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 @@ -1,11 +1,16 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. - - # The test environment is used exclusively to run your application's - # test suite. You never need to work with it otherwise. Remember that - # your test database is "scratch space" for the test suite and is wiped - # and recreated between test runs. Don't rely on the data there! + <%# Spring executes the reloaders when files change. %> + <%- if spring_install? -%> + config.cache_classes = false + <%- else -%> config.cache_classes = true + <%- end -%> # Do not eager load code on boot. This avoids loading your whole application # just for the purpose of running a single test. If you are using a tool that 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 bac1339923..096cfd36a8 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 @@ -<%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" -%> +<%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %> diff --git a/railties/lib/rails/generators/rails/assets/assets_generator.rb b/railties/lib/rails/generators/rails/assets/assets_generator.rb index 9ce8570172..e60637ff37 100644 --- a/railties/lib/rails/generators/rails/assets/assets_generator.rb +++ b/railties/lib/rails/generators/rails/assets/assets_generator.rb @@ -3,7 +3,10 @@ module Rails module Generators class AssetsGenerator < NamedBase # :nodoc: + class_option :javascripts, type: :boolean, desc: "Generate JavaScripts" class_option :stylesheets, type: :boolean, desc: "Generate Stylesheets" + + class_option :javascript_engine, desc: "Engine for JavaScripts" class_option :stylesheet_engine, desc: "Engine for Stylesheets" private @@ -11,6 +14,10 @@ module Rails file_name end + hook_for :javascript_engine do |javascript_engine| + invoke javascript_engine, [name] if options[:javascripts] + end + hook_for :stylesheet_engine do |stylesheet_engine| invoke stylesheet_engine, [name] if options[:stylesheets] end diff --git a/railties/lib/rails/generators/rails/db/system/change/change_generator.rb b/railties/lib/rails/generators/rails/db/system/change/change_generator.rb index 63849eb18d..24db92fad7 100644 --- a/railties/lib/rails/generators/rails/db/system/change/change_generator.rb +++ b/railties/lib/rails/generators/rails/db/system/change/change_generator.rb @@ -35,8 +35,9 @@ module Rails end def edit_gemfile - database_gem_name, _ = gem_for_database - gsub_file("Gemfile", all_database_gems_regex, database_gem_name) + name, version = gem_for_database + gsub_file("Gemfile", all_database_gems_regex, name) + gsub_file("Gemfile", gem_entry_regex_for(name), gem_entry_for(name, *version)) end private @@ -48,6 +49,15 @@ module Rails all_database_gem_names = all_database_gems.map(&:first) /(\b#{all_database_gem_names.join('\b|\b')}\b)/ end + + def gem_entry_regex_for(gem_name) + /^gem.*\b#{gem_name}\b.*/ + end + + def gem_entry_for(*gem_name_and_version) + gem_name_and_version.map! { |segment| "'#{segment}'" } + "gem #{gem_name_and_version.join(", ")}" + end end end end diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index 79a06648b5..895b3b2e92 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -144,17 +144,6 @@ task default: :test end end - def javascripts - return if options.skip_javascript? - - if mountable? - template "rails/javascripts.js", - "app/assets/javascripts/#{namespaced_name}/application.js" - elsif full? - empty_directory_with_keep_file "app/assets/javascripts/#{namespaced_name}" - end - end - def bin(force = false) bin_file = engine? ? "bin/rails.tt" : "bin/test.tt" template bin_file, force: force do |content| @@ -236,10 +225,6 @@ task default: :test build(:stylesheets) unless api? end - def create_javascript_files - build(:javascripts) unless api? - end - def create_bin_files build(:bin) end diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb index 7030561a33..e1ca54ec91 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb @@ -32,6 +32,20 @@ module Rails hook_for :helper, as: :scaffold do |invoked| invoke invoked, [ controller_name ] end + + private + + def permitted_params + attachments, others = attributes_names.partition { |name| attachments?(name) } + params = others.map { |name| ":#{name}" } + params += attachments.map { |name| "#{name}: []" } + params.join(", ") + end + + def attachments?(name) + attribute = attributes.find { |attr| attr.name == name } + attribute&.attachments? + end end end end diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb.tt b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb.tt index 400afec6dc..bb26370276 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb.tt +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb.tt @@ -54,7 +54,7 @@ class <%= controller_class_name %>Controller < ApplicationController <%- if attributes_names.empty? -%> params.fetch(:<%= singular_table_name %>, {}) <%- else -%> - params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>) + params.require(:<%= singular_table_name %>).permit(<%= permitted_params %>) <%- end -%> end end diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt index 05f1c2b2d3..82b43987b4 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt @@ -61,7 +61,7 @@ class <%= controller_class_name %>Controller < ApplicationController <%- if attributes_names.empty? -%> params.fetch(:<%= singular_table_name %>, {}) <%- else -%> - params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>) + params.require(:<%= singular_table_name %>).permit(<%= permitted_params %>) <%- 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..0fd9f305d7 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 %>: @@ -7,7 +7,7 @@ password_digest: <%%= BCrypt::Password.create('secret') %> <%- elsif attribute.reference? -%> <%= yaml_key_value(attribute.column_name.sub(/_id$/, ''), attribute.default || name) %> - <%- else -%> + <%- elsif !attribute.virtual? -%> <%= yaml_key_value(attribute.column_name, attribute.default) %> <%- end -%> <%- if attribute.polymorphic? -%> 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 6df50c3217..26002a0704 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -49,16 +49,21 @@ module TestUnit # :nodoc: attributes_names.map do |name| if %w(password password_confirmation).include?(name) && attributes.any?(&:password_digest?) ["#{name}", "'secret'"] - else + elsif !virtual?(name) ["#{name}", "@#{singular_table_name}.#{name}"] end - end.sort.to_h + end.compact.sort.to_h end def boolean?(name) attribute = attributes.find { |attr| attr.name == name } attribute&.type == :boolean end + + def virtual?(name) + attribute = attributes.find { |attr| attr.name == name } + attribute&.virtual? + end end end end diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb index 95dae3ec2d..4a1942790b 100644 --- a/railties/lib/rails/mailers_controller.rb +++ b/railties/lib/rails/mailers_controller.rb @@ -5,8 +5,9 @@ require "rails/application_controller" class Rails::MailersController < Rails::ApplicationController # :nodoc: prepend_view_path ActionDispatch::DebugView::RESCUES_TEMPLATE_PATH + around_action :set_locale, only: :preview + before_action :find_preview, only: :preview before_action :require_local!, unless: :show_previews? - before_action :find_preview, :set_locale, only: :preview helper_method :part_query, :locale_query @@ -38,7 +39,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}" @@ -92,6 +93,8 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: end def set_locale - I18n.locale = params[:locale] || I18n.default_locale + I18n.with_locale(params[:locale] || I18n.default_locale) do + yield + end end end diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index d7170e6282..9ce22b96a6 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -29,6 +29,16 @@ module Rails directories.push(*dirs) end + def self.tags + @@tags ||= %w(OPTIMIZE FIXME TODO) + end + + # Registers additional tags + # Rails::SourceAnnotationExtractor::Annotation.register_tags("TESTME", "DEPRECATEME") + def self.register_tags(*additional_tags) + tags.push(*additional_tags) + end + def self.extensions @@extensions ||= {} end @@ -66,6 +76,8 @@ module Rails # Prints all annotations with tag +tag+ under the root directories +app+, # +config+, +db+, +lib+, and +test+ (recursively). # + # If +tag+ is <tt>nil</tt>, annotations with either default or registered tags are printed. + # # Specific directories can be explicitly set using the <tt>:dirs</tt> key in +options+. # # Rails::SourceAnnotationExtractor.enumerate 'TODO|FIXME', dirs: %w(app lib), tag: true @@ -75,7 +87,8 @@ module Rails # 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 `rails notes` command. - def self.enumerate(tag, options = {}) + def self.enumerate(tag = nil, options = {}) + tag ||= Annotation.tags.join("|") extractor = new(tag) dirs = options.delete(:dirs) || Annotation.directories extractor.display(extractor.find(dirs), options) diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb index 2f644a20c9..0e592e15c3 100644 --- a/railties/lib/rails/tasks.rb +++ b/railties/lib/rails/tasks.rb @@ -15,6 +15,7 @@ require "rake" routes tmp yarn + zeitwerk ).tap { |arr| arr << "statistics" if Rake.application.current_scope.empty? }.each do |task| diff --git a/railties/lib/rails/tasks/zeitwerk.rake b/railties/lib/rails/tasks/zeitwerk.rake new file mode 100644 index 0000000000..b7f5cd154b --- /dev/null +++ b/railties/lib/rails/tasks/zeitwerk.rake @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +ensure_classic_mode = ->() do + if Rails.autoloaders.zeitwerk_enabled? + abort <<~EOS + Please, enable temporarily :classic mode: + + # config/application.rb + config.autoloader = :classic + + and try again. When all is good, you can delete that line. + EOS + end +end + +eager_load = ->() do + Rails.configuration.eager_load_namespaces.each(&:eager_load!) +end + +mismatches = [] +check_directory = ->(directory, parent) do + # test/mailers/previews might not exist. + return unless File.exist?(directory) + + Dir.foreach(directory) do |entry| + next if entry.start_with?(".") + next if parent == Object && entry == "concerns" + + abspath = File.join(directory, entry) + + if File.directory?(abspath) || abspath.end_with?(".rb") + print "." + cname = File.basename(abspath, ".rb").camelize.to_sym + if parent.const_defined?(cname, false) + if File.directory?(abspath) + check_directory[abspath, parent.const_get(cname)] + end + else + mismatches << [abspath, parent, cname] + end + end + end +end + +report = ->() do + puts + if mismatches.empty? + puts "All is good!" + puts "Please, remember to delete `config.autoloader = :classic` from config/application.rb." + else + mismatches.each do |abspath, parent, cname| + relpath = abspath.sub(%r{\A#{Regexp.escape(Rails.root.to_path)}/}, "") + cpath = parent == Object ? cname : "#{parent.name}::#{cname}" + puts "expected #{relpath} to define #{cpath}" + end + puts + puts <<~EOS + Please revise the reported mismatches. You can normally fix them by adding + acronyms to config/initializers/inflections.rb or renaming the constants. + EOS + end +end + +namespace :zeitwerk do + desc "Checks project structure for Zeitwerk compatibility" + task check: :environment do + ensure_classic_mode[] + eager_load[] + + $stdout.sync = true + ActiveSupport::Dependencies.autoload_paths.each do |autoload_path| + check_directory[autoload_path, Object] + end + puts + + report[] + end +end |