diff options
12 files changed, 159 insertions, 13 deletions
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb index 38d5d0214e..ffc57dae84 100644 --- a/activejob/lib/active_job/arguments.rb +++ b/activejob/lib/active_job/arguments.rb @@ -26,9 +26,6 @@ module ActiveJob module Arguments extend self - # :nodoc: - PERMITTED_TYPES = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ] - # Serializes a set of arguments. Intrinsic types that can safely be # serialized without mutation are returned as-is. Arrays/Hashes are # serialized element by element. All other types are serialized using @@ -50,6 +47,8 @@ module ActiveJob private # :nodoc: + PERMITTED_TYPES = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ] + # :nodoc: GLOBALID_KEY = "_aj_globalid" # :nodoc: SYMBOL_KEYS_KEY = "_aj_symbol_keys" @@ -65,7 +64,7 @@ module ActiveJob OBJECT_SERIALIZER_KEY, OBJECT_SERIALIZER_KEY.to_sym, WITH_INDIFFERENT_ACCESS_KEY, WITH_INDIFFERENT_ACCESS_KEY.to_sym, ] - private_constant :RESERVED_KEYS, :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY + private_constant :PERMITTED_TYPES, :RESERVED_KEYS, :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY def serialize_argument(argument) case argument diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index af94d52d45..9de6b609a3 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -327,11 +327,11 @@ module ActiveModel # person.errors.added? :name, :too_long # => false # person.errors.added? :name, "is too long" # => false def added?(attribute, message = :invalid, options = {}) + message = message.call if message.respond_to?(:call) + if message.is_a? Symbol - self.details[attribute.to_sym].map { |e| e[:error] }.include? message + details[attribute.to_sym].include? normalize_detail(message, options) else - message = message.call if message.respond_to?(:call) - message = normalize_message(attribute, message, options) self[attribute].include? message end end diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index 41ff6443fe..185b5a24ae 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -228,6 +228,16 @@ class ErrorsTest < ActiveModel::TestCase assert_not person.errors.added?(:name) end + test "added? returns false when checking for an error with an incorrect or missing option" do + person = Person.new + person.errors.add :name, :too_long, count: 25 + + assert person.errors.added? :name, :too_long, count: 25 + assert_not person.errors.added? :name, :too_long, count: 24 + assert_not person.errors.added? :name, :too_long + assert_not person.errors.added? :name, "is too long" + end + test "added? returns false when checking for an error by symbol and a different error with same message is present" do I18n.backend.store_translations("en", errors: { attributes: { name: { wrong: "is wrong", used: "is wrong" } } }) person = Person.new diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index 19ff780192..2cb0a2a4df 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -39,7 +39,7 @@ module ActiveRecord end def visit_TableDefinition(o) - create_sql = +"CREATE#{' TEMPORARY' if o.temporary} TABLE " + create_sql = +"CREATE#{table_modifier_in_create(o)} TABLE " create_sql << "IF NOT EXISTS " if o.if_not_exists create_sql << "#{quote_table_name(o.name)} " @@ -121,6 +121,11 @@ module ActiveRecord sql end + # Returns any SQL string to go between CREATE and TABLE. May be nil. + def table_modifier_in_create(o) + " TEMPORARY" if o.temporary + end + def foreign_key_in_create(from_table, to_table, options) options = foreign_key_options(from_table, to_table, options) accept ForeignKeyDefinition.new(from_table, to_table, options) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb index 8e381a92cf..ceb8b40bd9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb @@ -23,6 +23,17 @@ module ActiveRecord end super end + + # Returns any SQL string to go between CREATE and TABLE. May be nil. + def table_modifier_in_create(o) + # A table cannot be both TEMPORARY and UNLOGGED, since all TEMPORARY + # tables are already UNLOGGED. + if o.temporary + " TEMPORARY" + elsif o.unlogged + " UNLOGGED" + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index 206b855a18..dc4a0bb26e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -175,6 +175,13 @@ module ActiveRecord class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods + attr_reader :unlogged + + def initialize(*) + super + @unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables + end + private def integer_like_primary_key_type(type, options) if type == :bigint || options[:limit] == 8 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index a11a786ec7..f701292be3 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -85,6 +85,18 @@ module ActiveRecord class PostgreSQLAdapter < AbstractAdapter ADAPTER_NAME = "PostgreSQL" + ## + # :singleton-method: + # PostgreSQL allows the creation of "unlogged" tables, which do not record + # data in the PostgreSQL Write-Ahead Log. This can make the tables faster, + # bug significantly increases the risk of data loss if the database + # crashes. As a result, this should not be used in production + # environments. If you would like all created tables to be unlogged you + # can add the following line to your test.rb file: + # + # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true + class_attribute :create_unlogged_tables, default: false + NATIVE_DATABASE_TYPES = { primary_key: "bigserial primary key", string: { name: "character varying" }, diff --git a/activerecord/test/cases/adapters/postgresql/create_unlogged_tables_test.rb b/activerecord/test/cases/adapters/postgresql/create_unlogged_tables_test.rb new file mode 100644 index 0000000000..a02bae1453 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/create_unlogged_tables_test.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require "cases/helper" +require "support/schema_dumping_helper" + +class UnloggedTablesTest < ActiveRecord::PostgreSQLTestCase + include SchemaDumpingHelper + + TABLE_NAME = "things" + LOGGED_FIELD = "relpersistence" + LOGGED_QUERY = "SELECT #{LOGGED_FIELD} FROM pg_class WHERE relname = '#{TABLE_NAME}'" + LOGGED = "p" + UNLOGGED = "u" + TEMPORARY = "t" + + class Thing < ActiveRecord::Base + self.table_name = TABLE_NAME + end + + def setup + @connection = ActiveRecord::Base.connection + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = false + end + + teardown do + @connection.drop_table TABLE_NAME, if_exists: true + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = false + end + + def test_logged_by_default + @connection.create_table(TABLE_NAME) do |t| + end + assert_equal @connection.execute(LOGGED_QUERY).first[LOGGED_FIELD], LOGGED + end + + def test_unlogged_in_test_environment_when_unlogged_setting_enabled + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true + + @connection.create_table(TABLE_NAME) do |t| + end + assert_equal @connection.execute(LOGGED_QUERY).first[LOGGED_FIELD], UNLOGGED + end + + def test_not_included_in_schema_dump + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true + + @connection.create_table(TABLE_NAME) do |t| + end + assert_no_match(/unlogged/i, dump_table_schema(TABLE_NAME)) + end + + def test_not_changed_in_change_table + @connection.create_table(TABLE_NAME) do |t| + end + + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true + + @connection.change_table(TABLE_NAME) do |t| + t.column :name, :string + end + assert_equal @connection.execute(LOGGED_QUERY).first[LOGGED_FIELD], LOGGED + end + + def test_gracefully_handles_temporary_tables + @connection.create_table(TABLE_NAME, temporary: true) do |t| + end + + # Temporary tables are already unlogged, though this query results in a + # different result ("t" vs. "u"). This test is really just checking that we + # didn't try to run `CREATE TEMPORARY UNLOGGED TABLE`, which would result in + # a PostgreSQL error. + assert_equal @connection.execute(LOGGED_QUERY).first[LOGGED_FIELD], TEMPORARY + end +end diff --git a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb index a520fb1303..0e4ea62113 100644 --- a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb @@ -127,6 +127,9 @@ module ActiveRecord ActiveRecord::Base.connected_to(database: { writing: "postgres://localhost/bar" }) do handler = ActiveRecord::Base.connection_handler assert_equal handler, ActiveRecord::Base.connection_handlers[:writing] + + assert_not_nil pool = handler.retrieve_connection_pool("primary") + assert_equal({ adapter: "postgresql", database: "bar", host: "localhost" }, pool.spec.config) end ensure ActiveRecord::Base.establish_connection(:arunit) @@ -136,11 +139,14 @@ module ActiveRecord def test_switching_connections_with_database_config_hash previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env" - config = { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" } + config = { adapter: "sqlite3", database: "db/readonly.sqlite3" } ActiveRecord::Base.connected_to(database: { writing: config }) do handler = ActiveRecord::Base.connection_handler assert_equal handler, ActiveRecord::Base.connection_handlers[:writing] + + assert_not_nil pool = handler.retrieve_connection_pool("primary") + assert_equal(config, pool.spec.config) end ensure ActiveRecord::Base.establish_connection(:arunit) @@ -150,11 +156,23 @@ module ActiveRecord def test_switching_connections_with_database_symbol previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env" - ActiveRecord::Base.connected_to(database: :arunit2) do + config = { + "default_env" => { + "readonly" => { adapter: "sqlite3", database: "db/readonly.sqlite3" }, + "primary" => { adapter: "sqlite3", database: "db/primary.sqlite3" } + } + } + @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config + + ActiveRecord::Base.connected_to(database: :readonly) do handler = ActiveRecord::Base.connection_handler - assert_equal handler, ActiveRecord::Base.connection_handlers[:arunit2] + assert_equal handler, ActiveRecord::Base.connection_handlers[:readonly] + + assert_not_nil pool = handler.retrieve_connection_pool("primary") + assert_equal(config["default_env"]["readonly"], pool.spec.config) end ensure + ActiveRecord::Base.configurations = @prev_configs ActiveRecord::Base.establish_connection(:arunit) ENV["RAILS_ENV"] = previous_env end diff --git a/guides/source/configuring.md b/guides/source/configuring.md index d03943ba4a..706964090e 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -379,6 +379,14 @@ The MySQL adapter adds one additional configuration option: * `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns as booleans. Defaults to `true`. +The PostgreSQL adapter adds one additional configuration option: + +* `ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables` + controls whether database tables created should be "unlogged," which can speed + up performance but adds a risk of data loss if the database crashes. It is + highly recommended that you do not enable this in a production environment. + Defaults to `false` in all environments. + The SQLite3Adapter adapter adds one additional configuration option: * `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer` diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index f56f79b8d4..33002790d4 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -258,8 +258,8 @@ module Rails class_option :skip_bundle, type: :boolean, aliases: "-B", default: false, desc: "Don't run bundle install" - class_option :webpack, type: :string, default: nil, - desc: "Preconfigure Webpack with a particular framework (options: #{WEBPACKS.join('/')})" + class_option :webpack, type: :string, aliases: "--webpacker", default: nil, + desc: "Preconfigure Webpack with a particular framework (options: #{WEBPACKS.join(", ")})" class_option :skip_webpack_install, type: :boolean, default: false, desc: "Don't run Webpack install" diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb index d02100d94c..a952d2466b 100644 --- a/railties/test/application/bin_setup_test.rb +++ b/railties/test/application/bin_setup_test.rb @@ -45,6 +45,8 @@ module ApplicationTests output.sub!(/^Resolving dependencies\.\.\.\n/, "") # Suppress Bundler platform warnings from output output.gsub!(/^The dependency .* will be unused .*\.\n/, "") + # Ignore warnings such as `Psych.safe_load is deprecated` + output.gsub!(/^warning:\s.*\n/, "") assert_equal(<<~OUTPUT, output) == Installing dependencies == |