-
On PostgreSQL 18.4+,
disable_referential_integrityusesNOT ENFORCED/ENFORCEDinstead ofDISABLE TRIGGER ALL/ENABLE TRIGGER ALL, requiring only table ownership rather than superuser privileges. Only currentlyENFORCEDforeign keys are toggled; intentionallyNOT ENFORCEDforeign keys are left unchanged.Unlike
ENABLE TRIGGER ALL, restoringENFORCEDchecks existing rows against the constraint, so FK violations raise::ActiveRecord::InvalidForeignKeyrather thanRuntimeError. The toggle and restore run in a single transaction so a restoration failure rolls back the initialNOT ENFORCEDtoggle too — preventing originally-ENFORCEDconstraints from being left in aNOT ENFORCEDstate indistinguishable from intentional ones.Fixtures sharing FK relationships must be passed together to
FixtureSet.create_fixturesto ensure all referenced rows are present when enforcement is restored.check_all_foreign_keys_valid!skipsNOT ENFORCEDconstraints on PostgreSQL 18.4+, asVALIDATE CONSTRAINTcannot be applied to them.Unlike
SET CONSTRAINTS ALL DEFERRED(the approach attempted in rails/rails#27636 and reverted),NOT ENFORCEDalso suppresses referential actions such asON DELETE CASCADE,SET NULL, andSET DEFAULT.Yasuo Honda
-
Add
exclusion_constraint_exists?andunique_constraint_exists?helpersfatkodima
-
Add
enforced:option toadd_foreign_keyandchange_foreign_keyfor PostgreSQL 18.4+.NOT ENFORCEDforeign keys are available since PostgreSQL 18.0, butDEFERRABLEwas lost on them until 18.4 ("Fix loss of deferrability of foreign-key triggers", https://www.postgresql.org/docs/release/18.4/). Rails therefore requires PostgreSQL 18.4 or later for this feature.When
enforced: falseis passed toadd_foreign_key, the constraint is created asNOT ENFORCED, meaning PostgreSQL skips referential integrity checks during DML. PostgreSQL marksNOT ENFORCEDconstraints asNOT VALIDinternally (andVALIDATE CONSTRAINTdoes not apply to them), so the schema dumper outputs bothenforced: falseandvalidate: false.change_foreign_keytoggles the enforced status of an existing foreign key. Without this method, users would need to issue rawALTER TABLE ... ALTER CONSTRAINTSQL to disable enforcement temporarily (e.g., for bulk DML that loads the referenced and referencing tables in arbitrary order) and then re-enable it.add_foreign_key :articles, :, enforced: false # => ALTER TABLE "articles" ADD CONSTRAINT ... FOREIGN KEY ("author_id") REFERENCES "authors" ("id") NOT ENFORCED change_foreign_key :articles, :, enforced: true # => ALTER TABLE "articles" ALTER CONSTRAINT "fk_rails_..." ENFORCED change_foreign_key :articles, :, enforced: false # => ALTER TABLE "articles" ALTER CONSTRAINT "fk_rails_..." NOT ENFORCEDYasuo Honda
-
Expose
cursor,orderanduse_rangesattributes forBatchEnumeratorfatkodima
-
Fix ActiveRecord::QueryMethods#in_order_of when passing an out-of-range
IntegerTo match the behavior of the
Enumerableversion,in_order_ofnow ignores an out-of-range Integer.tejanium
-
Revert alphabetical sorting of table columns inside
schema.rb.Alphabetical sorting of table columns inside the schema creates improper production tables when using
db:prepare.Bert McCutchen
-
Deprecated ActiveRecord::ConnectionAdapters::Column#auto_populated? in favor of
auto_populated_on_insert?Rafael Mendonça França
-
Reload virtual columns on update in PostgreSQL
Automatically reload virtual columns on
updatewhen using PostgreSQL. This is done by issuing a single UPDATE query that includes a RETURNING clause.Given a
Postmodel represented by the following schema:create_table :posts do |t| t.integer :upvotes_count t.integer :downvotes_count t.virtual :total_votes_count, type: :integer, as: "upvotes_count + downvotes_count", stored: true endtotal_votes_countwill reflect the sum of upvotes and downvotes afterupdateis successfully called. Prior to this change callingreloadwould have been necessary to obtain the new value calculated by the database.post = Post.find(1) post.update(upvotes_count: 2, downvotes_count: 2) # Calling `post.reload` no longer necessary post.total_votes => 4Alex Baldwin
-
Reset the optimistic locking column when a transaction is rolled back.
Previously, when a record with optimistic locking was successfully saved inside a transaction that later rolled back, the in-memory
lock_versionwas left at the incremented value while the database row was reverted to the previous one. Saving the same instance again then raised::ActiveRecord::StaleObjectErrorbecause the WHERE clause used the incremented value that no longer existed in the database.The locking column is now restored from the snapshot taken at the start of the transaction, so retrying a save on the same record after a rollback works without an explicit
reload.Kenta Ishizaki
-
Fix handling of expressions in array syntax for
add_index.This change allows passing expressions in array syntax for
add_indexmethod. For example,add_index :users, [ "lower(email)" ]now works the same asadd_index :users, "lower(email)".# This now works properly: add_index :users, [ "lower(email)" ] # As does this: add_index :users, [ "lower(email)", :status ]Alexandre Camillo
-
Fix
strict_loadingviolations ignored when usingpluckJohnson Chan
-
Move the defaulting of
prevent_writestotruewhen using thereadingrole into the parameters of the role switching methods, and raise anArgumentErrorifprevent_writes: falseis provided with thereadingrole.Joshua Young
-
Fix incorrect callback execution order when
config.active_record.run_after_transaction_callbacks_in_order_defined = trueand usingafter_commitandafter_rollbackcallbacks withprepend: true.Joshua Young
-
Accept encryption credentials as ENV
Taking advantage of Rails.apps.creds (#56455), the
primary_key,deterministic_keyandkey_derivation_saltrequired by ActiveRecord::Encryption can now also be provided through environment variables namedACTIVE_RECORD_ENCRYPTION__PRIMARY_KEY,ACTIVE_RECORD_ENCRYPTION__DETERMINISTIC_KEY,ACTIVE_RECORD_ENCRYPTION__KEY_DERIVATION_SALT.Claudio Baccigalupo
-
Treat
nilvalues as""during multi-parameter attribute assignmenttopic = Topic.where.not(last_read: nil).first topic.attributes = { "last_read(1i)" => nil, "last_read(2i)" => nil, "last_read(3i)" => nil } topic.last_read # => nilSean Doyle
-
Include record ID in error when uniqueness validation fails
When a uniqueness validation fails, the
errors.detailshash for the attribute now includes an:existing_idkey, holding the ID of the record that caused the conflict.# Before errors.details[:name] # => [{error: :taken, value: "John Doe"}] # After errors.details[:name] # => [{error: :taken, value: "John Doe", existing_id: 123}]Bruno Vicenzo
-
Bump the minimum PostgreSQL version to 10.0.
As part of this change,
supports_pgcrypto_uuid?is deprecated becausepgcryptoprovidesgen_random_uuid()since PostgreSQL 9.4, which is below the new 10.0 minimum.Yasuo Honda
-
Let
add_columnraiseArgumentErrorif:nullis set to a true value when defining a primary key.Primary keys get a
NOT NULLconstraint unconditionally. In particular,null: truewas being ignored, thus not doing what the user specified. We should rather raise to let the user know the call is invalid.Xavier Noria
-
Deprecate the
schema_orderoption in PostgreSQL database configurations.Use
schema_search_pathinstead. Theschema_orderalias will be removed in Rails 8.3.Eileen M. Uchitelle
-
Deprecate the
strictoption in MySQL database configurations.The
strictoption for MySQL will be removed in Rails 8.3 because it is the default behavior.To change the default behavior of
strict, usevariables: { sql_mode: "..." }to configuresql_modedirectly.strict: falsecan be replaced withvariables: { sql_mode: "" }, andstrict: :defaultcan be replaced withvariables: { sql_mode: :default }.Eileen M. Uchitelle
-
Allow configuring
SETqueriers for the PostgreSQL and MySQL adapters.Individual settings can be skipped by setting them to
falseindatabase.yml, which is useful when connecting through a load balancer or proxy that handles configuration:PostgreSQL example:
production: adapter: postgresql standard_conforming_strings: false intervalstyle: false min_messages: false schema_search_path: falseMySQL example:
production: adapter: mysql2 wait_timeout: false variables: sql_mode: falseAlso deprecates
set_standard_conforming_strings— it is now handled automatically through the consolidated settings hash.Eileen M. Uchitelle, Matthew Draper
-
MySQL error 1046 (
ER_NO_DB_ERROR: No database selected) is now retryable as aConnectionFailedexceptionClay Harmon
-
Batch SQL statements when creating tables to improve performance.
Andrew Novoselac
-
Support PostgreSQL
RESETon readonly queries.ActiveRecord::Base.connected_to(role: :reading, prevent_writes: true) do ActiveRecord::Base.with_connection do |c| c.execute("SET statement_timeout = '7s'") # some queries c.execute("RESET statement_timeout") # => no longer raises ActiveRecord::ReadOnlyError end endFrancesco Rodriguez
-
Add MySQL
lock:option foradd_index,remove_index, and ALTER TABLE column operations (add_column,remove_column,change_column,rename_column).Also extend
algorithm:option support to ALTER TABLE column operations on MySQL.MySQL supports
ALGORITHM = {DEFAULT|COPY|INPLACE|INSTANT}andLOCK = {DEFAULT|NONE|SHARED|EXCLUSIVE}to control how DDL operations are performed, enabling online schema changes without blocking reads or writes.add_index :users, :email, algorithm: :inplace, lock: :none remove_index :users, :email, algorithm: :inplace, lock: :none add_column :users, :name, :string, algorithm: :instant, lock: :none change_column :users, :name, :string, null: false, algorithm: :inplace, lock: :none remove_column :users, :name, algorithm: :inplace, lock: :none rename_column :users, :name, :full_name, algorithm: :inplace, lock: :noneDominik Darnel
-
Avoid issuing a
ROLLBACKstatement followingTransactionRollbackErrorduringCOMMIT.This prevents the unnecessary "WARNING: there is no transaction in progress" log spilled to stderr directly from libpq.
Sorah Fukumori
-
Add
implicit_persistence_transactionhook for customizing transaction behavior.A new protected method
implicit_persistence_transactionhas been added that wraps persistence operations (save,destroy,touch) in a transaction. This method can be overridden in models to customize transaction behavior, such as setting a specific isolation level or skipping transaction creation when one is already open.Example skipping transaction creation if one is already open:
class Account < ApplicationRecord private def implicit_persistence_transaction(connection, &block) if connection.transaction_open? yield else super end end endIsrael P Valverde
-
Pass sql query to query log tags.
config.active_record. = [ sql_length: ->(context) { context[:sql].length } ]fatkodima
-
Speedup ActiveRecord::Migration.maintain_test_schema! when using multiple databases.
Previously, Active Record would inefficiently connect twice to each database, now it only connects once per database to reverify the schema.
Iliana Hadzhiatanasova
-
Add
unique_byoption toinsert_all!.Chedli Bourguiba
-
Fix PostgreSQL schema dumping to handle schema-qualified table names in foreign_key references that span different schemas.
# before add_foreign_key "hst.event_log_attributes", "hst.event_logs" # emits correctly because they're in the same schema (hst) add_foreign_key "hst.event_log_attributes", "hst.usr.user_profiles", column: "created_by_id" # emits hst.user.* when user.* is expected # after add_foreign_key "hst.event_log_attributes", "hst.event_logs" add_foreign_key "hst.event_log_attributes", "usr.user_profiles", column: "created_by_id"Chiperific
-
Add
PostgreSQLAdapter.register_type_mappingfor custom SQL type registration.Third-party gems can now register custom type mappings without prepending internal methods:
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.register_type_mapping do |type_map| type_map.register_type("geometry") do |oid, fmod, sql_type| MyGeometryType.new(sql_type) end endCallbacks execute in registration order.
Abdelkader Boudih
-
Yield the transaction object to the block when using
with_lock.Ngan Pham
-
Fix bug when
current_transaction.isolationwould not have been reset in test env.Additionally, extending the change in #55549 to handle
requires_new: true.Kir Shatrov
-
Allow
schema_dumpconfiguration to be an absolute path.Previously, the
schema_dumpconfiguration was always joined with thedb_dirpath. Now, if an absolute path is provided, it will be used as-is.Mike Dalessio
-
Decode PostgreSQL bytea and money columns when they appear in direct query results.
bytea columns are now decoded to binary-encoded Strings, and money columns are decoded to BigDecimal instead of String.
ActiveRecord::Base.connection .select_value("select '\\x48656c6c6f'::bytea").encoding #=> Encoding::BINARY ActiveRecord::Base.connection .select_value("select '12.34'::money").class #=> BigDecimalMatthew Draper
-
Add support for configuring migration strategy on a per-adapter basis.
migration_strategycan now be set on individual adapter classes, overriding the global ActiveRecord.migration_strategy. This allows individual databases to customize migration execution logic:class CustomPostgresStrategy < ActiveRecord::Migration::DefaultStrategy def drop_table(*) # Custom logic specific to PostgreSQL end end ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.migration_strategy = CustomPostgresStrategyAdrianna Chang
-
Allow either explain format syntax for EXPLAIN queries.
MySQL uses FORMAT=JSON whereas Postgres uses FORMAT JSON. We should be able to accept both formats as options.
Gannon McGibbon
-
On MySQL parallel test database table reset to use
DELETEinstead ofTRUNCATE.Truncating on MySQL is very slow even on empty or nearly empty tables.
As a result of this change auto increment counters are now no longer reset between test runs on MySQL and the
SKIP_TEST_DATABASE_TRUNCATEenvironment variable no longer has any effect.Donal McBreen
-
Fix inconsistency in PostgreSQL handling of unbounded time range types
Use
-infinityrather thanNULLfor the lower value of PostgreSQL time ranges when saving records with a Ruby range that begins withnil.create_table :products do |t| t.tsrange :period end class Product < ActiveRecord::Base; end t = Time.utc(2000) Product.create(period: t...nil) Product.create(period: nil...t)Previously this would create two records using different values to represent lower-unbounded and upper-unbounded ranges.
["2000-01-01 00:00:00",infinity) (NULL,"2000-01-01 00:00:00")Now both will use
-infinity/infinitywhich are handled differently thanNULLby some PostgreSQL range operators (e.g.,lower_inf) and support both exclusive and inclusive bounds.["2000-01-01 00:00:00",infinity) [-infinity,"2000-01-01 00:00:00")Martin-Alexander
-
Database-specific shard swap prohibition
In #43485 (v7.0.0), shard swapping prohibition was introduced as a global switch that applied to all databases.
For the use case of a multi-database application, the global prohibition is overly broad, and so with this change the method
prohibit_shard_swappingwill scope the prohibition to the same connection class (i.e.,connection_specification_name). This allows an application to prohibit shard swapping on a specific database while allowing it on all others.Mike Dalessio
-
Fix upsert_all when using repeated timestamp attributes.
Gannon McGibbon
-
PostgreSQL enable drop database FORCE option.
One of the benefits of developing with MySQL is that it allows dropping the current database without first disconnecting clients. As a result developers can use
bin/rails db:resetand similar, without first shutting down instances of the app, Rails consoles, background workers, etc. By default PostgreSQL fails to drop a database when clients are connected and displays the following error:> PG::ObjectInUse: ERROR: database "xyz" is being accessed by other users (PG::ObjectInUse)This is frustrating when working in development where the database may be dropped frequently.
PostgreSQL 13 added the
FORCEoption to theDROP DATABASEstatement (PostgreSQL docs) which automatically disconnects clients before dropping the database. This option is automatically enabled for supported PostgreSQL versions.Steven Webb
-
Raise specific exception when a prohibited shard change is attempted.
The new
ShardSwapProhibitedErrorexception allows applications and connection-related libraries to more easily recover from this specific scenario. Previously anArgumentErrorwas raised, so the new exception subclassesArgumentErrorfor backwards compatibility.Mike Dalessio
-
Fix SQLite3 data loss during table alterations with CASCADE foreign keys.
When altering a table in SQLite3 that is referenced by child tables with
ON DELETE CASCADEforeign keys, ActiveRecord would silently delete all data from the child tables. This occurred because SQLite requires table recreation for schema changes, and during this process the original table is temporarily dropped, triggering CASCADE deletes on child tables.The root cause was incorrect ordering of operations. The original code wrapped
disable_referential_integrityinside a transaction, butPRAGMA foreign_keyscannot be modified inside a transaction in SQLite - attempting to do so simply has no effect. This meant foreign keys remained enabled during table recreation, causing CASCADE deletes to fire.The fix reverses the order to follow the official SQLite 12-step ALTER TABLE procedure:
disable_referential_integritynow wraps the transaction instead of being wrapped by it. This ensures foreign keys are properly disabled before the transaction starts and re-enabled after it commits, preventing CASCADE deletes while maintaining data integrity through atomic transactions.Ruy Rocha
-
Fix negative scopes for enums to include records with
nilvalues.fatkodima
-
Improve support for SQLite database URIs.
The
db:createanddb:droptasks now correctly handle SQLite database URIs, and the SQLite3Adapter will create the parent directory if it does not exist.Mike Dalessio
Please check [8-1-stable]) for previous changes.