Skip to content

Commit

Permalink
Add active_record.postgresql_adapter_decode_dates
Browse files Browse the repository at this point in the history
to toggle automatic decoding of dates column with the
PostgresqlAdapter.

PR rails#51483 is a breaking change and should have been gated behind a
config.
  • Loading branch information
JoeDupuis committed May 8, 2024
1 parent dcfabdb commit b8c3f24
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 8 deletions.
4 changes: 4 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,7 @@
* Add `Rails.application.config.active_record.postgresql_adapter_decode_dates` to opt out of decoding dates automatically with the postgresql adapter. Defaults to true.

*Joé Dupuis*

* Add ENV["SKIP_TEST_DATABASE_TRUNCATE"] flag to speed up multi-process test runs on large DBs when all tests run within default txn. (This cuts ~10s from the test run of HEY when run by 24 processes against the 178 tables, since ~4,000 table truncates can then be skipped.)

*DHH*
Expand Down
Expand Up @@ -122,6 +122,15 @@ def dbconsole(config, options = {})
# setting, you should immediately run <tt>bin/rails db:migrate</tt> to update the types in your schema.rb.
class_attribute :datetime_type, default: :timestamp

##
# :singleton-method:
# Toggles automatic decoding of date columns.
#
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date").class #=> String
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.decode_dates = true
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date").class #=> Date
class_attribute :decode_dates, default: false

NATIVE_DATABASE_TYPES = {
primary_key: "bigserial primary key",
string: { name: "character varying" },
Expand Down Expand Up @@ -1159,8 +1168,8 @@ def add_pg_decoders
"bool" => PG::TextDecoder::Boolean,
"timestamp" => PG::TextDecoder::TimestampUtc,
"timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
"date" => PG::TextDecoder::Date,
}
coders_by_name["date"] = PG::TextDecoder::Date if decode_dates

known_coder_types = coders_by_name.keys.map { |n| quote(n) }
query = <<~SQL % known_coder_types.join(", ")
Expand Down
13 changes: 12 additions & 1 deletion activerecord/lib/active_record/railtie.rb
Expand Up @@ -217,6 +217,16 @@ class Railtie < Rails::Railtie # :nodoc:
end
end

initializer "active_record.postgresql_adapter_decode_dates" do
config.after_initialize do
if config.active_record.postgresql_adapter_decode_dates
ActiveSupport.on_load(:active_record_postgresqladapter) do
self.decode_dates = true
end
end
end
end

initializer "active_record.set_configs" do |app|
configs = app.config.active_record

Expand Down Expand Up @@ -245,7 +255,8 @@ class Railtie < Rails::Railtie # :nodoc:
:cache_query_log_tags,
:sqlite3_adapter_strict_strings_by_default,
:check_schema_cache_dump_version,
:use_schema_cache_dump
:use_schema_cache_dump,
:postgresql_adapter_decode_dates,
)

configs.each do |k, v|
Expand Down
6 changes: 0 additions & 6 deletions activerecord/test/cases/adapters/postgresql/date_test.rb
Expand Up @@ -39,10 +39,4 @@ def test_bc_date_year_zero
topic = Topic.create!(last_read: date)
assert_equal date, Topic.find(topic.id).last_read
end

def test_date_decoder
date = ActiveRecord::Base.connection.select_value("select '2024-01-01'::date")
assert_equal Date.new(2024, 01, 01), date
assert_equal Date, date.class
end
end
Expand Up @@ -649,7 +649,25 @@ def test_does_not_raise_notice_level_warnings
end
end

def test_date_decoder
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
connection = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.new(db_config.configuration_hash)

with_postgresql_apdater_decode_dates do
date = connection.select_value("select '2024-01-01'::date")
assert_equal Date.new(2024, 01, 01), date
assert_equal Date, date.class
end
end

private
def with_postgresql_apdater_decode_dates
PostgreSQLAdapter.decode_dates = true
yield
ensure
PostgreSQLAdapter.decode_dates = false
end

def with_example_table(definition = "id serial primary key, number integer, data character varying(255)", &block)
super(@connection, "ex", definition, &block)
end
Expand Down
19 changes: 19 additions & 0 deletions guides/source/configuring.md
Expand Up @@ -62,6 +62,7 @@ Below are the default values associated with each target version. In cases of co

- [`config.active_job.enqueue_after_transaction_commit`](#config-active-job-enqueue-after-transaction-commit): `:default`
- [`config.active_record.automatically_invert_plural_associations`](#config-active-record-automatically-invert-plural-associations): `true`
- [`config.active_record.postgresql_adapter_decode_dates`](#config-active-record-postgresql-adapter-decode-dates): `true`
- [`config.active_record.validate_migration_timestamps`](#config-active-record-validate-migration-timestamps): `true`
- [`config.active_storage.web_image_content_types`](#config-active-storage-web-image-content-types): `%w[image/png image/jpeg image/gif image/webp]`

Expand Down Expand Up @@ -1490,6 +1491,24 @@ The default value depends on the `config.load_defaults` target version:
| (original) | `false` |
| 7.1 | `true` |

#### `config.active_record.postgresql_adapter_decode_dates`

Specifies whether the PostgresqlAdapter should decode date columns.

```ruby
ActiveRecord::Base.connection
.select_value("select '2024-01-01'::date").class #=> Date
```


The default value depends on the `config.load_defaults` target version:

| Starting with version | The default value is |
| --------------------- | -------------------- |
| (original) | `false` |
| 7.2 | `true` |


#### `config.active_record.async_query_executor`

Specifies how asynchronous queries are pooled.
Expand Down
11 changes: 11 additions & 0 deletions guides/source/upgrading_ruby_on_rails.md
Expand Up @@ -81,6 +81,17 @@ Upgrading from Rails 7.1 to Rails 7.2

For more information on changes made to Rails 7.2 please see the [release notes](7_2_release_notes.html).

### `PostgresqlAdapter` now decodes dates automatically

Manual queries with columns of type date will automatically be decoded to `Date` objects.
To disable this new behavior and return dates as `String`:

```ruby
# config/application.rb
config.active_record.postgresql_adapter_decode_dates = false
```


Upgrading from Rails 7.0 to Rails 7.1
-------------------------------------

Expand Down
1 change: 1 addition & 0 deletions railties/lib/rails/application/configuration.rb
Expand Up @@ -327,6 +327,7 @@ def load_defaults(target_version)
end

if respond_to?(:active_record)
active_record.postgresql_adapter_decode_dates = true
active_record.validate_migration_timestamps = true
active_record.automatically_invert_plural_associations = true
end
Expand Down
Expand Up @@ -66,3 +66,13 @@
# on `Post`. With this option enabled, it will also look for a `:comments` association.
#++
# Rails.application.config.active_record.automatically_invert_plural_associations = true

###
# Controls whether the PostgresqlAdapter should decode dates automatically with manual queries.
#
# Example:
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date") #=> Date
#
# This query used to return a `String`.
#++
# Rails.application.config.active_record.postgresql_adapter_decode_dates = true
34 changes: 34 additions & 0 deletions railties/test/application/configuration_test.rb
Expand Up @@ -2793,6 +2793,40 @@ def index
assert_equal false, ActiveRecord::Base.run_commit_callbacks_on_first_saved_instances_in_transaction
end

test "PostgresqlAdapter.decode_dates is true by default for new apps" do
app_file "config/initializers/active_record.rb", <<~RUBY
ActiveRecord::Base.establish_connection(adapter: "postgresql", database: ":memory:")
RUBY

app "development"

assert_equal true, ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.decode_dates
end

test "PostgresqlAdapter.decode_dates is false by default for upgraded apps" do
remove_from_config '.*config\.load_defaults.*\n'
app_file "config/initializers/active_record.rb", <<~RUBY
ActiveRecord::Base.establish_connection(adapter: "postgresql", database: ":memory:")
RUBY

app "development"

assert_equal false, ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.decode_dates
end

test "PostgresqlAdapter.decode_dates can be configured via config.active_record.postgresql_adapter_decode_dates" do
remove_from_config '.*config\.load_defaults.*\n'
add_to_config "config.active_record.postgresql_adapter_decode_dates = true"

app_file "config/initializers/active_record.rb", <<~RUBY
ActiveRecord::Base.establish_connection(adapter: "postgresql", database: ":memory:")
RUBY

app "development"

assert_equal true, ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.decode_dates
end

test "SQLite3Adapter.strict_strings_by_default is true by default for new apps" do
app_file "config/initializers/active_record.rb", <<~RUBY
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
Expand Down

0 comments on commit b8c3f24

Please sign in to comment.