-
Fix bug with reloading models with all-queries default scopes. The previous implementation allowed non-all-queries on the current scope to leak into the scope used for reloading.
Andrew Novoselac and Matthew Draper
-
insert!now accepts the:unique_byoption, consistent withinsert.Kenta Ishizaki
-
Fix reading a
store_accessoron aNULLstructured column (json,jsonb, orhstore) marking the record as changed and overwriting theNULLwith an empty hash on the next save.Kenta Ishizaki
-
Fix
update_allcorrupting the optimistic locking column when it is set through analias_attribute.Kenta Ishizaki
-
Fix
serializewithcoder: ActiveRecord::Coders::JSONsilently double-encoding a nativejson/jsonbcolumn.Kenta Ishizaki
-
Fix
update_all/delete_allignoringgroupandhaving, updating or deleting every row in the table instead of only the rows that satisfy theHAVINGclause.Kenta Ishizaki
-
Fix ActiveRecord::Relation#cache_key /
cache_versionfor a loaded collection containing a record without a timestamp.Kenta Ishizaki
-
Fix
::ActiveRecord::MessagePackserialization raisingNoMethodErrorfor any record with a populatedtimecolumn, which made such records uncacheable through the MessagePack cache serializer.Kenta Ishizaki
-
Fix replacing or clearing a polymorphic
has_oneleaving a stale type column on the removed record.Kenta Ishizaki
-
Add
sql_notificationsconnection configuration option to disable SQL notifications for specific database connections.Libraries like Solid Cache and Solid Queue that use separate database connections via
connects_tocan suppress internal SQL notification overhead by settingsql_notifications: falseindatabase.yml:cache: adapter: postgresql database: myapp_cache sql_notifications: falseRosa Gutierrez
-
Allow the query log tags format to be configured per connection pool.
A
query_log_tags_formatkey in adatabase.ymlentry overrides the globalconfig.active_record.query_log_tags_formatfor connections in that pool, so different databases can emit:legacyor:sqlcommenterformatted comments.production: primary: database: primary analytics: database: analytics query_log_tags_format: sqlcommenterHartley McGuire
-
Fix
update_attribute/update_attribute!to raise for a readonly attribute referenced by an alias.Kenta Ishizaki
-
Fix
reset_column_sequences!for tables in quoted schemas.Kenta Ishizaki
-
Fix
accepts_nested_attributes_for:limitmiscounting a single-record hash.Kenta Ishizaki
-
Fix
rename_indexto preserve a partial index'sWHEREfor SQLite and older MySQL/MariaDB versions.Kenta Ishizaki
-
Fix PostgreSQL
foreign_keysreturning a corruptedto_tablefor a foreign key that references a table in a quoted schema.Kenta Ishizaki
-
Fix grouped calculations (e.g.
count) grouped by abelongs_toassociation that points to a composite primary key model.Book.group(:order).count, whereBook belongs_to :orderandOrderhas a composite primary key, raisedArgumentError: Expected corresponding value for ["shop_id", "id"] to be an Array. The grouped result is now keyed by the associated records as it already is for single-column keys.Kenta Ishizaki
-
Fix
has_many/has_one :throughassociations withdisable_joins: truesilently returning an empty result when the source points to a composite primary key model.Kenta Ishizaki
-
Fix collection association
ids=writers (e.g.author.book_ids=) raising::ActiveRecord::RecordNotFoundfor existing records when a composite primary key model is assigned string ids (the shape ids take from request params).Each id component is now cast against its own column type, instead of casting the whole tuple against an array of column names, which
type_for_attributecan't resolve to a real type (so the cast was a no-op and the string ids never matched the loaded records).Kenta Ishizaki
-
Fix
findwith multiple composite primary key ids passed as strings silently returning[].Model.find([["1", "10"], ["1", "20"]])(the shape ids take when they come from request parameters) returned an empty array instead of the records and without raisingRecordNotFound. The ids were cast against the array of key column names as a whole — which is a no-op — so the string tuples never compared equal to the records' integer ids when ordering the result. Each component is now cast against its own column type, matching the documented coercion already performed for single-column keys.Kenta Ishizaki
-
Fix PostgreSQL
daterange/tsrange/tstzrangeschema dump producing invalid Ruby.The schema dumper used to render range defaults via
Range#inspect, which falls back to Date#inspect /Time#inspectfor the bounds. The resultingschema.rbliteral (for exampledefault: Mon, 01 Jan 2024..Wed, 01 Jan 2025) raised aSyntaxErrorondb:schema:load. The bounds are now rendered via the subtype'stype_cast_for_schema, so date and timestamp range defaults round-trip throughschema.rblike any other column.Kenta Ishizaki
-
Respect
schema_search_pathonrails dbconsolefor PostgreSQL.Gabriel Sobrinho
-
Preserve IPv6 prefix when dumping PostgreSQL
cidr/inetdefaults toschema.rb.The schema dumper used to omit the prefix whenever it equaled
/32, which is the full mask for IPv4 but not for IPv6. As a result, an IPv6 default such as"::/32"was written as"::"inschema.rband reloaded as::/128, silently dropping the subnet information. The prefix is now only omitted when it covers the full address (/32for IPv4,/128for IPv6).Kenta Ishizaki
-
Fix deadlock when pool-less connection materializes while fetching database server version.
Hartley McGuire
-
Add
#default_orderquery method and association option which can be used to order records when no other order is specified.This extends the functionality offered by
#implicit_order_columnto scopes and associations.Usage:
class Post < ApplicationRecord has_many :comments, default_order: :likes # .. or .. has_many :comments, -> { default_order(:likes) } end post = Post.first post.comments # comments ordered by `likes` post.comments.order(:created_at) # comments ordered by `created_at`Dan Ungureanu
-
Raise
::ActiveRecord::MultiparameterAssignmentErrorsinstead ofNoMethodErrorwhen assigning a malformed multiparameter attribute name.A key routed to the multiparameter code path but missing a closing parenthesis (e.g.
"written_on(") used to crash withNoMethodError: undefined method 'first' for nilinsidefind_parameter_position. It now raises the sameMultiparameterAssignmentErrorsalready used for other invalid multiparameter input, so callers can rescue a single documented error class.Kenta Ishizaki
-
Include the list of valid values in the
ArgumentErrorraised when assigning an invalid value to an enum attribute.Book.new(status: "bogus") # ArgumentError: 'bogus' is not a valid status. Valid values are: "proposed", "written", "published"Hammad Khan
-
Include the mismatched keys in the error raised by
insert_all/upsert_allwhen the inserted objects have inconsistent attributes.Book.insert_all [{ name: "Rework", author_id: 1 }, { name: "Remote", author_id: 1, isbn: "x" }] # ArgumentError: All objects being inserted must have the same keys (extra: [:isbn])Hammad Khan
-
Allow
create_join_tableto accept a primary key.This is useful for databases like PostgreSQL where logical replication requires primary keys on all tables.
create_join_table :assemblies, :parts, primary_key: [:assembly_id, :part_id]This generates a join table with a composite primary key on both foreign key columns.
Genadi Samokovarov
-
Fix Active Record Pool Reaper thread leak after
Parallelization#shutdown.After parallelized test runs, the parent process leaked the Active Record Pool Reaper thread, holding open connection pools and file descriptors for up to
reaping_frequencyseconds (or indefinitely if never reaped).Three fixes:
Parallelization#shutdownnow callsrun_cleanup_hooks; Active Record registers a cleanup hook that discards all connection pools; andConnectionPool#discard!now callsReaper.discard_pool, which immediately kills and joins the reaper thread when no pools remain at that frequency.Ruy Rocha
-
Reset
lock_versionafter a nested savepoint rollback.When a record was saved inside a
transaction(requires_new: true)block that later rolled back, the in-memorylock_versionwas left at the incremented value while the database row was reverted by the savepoint. Saving the same instance again raised::ActiveRecord::StaleObjectErrorbecause the WHERE clause used the bumped value that no longer existed in the database.Restore the locking column from the snapshot on savepoint rollback in addition to the outermost transaction rollback handled in #57363.
Kenta Ishizaki
-
Fix duplicate
WHEREconditions increate_or_find_by.When
create_or_find_bycatches aRecordNotUniqueerror and retries the query, it now usesrewhereandtake!to prevent duplicating existing scope conditions.Yavor Dashev
-
Allow to pass array values to
in_order_ofPassing arrays allows to group records and order those groups with another query:
Posts .in_order_of(:state, [[:published, :canceled], :archived]) .order(created_at: :desc) # => # SELECT "posts".* FROM "posts" WHERE "posts"."state" IN (1, 2, 3) # ORDER BY CASE # WHEN "posts"."state" IN (1, 2) THEN 1 # WHEN "posts"."state" = 3 THEN 2 # END ASC, "posts"."created_at" DESCMarkus Doits
-
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.