mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
867d27dd64
In https://github.com/rails/rails/issues/21126 it was suggested to make "timestamp with time zone" the default type for datetime columns in PostgreSQL. This is in line with PostgreSQL [best practices](https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_timestamp_.28without_time_zone.29). This PR lays some groundwork for that. This PR adds a configuration option, `ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type`. The default is `:timestamp` which preserves current Rails behavior of using "timestamp without time zone" when you do `t.datetime` in a migration. If you change it to `:timestamptz`, you'll get "timestamp with time zone" columns instead. If you change this setting in an existing app, you should immediately call `bin/rails db:migrate` to ensure your `schema.rb` file remains correct. If you do so, then existing columns will not be impacted, so for example if you have an app with a mixture of both types of columns, and you change the config, schema dumps will continue to output the correct types. This PR also adds two new types that can be used in migrations: `t.timestamp` and `t.timestamptz`. ```ruby ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamp # default value is :timestamp create_table("foo1") do |t| t.datetime :default_format # "timestamp without time zone" t.timestamp :without_time_zone # "timestamp without time zone" t.timestamptz :with_time_zone # "timestamp with time zone" end ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz create_table("foo2") do |t| t.datetime :default_format # "timestamp with time zone" <-- note how this has changed! t.timestamp :without_time_zone # "timestamp without time zone" t.timestamptz :with_time_zone # "timestamp with time zone" end ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:my_custom_type] = { name: "custom_datetime_format_i_invented" } ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :my_custom_type create_table("foo3") do |t| t.datetime :default_format # "custom_datetime_format_i_invented" t.timestamp :without_time_zone # "timestamp without time zone" t.timestamptz :with_time_zone # "timestamp with time zone" end ``` **Notes** - This PR doesn't change the default `datetime` format. The default is still "timestamp without time zone". A future PR could do that, but there was enough code here just getting the config option right. - See also https://github.com/rails/rails/pull/41395 which set some groundwork (and added some tests) for this. - This reverts some of https://github.com/rails/rails/pull/15184. https://github.com/rails/rails/pull/15184 alluded to issues in XML serialization, but I couldn't find any related tests that this broke.
231 lines
7.9 KiB
Ruby
231 lines
7.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "cases/helper"
|
|
|
|
class ActiveRecordSchemaTest < ActiveRecord::TestCase
|
|
self.use_transactional_tests = false
|
|
|
|
setup do
|
|
@original_verbose = ActiveRecord::Migration.verbose
|
|
ActiveRecord::Migration.verbose = false
|
|
@connection = ActiveRecord::Base.connection
|
|
@schema_migration = @connection.schema_migration
|
|
@schema_migration.drop_table
|
|
end
|
|
|
|
teardown do
|
|
@connection.drop_table :fruits rescue nil
|
|
@connection.drop_table :nep_fruits rescue nil
|
|
@connection.drop_table :nep_schema_migrations rescue nil
|
|
@connection.drop_table :has_timestamps rescue nil
|
|
@connection.drop_table :multiple_indexes rescue nil
|
|
@schema_migration.delete_all rescue nil
|
|
ActiveRecord::Migration.verbose = @original_verbose
|
|
end
|
|
|
|
def test_has_primary_key
|
|
old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type
|
|
ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
|
|
assert_equal "version", @schema_migration.primary_key
|
|
|
|
@schema_migration.create_table
|
|
assert_difference "@schema_migration.count", 1 do
|
|
@schema_migration.create version: 12
|
|
end
|
|
ensure
|
|
@schema_migration.drop_table
|
|
ActiveRecord::Base.primary_key_prefix_type = old_primary_key_prefix_type
|
|
end
|
|
|
|
def test_schema_define
|
|
ActiveRecord::Schema.define(version: 7) do
|
|
create_table :fruits do |t|
|
|
t.column :color, :string
|
|
t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
|
|
t.column :texture, :string
|
|
t.column :flavor, :string
|
|
end
|
|
end
|
|
|
|
assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
|
|
assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" }
|
|
assert_equal 7, @connection.migration_context.current_version
|
|
end
|
|
|
|
def test_schema_define_with_table_name_prefix
|
|
old_table_name_prefix = ActiveRecord::Base.table_name_prefix
|
|
ActiveRecord::Base.table_name_prefix = "nep_"
|
|
@schema_migration.reset_table_name
|
|
ActiveRecord::InternalMetadata.reset_table_name
|
|
ActiveRecord::Schema.define(version: 7) do
|
|
create_table :fruits do |t|
|
|
t.column :color, :string
|
|
t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
|
|
t.column :texture, :string
|
|
t.column :flavor, :string
|
|
end
|
|
end
|
|
assert_equal 7, @connection.migration_context.current_version
|
|
ensure
|
|
ActiveRecord::Base.table_name_prefix = old_table_name_prefix
|
|
@schema_migration.reset_table_name
|
|
ActiveRecord::InternalMetadata.reset_table_name
|
|
end
|
|
|
|
def test_schema_raises_an_error_for_invalid_column_type
|
|
assert_raise NoMethodError do
|
|
ActiveRecord::Schema.define(version: 8) do
|
|
create_table :vegetables do |t|
|
|
t.unknown :color
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_schema_subclass
|
|
Class.new(ActiveRecord::Schema).define(version: 9) do
|
|
create_table :fruits
|
|
end
|
|
assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
|
|
end
|
|
|
|
def test_normalize_version
|
|
assert_equal "118", @schema_migration.normalize_migration_number("0000118")
|
|
assert_equal "002", @schema_migration.normalize_migration_number("2")
|
|
assert_equal "017", @schema_migration.normalize_migration_number("0017")
|
|
assert_equal "20131219224947", @schema_migration.normalize_migration_number("20131219224947")
|
|
end
|
|
|
|
def test_schema_load_with_multiple_indexes_for_column_of_different_names
|
|
ActiveRecord::Schema.define do
|
|
create_table :multiple_indexes do |t|
|
|
t.string "foo"
|
|
t.index ["foo"], name: "multiple_indexes_foo_1"
|
|
t.index ["foo"], name: "multiple_indexes_foo_2"
|
|
end
|
|
end
|
|
|
|
indexes = @connection.indexes("multiple_indexes")
|
|
|
|
assert_equal 2, indexes.length
|
|
assert_equal ["multiple_indexes_foo_1", "multiple_indexes_foo_2"], indexes.collect(&:name).sort
|
|
end
|
|
|
|
if current_adapter?(:PostgreSQLAdapter)
|
|
def test_timestamps_with_and_without_zones
|
|
ActiveRecord::Schema.define do
|
|
create_table :has_timestamps do |t|
|
|
t.datetime "default_format"
|
|
t.datetime "without_time_zone"
|
|
t.timestamp "also_without_time_zone"
|
|
t.timestamptz "with_time_zone"
|
|
end
|
|
end
|
|
|
|
assert @connection.column_exists?(:has_timestamps, :default_format, :datetime)
|
|
assert @connection.column_exists?(:has_timestamps, :without_time_zone, :datetime)
|
|
assert @connection.column_exists?(:has_timestamps, :also_without_time_zone, :datetime)
|
|
assert @connection.column_exists?(:has_timestamps, :with_time_zone, :timestamptz)
|
|
end
|
|
end
|
|
|
|
def test_timestamps_without_null_set_null_to_false_on_create_table
|
|
ActiveRecord::Schema.define do
|
|
create_table :has_timestamps do |t|
|
|
t.timestamps
|
|
end
|
|
end
|
|
|
|
assert @connection.column_exists?(:has_timestamps, :created_at, null: false)
|
|
assert @connection.column_exists?(:has_timestamps, :updated_at, null: false)
|
|
end
|
|
|
|
def test_timestamps_without_null_set_null_to_false_on_change_table
|
|
ActiveRecord::Schema.define do
|
|
create_table :has_timestamps
|
|
|
|
change_table :has_timestamps do |t|
|
|
t.timestamps default: Time.now
|
|
end
|
|
end
|
|
|
|
assert @connection.column_exists?(:has_timestamps, :created_at, null: false)
|
|
assert @connection.column_exists?(:has_timestamps, :updated_at, null: false)
|
|
end
|
|
|
|
if ActiveRecord::Base.connection.supports_bulk_alter?
|
|
def test_timestamps_without_null_set_null_to_false_on_change_table_with_bulk
|
|
ActiveRecord::Schema.define do
|
|
create_table :has_timestamps
|
|
|
|
change_table :has_timestamps, bulk: true do |t|
|
|
t.timestamps default: Time.now
|
|
end
|
|
end
|
|
|
|
assert @connection.column_exists?(:has_timestamps, :created_at, null: false)
|
|
assert @connection.column_exists?(:has_timestamps, :updated_at, null: false)
|
|
end
|
|
end
|
|
|
|
def test_timestamps_without_null_set_null_to_false_on_add_timestamps
|
|
ActiveRecord::Schema.define do
|
|
create_table :has_timestamps
|
|
add_timestamps :has_timestamps, default: Time.now
|
|
end
|
|
|
|
assert @connection.column_exists?(:has_timestamps, :created_at, null: false)
|
|
assert @connection.column_exists?(:has_timestamps, :updated_at, null: false)
|
|
end
|
|
|
|
if supports_datetime_with_precision?
|
|
def test_timestamps_sets_precision_on_create_table
|
|
ActiveRecord::Schema.define do
|
|
create_table :has_timestamps do |t|
|
|
t.timestamps
|
|
end
|
|
end
|
|
|
|
assert @connection.column_exists?(:has_timestamps, :created_at, precision: 6, null: false)
|
|
assert @connection.column_exists?(:has_timestamps, :updated_at, precision: 6, null: false)
|
|
end
|
|
|
|
def test_timestamps_sets_precision_on_change_table
|
|
ActiveRecord::Schema.define do
|
|
create_table :has_timestamps
|
|
|
|
change_table :has_timestamps do |t|
|
|
t.timestamps default: Time.now
|
|
end
|
|
end
|
|
|
|
assert @connection.column_exists?(:has_timestamps, :created_at, precision: 6, null: false)
|
|
assert @connection.column_exists?(:has_timestamps, :updated_at, precision: 6, null: false)
|
|
end
|
|
|
|
if ActiveRecord::Base.connection.supports_bulk_alter?
|
|
def test_timestamps_sets_precision_on_change_table_with_bulk
|
|
ActiveRecord::Schema.define do
|
|
create_table :has_timestamps
|
|
|
|
change_table :has_timestamps, bulk: true do |t|
|
|
t.timestamps default: Time.now
|
|
end
|
|
end
|
|
|
|
assert @connection.column_exists?(:has_timestamps, :created_at, precision: 6, null: false)
|
|
assert @connection.column_exists?(:has_timestamps, :updated_at, precision: 6, null: false)
|
|
end
|
|
end
|
|
|
|
def test_timestamps_sets_precision_on_add_timestamps
|
|
ActiveRecord::Schema.define do
|
|
create_table :has_timestamps
|
|
add_timestamps :has_timestamps, default: Time.now
|
|
end
|
|
|
|
assert @connection.column_exists?(:has_timestamps, :created_at, precision: 6, null: false)
|
|
assert @connection.column_exists?(:has_timestamps, :updated_at, precision: 6, null: false)
|
|
end
|
|
end
|
|
end
|