activerecord 8.1.3.1 → 8.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +617 -0
  3. data/lib/active_record/associations/association.rb +34 -16
  4. data/lib/active_record/associations/association_scope.rb +1 -1
  5. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  6. data/lib/active_record/associations/builder/belongs_to.rb +19 -4
  7. data/lib/active_record/associations/collection_association.rb +6 -2
  8. data/lib/active_record/associations/collection_proxy.rb +4 -0
  9. data/lib/active_record/associations/foreign_association.rb +3 -1
  10. data/lib/active_record/associations/has_one_association.rb +1 -0
  11. data/lib/active_record/associations/join_dependency.rb +1 -2
  12. data/lib/active_record/associations/preloader/association.rb +1 -1
  13. data/lib/active_record/attribute_methods/serialization.rb +2 -2
  14. data/lib/active_record/attribute_methods.rb +6 -4
  15. data/lib/active_record/callbacks.rb +1 -2
  16. data/lib/active_record/coders/column_serializer.rb +1 -1
  17. data/lib/active_record/coders/json.rb +7 -6
  18. data/lib/active_record/coders/yaml_column.rb +2 -2
  19. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +17 -0
  20. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +32 -20
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -2
  22. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +2 -2
  23. data/lib/active_record/connection_adapters/abstract_adapter.rb +12 -0
  24. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +79 -23
  25. data/lib/active_record/connection_adapters/column.rb +2 -5
  26. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -21
  27. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +3 -16
  28. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  29. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  30. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  31. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +17 -5
  32. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  33. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
  34. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +4 -2
  35. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -0
  36. data/lib/active_record/connection_adapters/trilogy_adapter.rb +14 -0
  37. data/lib/active_record/counter_cache.rb +8 -6
  38. data/lib/active_record/database_configurations/hash_config.rb +9 -2
  39. data/lib/active_record/database_configurations/url_config.rb +2 -0
  40. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  41. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  42. data/lib/active_record/filter_attribute_handler.rb +6 -3
  43. data/lib/active_record/gem_version.rb +2 -2
  44. data/lib/active_record/inheritance.rb +8 -3
  45. data/lib/active_record/log_subscriber.rb +28 -16
  46. data/lib/active_record/message_pack.rb +13 -0
  47. data/lib/active_record/migration/command_recorder.rb +12 -2
  48. data/lib/active_record/model_schema.rb +2 -2
  49. data/lib/active_record/nested_attributes.rb +8 -6
  50. data/lib/active_record/persistence.rb +1 -0
  51. data/lib/active_record/querying.rb +13 -1
  52. data/lib/active_record/reflection.rb +1 -1
  53. data/lib/active_record/relation/calculations.rb +8 -3
  54. data/lib/active_record/relation/finder_methods.rb +34 -20
  55. data/lib/active_record/relation/merger.rb +4 -2
  56. data/lib/active_record/relation/predicate_builder.rb +1 -1
  57. data/lib/active_record/relation/query_methods.rb +63 -13
  58. data/lib/active_record/relation.rb +20 -12
  59. data/lib/active_record/schema.rb +1 -1
  60. data/lib/active_record/schema_dumper.rb +1 -1
  61. data/lib/active_record/signed_id.rb +1 -1
  62. data/lib/active_record/statement_cache.rb +1 -1
  63. data/lib/active_record/store.rb +4 -2
  64. data/lib/active_record/tasks/abstract_tasks.rb +8 -1
  65. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -16
  66. data/lib/active_record/test_databases.rb +4 -0
  67. data/lib/active_record/test_fixtures.rb +13 -4
  68. data/lib/active_record/transactions.rb +19 -16
  69. data/lib/active_record/type_caster/connection.rb +1 -1
  70. data/lib/active_record.rb +1 -1
  71. data/lib/arel/visitors/to_sql.rb +1 -1
  72. metadata +9 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 634b5e9543d35ba0aa1b4d18b06a0b49a8c4cc56057fd2a050a9d00805d649a3
4
- data.tar.gz: fe583e47b47f1a9b03d04e073b3109545d83c37eafb8c490adecc07c07fb656d
3
+ metadata.gz: 5fa58a44daca2d6355264162e3a8f7df6249f432fd3e980b14ffbba7b0aa732c
4
+ data.tar.gz: 706535a8d0930e833eefe7cb2b7e971e0ff1b5297028ced6e53c42a3ae2cd40d
5
5
  SHA512:
6
- metadata.gz: 7eadc3d4b13e69414c53fb9c619ca778673b4f3d7b91b809eac8103e792ecf2c4d25dd53c58e98ebf7dda92e107a4f4e936822e21a35a6ecdd51c9ec5c8b0fc4
7
- data.tar.gz: 3f0b7431b03aa81dc8586b851d1c719c3d8465d7fce3938e225b9972c0346b236c5c579d34c89e1a3a1dbe22337f2b52e2213006226fc68dddacc36f979eb231
6
+ metadata.gz: 222277a1c6de4a2c59894eb4c3f1c272482a67ef0eb268edde6c0c70b57e5044c24988273a2a13f1433c3d7f18574e88ad33ea0d994251f01eacdf8b5397c1ae
7
+ data.tar.gz: 476e3611c860d38fed3cee992e8848218f38fd12e505d4463541d1f7015006ef481d224df4263c7f1168f4349e25fa8509ef35dffc9f44858e9e6cf210c99522
data/CHANGELOG.md CHANGED
@@ -1,3 +1,620 @@
1
+ ## Rails 8.1.4 (September 24, 2026) ##
2
+
3
+ * Avoid deadlocks when concurrent `find_or_create_by` calls read back the same
4
+ record within MySQL transactions.
5
+
6
+ Use a shared lock for the read after a duplicate insert, preserving visibility
7
+ under REPEATABLE READ without upgrading competing shared locks to exclusive locks.
8
+ This also applies to `create_or_find_by` and the bang variants of both methods.
9
+
10
+ Fixes #54281.
11
+
12
+ *Kirsten Westeinde*
13
+
14
+ * Filter the database password out of failed `db:` task command error messages.
15
+
16
+ *Ngan Pham*
17
+
18
+ * Fix `ActiveRecord::TypeCaster::Connection` sometimes leaking a checked-out
19
+ connection.
20
+
21
+ *Hartley McGuire*
22
+
23
+ * Fix PostgreSQL exclusion constraints with multiline expressions being parsed
24
+ incorrectly during schema introspection.
25
+
26
+ *Jake McAllister*
27
+
28
+ * Fix async `ActiveRecord::StatementCache#execute` raising an error for
29
+ out-of-range bind values instead of returning an empty result.
30
+
31
+ *viralpraxis*
32
+
33
+ * Fix `where` clauses with column-tuple syntax not resolving references to
34
+ other tables.
35
+
36
+ *Chris Gunther*
37
+
38
+ * Fix `distinct: true` being ignored by `average`.
39
+
40
+ *Kenta Ishizaki*
41
+
42
+ * Fix `belongs_to` change tracking for composite foreign keys.
43
+
44
+ Only the first foreign key column was checked for changes; now all foreign
45
+ key columns are checked.
46
+
47
+ *Anas Khan*
48
+
49
+ * Make `add_column(if_not_exists: true)` reversible.
50
+
51
+ *Kenta Ishizaki*
52
+
53
+ * Quote the index name in MySQL `enable_index` and `disable_index`.
54
+
55
+ Unquoted index names containing special characters could cause SQL syntax
56
+ errors.
57
+
58
+ *Kenta Ishizaki*
59
+
60
+ * Make `remove_foreign_key(if_exists: true)` reversible.
61
+
62
+ *Kenta Ishizaki*
63
+
64
+ * Fix `distinct: true` being ignored by grouped `sum`.
65
+
66
+ *Kenta Ishizaki*
67
+
68
+ * Return an `ActiveRecord::Promise` from `async_ids` on a contradictory
69
+ relation, instead of a plain `ActiveRecord::Result`.
70
+
71
+ *Kenta Ishizaki*
72
+
73
+ * Fix reversibility check for `drop_virtual_table`.
74
+
75
+ The wrong argument was extracted, so the check for whether options were
76
+ given could pass or fail incorrectly.
77
+
78
+ *Kenta Ishizaki*
79
+
80
+ * Fix `remove_check_constraint(if_exists: true)` incorrectly raising when
81
+ removing by expression.
82
+
83
+ The `if_exists` check did not pass the expression to
84
+ `check_constraint_exists?`, so it could not find the constraint.
85
+
86
+ *Kenta Ishizaki*
87
+
88
+ * Clear PostgreSQL notices as they are handled, preventing warnings from
89
+ leaking to subsequent queries.
90
+
91
+ *Matthew Draper*
92
+
93
+ * Fix `WeakThreadKeyMap` raising "can't add a new key into hash during
94
+ iteration" when connections are checked in from multiple threads.
95
+
96
+ *Matthew Draper*
97
+
98
+ * Continue pool disconnect after a connection's cleanup or disconnect raises.
99
+
100
+ Previously, a failure on one connection aborted the teardown, stranding
101
+ remaining connections and their resources.
102
+
103
+ *Matthew Draper*
104
+
105
+ * Fix race condition between `preconnect` and the connection pool reaper.
106
+
107
+ *Yasuo Honda*
108
+
109
+ * Fix `in_order_of` raising when the column is a CTE reference.
110
+
111
+ *viralpraxis*
112
+
113
+ * Fix `content_columns` including composite primary key components.
114
+
115
+ Only the first primary key column was excluded; now all primary key columns
116
+ are excluded.
117
+
118
+ *Kenta Ishizaki*
119
+
120
+ * Treat `remove_column` with options but no type as irreversible.
121
+
122
+ Previously, `remove_column(:table, :column, default: nil)` would not raise
123
+ `IrreversibleMigration` because the options hash was mistaken for a column
124
+ type.
125
+
126
+ *Kenta Ishizaki*
127
+
128
+ * Fix `counter_cache_column` crashing when `:counter_cache` is given as a
129
+ Hash without a `:column` key.
130
+
131
+ *Kenta Ishizaki*
132
+
133
+ * Coerce `seeds` and `use_metadata_table` to booleans in `UrlConfig`.
134
+
135
+ When configured via `DATABASE_URL`, these options were left as strings
136
+ instead of being coerced to booleans like `replica` and `database_tasks`.
137
+
138
+ *Kenta Ishizaki*
139
+
140
+ * Fix `excluding` (and its `without` alias) to work with composite primary key
141
+ models. Records passed to `excluding` are now matched by their full composite id
142
+ instead of being looked up by a single primary key column.
143
+
144
+ *Kenta Ishizaki*
145
+
146
+ * Fix `Relation#delete` raising on a single composite primary key id.
147
+
148
+ `Model.delete(id)` now wraps a single composite id in an array before building
149
+ the `WHERE` clause, so a single record can be deleted by its composite id.
150
+
151
+ *Kenta Ishizaki*
152
+
153
+ * Fix reading a composite primary key after a partial `select` returning the
154
+ raw value instead of `nil` for unselected key columns.
155
+
156
+ The attribute defaults builder excluded only the single primary key column from
157
+ defaults; for composite primary keys it left the unselected key columns in the
158
+ defaults, so `record.id` returned an array of raw column values instead of
159
+ `[nil, nil]`. All primary key columns are now excluded.
160
+
161
+ *Kenta Ishizaki*
162
+
163
+ * Fix `find` with an empty array on a composite primary key model returning the
164
+ argument array instead of `[]`.
165
+
166
+ `Model.find([])` now returns a fresh empty array for composite primary key
167
+ models, matching single-key models.
168
+
169
+ *Kenta Ishizaki*
170
+
171
+ * Fix `find` with an order clause and an offset past the end of the ids raising
172
+ `RecordNotFound` instead of returning an empty result.
173
+
174
+ *Kenta Ishizaki*
175
+
176
+ * Fix `in_order_of` dropping values that cannot be serialized for the column
177
+ instead of turning them into an `IS NULL` match.
178
+
179
+ Out-of-range integers and unknown enum keys are now ignored rather than
180
+ silently matching rows with `NULL` in that column. When all requested values
181
+ are unrepresentable, the relation returns no rows instead of building an empty
182
+ `CASE` expression.
183
+
184
+ *Kenta Ishizaki*
185
+
186
+ * Fix stale `finder_needs_type_condition?` memo after the schema cache is reloaded.
187
+
188
+ When an STI model's `type` column was removed and re-added (e.g. via a
189
+ migration) and `reset_column_information` was called, the cached
190
+ `finder_needs_type_condition` value was not cleared, so queries could omit the
191
+ STI type condition. The memo is now reset on `reload_schema_from_cache`.
192
+
193
+ *Kenta Ishizaki*
194
+
195
+ * Fix PostgreSQL schema dump to qualify `enum_type` when the same enum name exists
196
+ in different schemas.
197
+
198
+ Previously the dumped `enum_type` used the bare type name, which could resolve to
199
+ the wrong enum in another schema. It now uses the schema-qualified name.
200
+
201
+ *fatkodima*
202
+
203
+ * Fix PostgreSQL index introspection to parse opclasses from another schema.
204
+
205
+ Index expressions using a schema-qualified opclass (e.g.
206
+ `USING gin(position test_schema.gin_trgm_ops)`) are now parsed correctly so the
207
+ opclass is captured without the schema prefix.
208
+
209
+ *Nicolas Vandenboegaerde*
210
+
211
+ * Fix `belongs_to` `:touch` with composite foreign keys.
212
+
213
+ Touching the parent record when a `belongs_to` with a composite foreign key
214
+ changes or is destroyed now correctly resolves the old and new parent by all
215
+ foreign key columns.
216
+
217
+ *Romulo Storel*
218
+
219
+ * Fix `add_index` `comment:` option on a schema-qualified PostgreSQL table.
220
+
221
+ The `COMMENT ON INDEX` statement now schema-qualifies the index name, so the
222
+ comment is applied to the index in the correct schema instead of raising.
223
+
224
+ *Tobias Egli*
225
+
226
+ * Fix `check_all_foreign_keys_valid!` on PostgreSQL partitioned tables.
227
+
228
+ The validation query previously matched constraints by name across all tables in
229
+ the schema, marking unrelated constraints (including partition children) as not
230
+ validated. It now scopes the update to the specific table.
231
+
232
+ *Alexis Andrieu*
233
+
234
+ * Revert stable sorting of columns and indexes in serialized schema caches.
235
+
236
+ `SchemaCache#encode_with` no longer sorts a table's columns and indexes by name
237
+ when writing the cache, restoring the pre-8.1 behavior.
238
+
239
+ *Jean Boussier*
240
+
241
+ * Fix `only_columns` to enumerate columns in the `SELECT` clause.
242
+
243
+ Models using `only_columns` were generating `SELECT *` instead of an explicit
244
+ column list, which could select columns that were meant to be excluded.
245
+
246
+ *sarub0b0*
247
+
248
+ * Fix `eager_load` with a composite primary key and an explicit `select`.
249
+
250
+ The join dependency used the single primary key value to build aliases; for
251
+ composite primary keys it now uses the full array of primary key columns, so
252
+ eager loading with a custom `select` no longer drops the association records.
253
+
254
+ *fatkodima*
255
+
256
+ * Fix `find_signed` with a composite primary key.
257
+
258
+ `find_signed` now wraps the decoded id in an array so `find_by` matches against
259
+ all primary key columns, instead of silently returning `nil`.
260
+
261
+ *Kenta Ishizaki*
262
+
263
+ * Fix `with_connection(prevent_permanent_checkout: true)` clearing a sticky lease
264
+ that was already set by `lease_connection`.
265
+
266
+ A `with_connection` call with `prevent_permanent_checkout: true` no longer resets
267
+ `lease.sticky` to its previous value when the connection was already sticky, so
268
+ the connection remains checked out after the block.
269
+
270
+ *Ben Sheldon*
271
+
272
+ * Fix `reset_counters` to cast string ids to the primary key type.
273
+
274
+ `reset_counters` called with a string id (the form ids take from request params)
275
+ failed to find the record for both single and composite primary keys. Each id is
276
+ now cast against its own column type.
277
+
278
+ *fatkodima*
279
+
280
+ * Fix `belongs_to` presence validation for composite foreign keys.
281
+
282
+ The required-association validation only checked the first foreign key column.
283
+ For composite foreign keys it now considers the association present only when
284
+ none of the foreign key columns are `nil`, and changed when any of them changed.
285
+
286
+ *Felix Descoteaux*
287
+
288
+ * Fix preloading a `belongs_to` with a composite foreign key when some key columns
289
+ are `nil`.
290
+
291
+ The preloader now skips owners whose composite key has any `nil` component
292
+ instead of issuing a query that matches unrelated rows.
293
+
294
+ *Rose Wiegley*
295
+
296
+ * Fix `belongs_to` inverse matching with composite foreign keys.
297
+
298
+ `inversable?` compared a single foreign key column against a single primary key,
299
+ so `belongs_to` associations with composite foreign keys never matched their
300
+ inverse. Foreign key and primary key values are now compared component by
301
+ component.
302
+
303
+ *Martin Samson*
304
+
305
+ * Fix `validate_table_length!` to ignore the schema prefix on PostgreSQL.
306
+
307
+ Schema-qualified table names (e.g. `"schema.very_long_table_name"`) were
308
+ incorrectly rejected as exceeding the table name length limit because the schema
309
+ prefix was counted. The schema is now stripped before checking the length.
310
+
311
+ *fatkodima*
312
+
313
+ * Fix passing Trilogy SSL options to `mysql` and `mysqldump` commands.
314
+
315
+ *Ngan Pham*
316
+
317
+ * Fix `keepalive: false` in `database.yml` falling back to the
318
+ 600 second default.
319
+
320
+ *Joe Sak*
321
+
322
+ * Fix connection checkout blocking past the configured `checkout_timeout` while
323
+ waiting behind background connection maintenance.
324
+
325
+ `ConnectionPool#try_to_queue_for_background_connection` waited up to a
326
+ hardcoded 100 seconds for a connection tied up in maintenance (e.g. a keepalive
327
+ ping), regardless of the pool's configured `checkout_timeout`. If maintenance
328
+ stalled (a slow or unresponsive database server), checkout could block for far
329
+ longer than the caller asked for.
330
+
331
+ *Joe Sak*
332
+
333
+ * Fix encrypted fixtures for JSON columns.
334
+
335
+ Encrypted fixture values were serialized twice: once by the encrypted
336
+ attribute type and again by the column type when the rows were inserted.
337
+ For `json`/`jsonb` columns this stored the encrypted payload as a JSON
338
+ string, which then failed to decrypt. The payload is now handed back in
339
+ the column's cast form so it round-trips through the column type.
340
+
341
+ *Carlos Daniel Pohlod*
342
+
343
+ * Fix performance regression in `method_missing` for virtual SELECT alias
344
+ attributes.
345
+
346
+ Fixes #57183.
347
+
348
+ *Hammad Khan*
349
+
350
+ * Column defaults are no longer eagerly deserialized.
351
+
352
+ Restore the Active Record 8.0 and older behavior.
353
+ Column defaults are now whatever is returned by the database, generally a string.
354
+
355
+ This was changed in 8.1.0 to make the attribute API more consistent,
356
+ however it caused problems when the attribute type is later changed in the model.
357
+
358
+ Typically, some databases treat boolean columns as small integers, ance once the
359
+ default integer is casted to a boolean, it's not longer possible to change the
360
+ attribute back into an integer in the model.
361
+
362
+ Before:
363
+
364
+ ```ruby
365
+ Person.columns_hash["age"].default # => 0
366
+ ```
367
+
368
+ After:
369
+
370
+ ```ruby
371
+ Person.columns_hash["age"].default # => "0"
372
+ ```
373
+
374
+ See https://github.com/rails/rails/issues/58292.
375
+
376
+ *Jean Boussier*
377
+
378
+ * Preserve the declared parent order when dumping PostgreSQL `INHERITS` table options.
379
+
380
+ The schema dumper read a table's inherited parents without an `ORDER BY`, so the
381
+ `INHERITS (...)` clause in `schema.rb` could be emitted in a nondeterministic order.
382
+ Parents are now ordered by `pg_inherits.inhseqno`.
383
+
384
+ *viralpraxis*
385
+
386
+ * Preserve SQLite partial and expression indexes when their stored SQL contains newlines.
387
+
388
+ *Javi Ramírez*
389
+
390
+ * Only use multi statement for `SET TRANSACTION ISOLATION LEVEL; BEGIN` if
391
+ MySQL connection is configured to use multi statement.
392
+
393
+ *Eliseu Daroit*, *Hartley McGuire*, *Matthew Draper*
394
+
395
+ * Mark a connection unverified when a non-`StandardError` interrupts a query.
396
+
397
+ A query interrupted by a non-`StandardError`, such as a fiber scheduler's
398
+ cancel or a `Timeout`, leaves the connection desynced. Active Record
399
+ previously returned it to the pool still marked verified, so the next
400
+ checkout reused it and failed on a dead socket. The connection is now
401
+ marked unverified, so it re-verifies and reconnects before its next use.
402
+
403
+ *Eliseu Daroit*
404
+
405
+ * Fix `has_many` and `has_one` associations on a new record returning an empty
406
+ result when the owner has a composite primary key, even when every primary
407
+ key column is populated.
408
+
409
+ *Jean-Samuel Aubry-Guzzi*
410
+
411
+ * Fix inverse association matching for `has_many` and `has_one` associations
412
+ with association-specific composite primary keys.
413
+
414
+ *Hugo Vacher*
415
+
416
+ * Fix `ActiveRecord::Base.with` to call `Object#with` when given a block.
417
+
418
+ The class-level `with` delegates to the relation's CTE query method, which
419
+ does not accept a block. When a block is passed, it now delegates to
420
+ `Object#with` instead, so it can be used to temporarily set attributes on
421
+ the class for the duration of the block.
422
+
423
+ *Janko Marohnić*
424
+
425
+ * Fix `serialize` with `coder: ActiveRecord::Coders::JSON` silently double-encoding
426
+ a native `json`/`jsonb` column.
427
+
428
+ *Kenta Ishizaki*
429
+
430
+ * Fix `update_all` / `delete_all` ignoring `group` and `having`, updating or
431
+ deleting every row in the table instead of only the rows that satisfy the
432
+ `HAVING` clause.
433
+
434
+ *Kenta Ishizaki*
435
+
436
+ * Fix `ActiveRecord::Relation#cache_key` / `cache_version` for a loaded collection
437
+ containing a record without a timestamp.
438
+
439
+ *Kenta Ishizaki*
440
+
441
+ * Fix model `filter_attributes` to not add useless filters into `config.filter_parameters`.
442
+
443
+ Proc and Regexp filters would be improperly turned into string matchers.
444
+ Now they are skipped.
445
+
446
+ *Jonathan Calvert*
447
+
448
+ * Fix `ActiveRecord::MessagePack` serialization raising `NoMethodError`
449
+ for any record with a populated `time` column, which made such records
450
+ uncacheable through the MessagePack cache serializer.
451
+
452
+ *Kenta Ishizaki*
453
+
454
+ * Fix replacing or clearing a polymorphic `has_one` leaving a stale type column
455
+ on the removed record.
456
+
457
+ *Kenta Ishizaki*
458
+
459
+ * Fix `accepts_nested_attributes_for` `:limit` miscounting a single-record hash.
460
+
461
+ *Kenta Ishizaki*
462
+
463
+ * Fix `rename_index` to preserve a partial index's `WHERE` for SQLite and older MySQL/MariaDB versions.
464
+
465
+ *Kenta Ishizaki*
466
+
467
+ * Fix PostgreSQL `foreign_keys` returning a corrupted `to_table` for a foreign key
468
+ that references a table in a quoted schema.
469
+
470
+ *Kenta Ishizaki*
471
+
472
+ * Fix grouped calculations (e.g. `count`) grouped by a `belongs_to` association
473
+ that points to a composite primary key model.
474
+
475
+ `Book.group(:order).count`, where `Book belongs_to :order` and `Order` has a
476
+ composite primary key, raised `ArgumentError: Expected corresponding value
477
+ for ["shop_id", "id"] to be an Array`. The grouped result is now keyed by the
478
+ associated records as it already is for single-column keys.
479
+
480
+ *Kenta Ishizaki*
481
+
482
+ * Fix `has_many`/`has_one :through` associations with `disable_joins: true`
483
+ silently returning an empty result when the source points to a composite
484
+ primary key model.
485
+
486
+ *Kenta Ishizaki*
487
+
488
+ * Fix collection association `ids=` writers (e.g. `author.book_ids=`) raising
489
+ `ActiveRecord::RecordNotFound` for existing records when a composite primary
490
+ key model is assigned string ids (the shape ids take from request params).
491
+
492
+ Each id component is now cast against its own column type, instead of casting
493
+ the whole tuple against an array of column names, which `type_for_attribute`
494
+ can't resolve to a real type (so the cast was a no-op and the string ids
495
+ never matched the loaded records).
496
+
497
+ *Kenta Ishizaki*
498
+
499
+ * Fix `find` with multiple composite primary key ids passed as strings
500
+ silently returning `[]`.
501
+
502
+ `Model.find([["1", "10"], ["1", "20"]])` (the shape ids take when they come
503
+ from request parameters) returned an empty array instead of the records and
504
+ without raising `RecordNotFound`. The ids were cast against the array of key
505
+ column names as a whole — which is a no-op — so the string tuples never
506
+ compared equal to the records' integer ids when ordering the result. Each
507
+ component is now cast against its own column type, matching the documented
508
+ coercion already performed for single-column keys.
509
+
510
+ *Kenta Ishizaki*
511
+
512
+ * Fix PostgreSQL `daterange` / `tsrange` / `tstzrange` schema dump producing
513
+ invalid Ruby.
514
+
515
+ The schema dumper used to render range defaults via `Range#inspect`, which
516
+ falls back to `Date#inspect` / `Time#inspect` for the bounds. The resulting
517
+ `schema.rb` literal (for example `default: Mon, 01 Jan 2024..Wed, 01 Jan 2025`)
518
+ raised a `SyntaxError` on `db:schema:load`. The bounds are now rendered via
519
+ the subtype's `type_cast_for_schema`, so date and timestamp range defaults
520
+ round-trip through `schema.rb` like any other column.
521
+
522
+ *Kenta Ishizaki*
523
+
524
+ * Fix deadlock when pool-less connection materializes while fetching database
525
+ server version.
526
+
527
+ *Hartley McGuire*
528
+
529
+ * Fix PostgreSQL range columns corrupting bounds that contain a comma.
530
+
531
+ `PostgreSQL::OID::Range#extract_bounds` split a range's textual
532
+ representation on the first comma, but PostgreSQL double-quotes any bound
533
+ that itself contains a comma (e.g. a range over a text subtype). A value
534
+ such as `["a,b","c,d")` was therefore split inside the first quoted bound
535
+ and read back with both endpoints corrupted. The split now skips over
536
+ double-quoted segments when locating the separating comma.
537
+
538
+ *Kenta Ishizaki*
539
+
540
+ * Fix store accessor `*_change` and `saved_change_to_*` reporting a change
541
+ for unchanged keys.
542
+
543
+ When one key of a `store` column changed, the generated `<key>_change` and
544
+ `saved_change_to_<key>` methods returned a `[value, value]` pair for the
545
+ column's *other* keys too, even though their matching `<key>_changed?` /
546
+ `saved_change_to_<key>?` correctly returned `false` and standard attribute
547
+ `_change` / `saved_change_to_*` methods return `nil` when unchanged. The
548
+ methods now return `nil` unless that specific key actually changed,
549
+ matching their predicate counterparts.
550
+
551
+ *Kenta Ishizaki*
552
+
553
+ * Reduce number of queries when introspecting MySQL/MariaDB tables.
554
+
555
+ A fix to support MariaDB function defaults caused the MySQL adapters to
556
+ issue one query per column when introspecting tables.
557
+
558
+ Now it only issue a single extra query per table, and only when using MariaDB.
559
+
560
+ *Iliana Hadzhiatanasova*
561
+
562
+ * Fully qualify query criterias in `has_and_belongs_to_many` associations.
563
+
564
+ Ensure that scopes with the same column name but in different tables aren't lost
565
+ when following an HABTM association.
566
+
567
+ *Philip Maina*
568
+
569
+ * Fix Active Record Pool Reaper thread leak after `Parallelization#shutdown`.
570
+
571
+ After parallelized test runs, the parent process leaked the Active Record Pool
572
+ Reaper thread, holding open connection pools and file descriptors for up to
573
+ `reaping_frequency` seconds (or indefinitely if never reaped).
574
+
575
+ Three fixes: `Parallelization#shutdown` now calls `run_cleanup_hooks`;
576
+ Active Record registers a cleanup hook that discards all connection pools; and
577
+ `ConnectionPool#discard!` now calls `Reaper.discard_pool`, which immediately
578
+ kills and joins the reaper thread when no pools remain at that frequency.
579
+
580
+ *Ruy Rocha*
581
+
582
+ * Fix duplicate `WHERE` conditions in `create_or_find_by`.
583
+
584
+ When `create_or_find_by` catches a `RecordNotUnique` error and retries the query, it now uses `rewhere` and `take!` to prevent duplicating existing scope conditions.
585
+
586
+ *Yavor Dashev*
587
+
588
+ * Fix `ActiveRecord::QueryMethods#in_order_of` when passing an out-of-range Integer
589
+
590
+ To match the behavior of the `Enumerable` version, `in_order_of` now ignores an out-of-range Integer.
591
+
592
+ *tejanium*
593
+
594
+ * Revert alphabetical sorting of table columns inside `schema.rb`.
595
+
596
+ Alphabetical sorting of table columns inside the schema creates improper production tables when using `db:prepare`.
597
+
598
+ *Bert McCutchen*
599
+
600
+ * Fix `strict_loading` violations ignored when using `pluck`
601
+
602
+ *Johnson Chan*
603
+
604
+ * Fix incorrect callback execution order when `config.active_record.run_after_transaction_callbacks_in_order_defined = true`
605
+ and using `after_commit` and `after_rollback` callbacks with `prepend: true`.
606
+
607
+ *Joshua Young*
608
+
609
+ * Fix `becomes` to preserve `marked_for_destruction?`.
610
+
611
+ *Vincent Robert*
612
+
613
+ * MySQL error 1046 (`ER_NO_DB_ERROR: No database selected`) is now retryable as a `ConnectionFailed` exception
614
+
615
+ *Clay Harmon*
616
+
617
+
1
618
  ## Rails 8.1.3.1 (July 29, 2026) ##
2
619
 
3
620
  * No changes.
@@ -232,6 +232,18 @@ module ActiveRecord
232
232
  _create_record(attributes, true, &block)
233
233
  end
234
234
 
235
+ def violates_strict_loading?
236
+ return unless find_target?
237
+
238
+ return if @skip_strict_loading
239
+
240
+ return unless owner.validation_context.nil?
241
+
242
+ return reflection.strict_loading? if reflection.options.key?(:strict_loading)
243
+
244
+ owner.strict_loading? && !owner.strict_loading_n_plus_one_only?
245
+ end
246
+
235
247
  # Whether the association represents a single record
236
248
  # or a collection of records.
237
249
  def collection?
@@ -281,16 +293,6 @@ module ActiveRecord
281
293
  @skip_strict_loading = skip_strict_loading_was
282
294
  end
283
295
 
284
- def violates_strict_loading?
285
- return if @skip_strict_loading
286
-
287
- return unless owner.validation_context.nil?
288
-
289
- return reflection.strict_loading? if reflection.options.key?(:strict_loading)
290
-
291
- owner.strict_loading? && !owner.strict_loading_n_plus_one_only?
292
- end
293
-
294
296
  # The scope for this association.
295
297
  #
296
298
  # Note that the association_scope is merged into the target_scope only when the
@@ -409,12 +411,28 @@ module ActiveRecord
409
411
  end
410
412
 
411
413
  def matches_foreign_key?(record)
412
- if foreign_key_for?(record)
413
- record.read_attribute(reflection.foreign_key) == owner.id ||
414
- (foreign_key_for?(owner) && owner.read_attribute(reflection.foreign_key) == record.id)
415
- else
416
- owner.read_attribute(reflection.foreign_key) == record.id
417
- end
414
+ (foreign_key_for?(record) && record_foreign_key_matches_owner?(record)) ||
415
+ (foreign_key_for?(owner) && owner_foreign_key_matches_record?(record))
416
+ end
417
+
418
+ def record_foreign_key_matches_owner?(record)
419
+ foreign_key_values(record) == active_record_primary_key_values(owner)
420
+ end
421
+
422
+ def owner_foreign_key_matches_record?(record)
423
+ foreign_key_values(owner) == association_primary_key_values(record)
424
+ end
425
+
426
+ def foreign_key_values(record)
427
+ Array(reflection.foreign_key).map { |key| record.read_attribute(key) }
428
+ end
429
+
430
+ def active_record_primary_key_values(record)
431
+ Array(reflection.active_record_primary_key).map { |key| record.read_attribute(key) }
432
+ end
433
+
434
+ def association_primary_key_values(record)
435
+ Array(reflection.association_primary_key(record.class)).map { |key| record.read_attribute(key) }
418
436
  end
419
437
  end
420
438
  end