activerecord 7.1.3.2 → 7.2.1

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 (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +570 -2094
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +18 -11
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +11 -5
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +3 -3
  18. data/lib/active_record/associations/has_one_association.rb +2 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  20. data/lib/active_record/associations/join_dependency.rb +6 -8
  21. data/lib/active_record/associations/nested_error.rb +47 -0
  22. data/lib/active_record/associations/preloader/association.rb +2 -1
  23. data/lib/active_record/associations/preloader/branch.rb +7 -1
  24. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  25. data/lib/active_record/associations/singular_association.rb +6 -0
  26. data/lib/active_record/associations/through_association.rb +1 -1
  27. data/lib/active_record/associations.rb +34 -273
  28. data/lib/active_record/attribute_assignment.rb +1 -11
  29. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  30. data/lib/active_record/attribute_methods/dirty.rb +2 -2
  31. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  32. data/lib/active_record/attribute_methods/read.rb +4 -16
  33. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  35. data/lib/active_record/attribute_methods/write.rb +3 -3
  36. data/lib/active_record/attribute_methods.rb +89 -58
  37. data/lib/active_record/attributes.rb +60 -45
  38. data/lib/active_record/autosave_association.rb +17 -31
  39. data/lib/active_record/base.rb +2 -3
  40. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  41. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +244 -58
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +188 -75
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  46. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  47. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +22 -9
  48. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  49. data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
  50. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  53. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -1
  54. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  55. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  56. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  57. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  58. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  59. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  60. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  61. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  62. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  63. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  64. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  65. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  66. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  67. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  68. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  69. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  71. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  72. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  73. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -77
  74. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  75. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  76. data/lib/active_record/connection_adapters.rb +121 -0
  77. data/lib/active_record/connection_handling.rb +56 -41
  78. data/lib/active_record/core.rb +60 -39
  79. data/lib/active_record/counter_cache.rb +23 -10
  80. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  81. data/lib/active_record/database_configurations/database_config.rb +19 -4
  82. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  83. data/lib/active_record/database_configurations/url_config.rb +20 -1
  84. data/lib/active_record/database_configurations.rb +1 -1
  85. data/lib/active_record/delegated_type.rb +30 -6
  86. data/lib/active_record/destroy_association_async_job.rb +1 -1
  87. data/lib/active_record/dynamic_matchers.rb +2 -2
  88. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  89. data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
  90. data/lib/active_record/encryption/encryptor.rb +18 -3
  91. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  92. data/lib/active_record/encryption/message_serializer.rb +4 -0
  93. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  94. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  95. data/lib/active_record/encryption/scheme.rb +8 -4
  96. data/lib/active_record/enum.rb +26 -6
  97. data/lib/active_record/errors.rb +46 -20
  98. data/lib/active_record/explain.rb +13 -24
  99. data/lib/active_record/fixtures.rb +37 -31
  100. data/lib/active_record/future_result.rb +17 -4
  101. data/lib/active_record/gem_version.rb +3 -3
  102. data/lib/active_record/inheritance.rb +4 -2
  103. data/lib/active_record/insert_all.rb +18 -15
  104. data/lib/active_record/integration.rb +4 -1
  105. data/lib/active_record/internal_metadata.rb +48 -34
  106. data/lib/active_record/locking/optimistic.rb +8 -7
  107. data/lib/active_record/log_subscriber.rb +0 -21
  108. data/lib/active_record/marshalling.rb +1 -1
  109. data/lib/active_record/message_pack.rb +2 -2
  110. data/lib/active_record/migration/command_recorder.rb +2 -3
  111. data/lib/active_record/migration/compatibility.rb +11 -3
  112. data/lib/active_record/migration/default_strategy.rb +4 -5
  113. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  114. data/lib/active_record/migration.rb +85 -76
  115. data/lib/active_record/model_schema.rb +39 -70
  116. data/lib/active_record/nested_attributes.rb +11 -3
  117. data/lib/active_record/normalization.rb +3 -7
  118. data/lib/active_record/persistence.rb +32 -354
  119. data/lib/active_record/query_cache.rb +18 -6
  120. data/lib/active_record/query_logs.rb +15 -0
  121. data/lib/active_record/query_logs_formatter.rb +1 -1
  122. data/lib/active_record/querying.rb +21 -9
  123. data/lib/active_record/railtie.rb +54 -67
  124. data/lib/active_record/railties/controller_runtime.rb +13 -4
  125. data/lib/active_record/railties/databases.rake +42 -45
  126. data/lib/active_record/reflection.rb +102 -37
  127. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  128. data/lib/active_record/relation/batches.rb +14 -8
  129. data/lib/active_record/relation/calculations.rb +95 -62
  130. data/lib/active_record/relation/delegation.rb +8 -11
  131. data/lib/active_record/relation/finder_methods.rb +16 -2
  132. data/lib/active_record/relation/merger.rb +4 -6
  133. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  134. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  135. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  136. data/lib/active_record/relation/predicate_builder.rb +3 -3
  137. data/lib/active_record/relation/query_methods.rb +212 -47
  138. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  139. data/lib/active_record/relation/spawn_methods.rb +2 -18
  140. data/lib/active_record/relation/where_clause.rb +7 -19
  141. data/lib/active_record/relation.rb +500 -66
  142. data/lib/active_record/result.rb +32 -45
  143. data/lib/active_record/runtime_registry.rb +39 -0
  144. data/lib/active_record/sanitization.rb +24 -19
  145. data/lib/active_record/schema.rb +8 -6
  146. data/lib/active_record/schema_dumper.rb +19 -9
  147. data/lib/active_record/schema_migration.rb +30 -14
  148. data/lib/active_record/scoping/named.rb +1 -0
  149. data/lib/active_record/signed_id.rb +20 -1
  150. data/lib/active_record/statement_cache.rb +7 -7
  151. data/lib/active_record/table_metadata.rb +1 -10
  152. data/lib/active_record/tasks/database_tasks.rb +87 -48
  153. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  154. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  155. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  156. data/lib/active_record/test_fixtures.rb +87 -89
  157. data/lib/active_record/testing/query_assertions.rb +121 -0
  158. data/lib/active_record/timestamp.rb +5 -3
  159. data/lib/active_record/token_for.rb +22 -12
  160. data/lib/active_record/touch_later.rb +1 -1
  161. data/lib/active_record/transaction.rb +132 -0
  162. data/lib/active_record/transactions.rb +70 -14
  163. data/lib/active_record/translation.rb +0 -2
  164. data/lib/active_record/type/serialized.rb +1 -3
  165. data/lib/active_record/type_caster/connection.rb +4 -4
  166. data/lib/active_record/validations/associated.rb +9 -3
  167. data/lib/active_record/validations/uniqueness.rb +14 -10
  168. data/lib/active_record/validations.rb +4 -1
  169. data/lib/active_record.rb +150 -41
  170. data/lib/arel/alias_predication.rb +1 -1
  171. data/lib/arel/collectors/bind.rb +2 -0
  172. data/lib/arel/collectors/composite.rb +7 -0
  173. data/lib/arel/collectors/sql_string.rb +1 -1
  174. data/lib/arel/collectors/substitute_binds.rb +1 -1
  175. data/lib/arel/nodes/binary.rb +0 -6
  176. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  177. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  178. data/lib/arel/nodes/node.rb +4 -3
  179. data/lib/arel/nodes/sql_literal.rb +7 -0
  180. data/lib/arel/nodes.rb +2 -2
  181. data/lib/arel/predications.rb +1 -1
  182. data/lib/arel/select_manager.rb +1 -1
  183. data/lib/arel/tree_manager.rb +8 -3
  184. data/lib/arel/update_manager.rb +2 -1
  185. data/lib/arel/visitors/dot.rb +1 -0
  186. data/lib/arel/visitors/mysql.rb +9 -4
  187. data/lib/arel/visitors/postgresql.rb +1 -12
  188. data/lib/arel/visitors/to_sql.rb +31 -17
  189. data/lib/arel.rb +7 -3
  190. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  191. metadata +18 -12
@@ -125,11 +125,11 @@ module ActiveRecord
125
125
  end
126
126
 
127
127
  def create_all
128
- each_local_configuration do |db_config|
129
- with_temporary_connection(db_config) do
130
- create(db_config)
131
- end
132
- end
128
+ db_config = migration_connection.pool.db_config
129
+
130
+ each_local_configuration { |db_config| create(db_config) }
131
+
132
+ migration_class.establish_connection(db_config)
133
133
  end
134
134
 
135
135
  def setup_initial_database_yaml # :nodoc:
@@ -179,7 +179,7 @@ module ActiveRecord
179
179
  each_current_configuration(env) do |db_config|
180
180
  with_temporary_pool(db_config) do
181
181
  begin
182
- database_initialized = migration_connection.schema_migration.table_exists?
182
+ database_initialized = migration_connection_pool.schema_migration.table_exists?
183
183
  rescue ActiveRecord::NoDatabaseError
184
184
  create(db_config)
185
185
  retry
@@ -192,9 +192,17 @@ module ActiveRecord
192
192
 
193
193
  seed = true
194
194
  end
195
+ end
196
+ end
195
197
 
196
- migrate
197
- dump_schema(db_config) if ActiveRecord.dump_schema_after_migration
198
+ each_current_environment(env) do |environment|
199
+ db_configs_with_versions(environment).sort.each do |version, db_configs|
200
+ db_configs.each do |db_config|
201
+ with_temporary_pool(db_config) do
202
+ migrate(version)
203
+ dump_schema(db_config) if ActiveRecord.dump_schema_after_migration
204
+ end
205
+ end
198
206
  end
199
207
  end
200
208
 
@@ -240,7 +248,7 @@ module ActiveRecord
240
248
 
241
249
  check_target_version
242
250
 
243
- migration_connection.migration_context.migrate(target_version) do |migration|
251
+ migration_connection_pool.migration_context.migrate(target_version) do |migration|
244
252
  if version.blank?
245
253
  scope.blank? || scope == migration.scope
246
254
  else
@@ -250,17 +258,17 @@ module ActiveRecord
250
258
  Migration.write("No migrations ran. (using #{scope} scope)") if scope.present? && migrations_ran.empty?
251
259
  end
252
260
 
253
- migration_connection.schema_cache.clear!
261
+ migration_connection_pool.schema_cache.clear!
254
262
  ensure
255
263
  Migration.verbose = verbose_was
256
264
  end
257
265
 
258
- def db_configs_with_versions(db_configs) # :nodoc:
266
+ def db_configs_with_versions(environment = env) # :nodoc:
259
267
  db_configs_with_versions = Hash.new { |h, k| h[k] = [] }
260
268
 
261
- with_temporary_connection_for_each do |conn|
262
- db_config = conn.pool.db_config
263
- versions_to_run = conn.migration_context.pending_migration_versions
269
+ with_temporary_pool_for_each(env: environment) do |pool|
270
+ db_config = pool.db_config
271
+ versions_to_run = pool.migration_context.pending_migration_versions
264
272
  target_version = ActiveRecord::Tasks::DatabaseTasks.target_version
265
273
 
266
274
  versions_to_run.each do |version|
@@ -273,15 +281,15 @@ module ActiveRecord
273
281
  end
274
282
 
275
283
  def migrate_status
276
- unless migration_connection.schema_migration.table_exists?
284
+ unless migration_connection_pool.schema_migration.table_exists?
277
285
  Kernel.abort "Schema migrations table does not exist yet."
278
286
  end
279
287
 
280
288
  # output
281
- puts "\ndatabase: #{migration_connection.pool.db_config.database}\n\n"
289
+ puts "\ndatabase: #{migration_connection_pool.db_config.database}\n\n"
282
290
  puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
283
291
  puts "-" * 50
284
- migration_connection.migration_context.migrations_status.each do |status, version, name|
292
+ migration_connection_pool.migration_context.migrations_status.each do |status, version, name|
285
293
  puts "#{status.center(8)} #{version.ljust(14)} #{name}"
286
294
  end
287
295
  puts
@@ -362,7 +370,7 @@ module ActiveRecord
362
370
  raise ArgumentError, "unknown format #{format.inspect}"
363
371
  end
364
372
 
365
- migration_connection.internal_metadata.create_table_and_set_flags(db_config.env_name, schema_sha1(file))
373
+ migration_connection_pool.internal_metadata.create_table_and_set_flags(db_config.env_name, schema_sha1(file))
366
374
  ensure
367
375
  Migration.verbose = verbose_was
368
376
  end
@@ -374,11 +382,12 @@ module ActiveRecord
374
382
 
375
383
  return true unless file && File.exist?(file)
376
384
 
377
- with_temporary_connection(db_config) do |connection|
378
- return false unless connection.internal_metadata.enabled?
379
- return false unless connection.internal_metadata.table_exists?
385
+ with_temporary_pool(db_config) do |pool|
386
+ internal_metadata = pool.internal_metadata
387
+ return false unless internal_metadata.enabled?
388
+ return false unless internal_metadata.table_exists?
380
389
 
381
- connection.internal_metadata[:schema_sha1] == schema_sha1(file)
390
+ internal_metadata[:schema_sha1] == schema_sha1(file)
382
391
  end
383
392
  end
384
393
 
@@ -389,7 +398,7 @@ module ActiveRecord
389
398
 
390
399
  with_temporary_pool(db_config, clobber: true) do
391
400
  if schema_up_to_date?(db_config, format, file)
392
- truncate_tables(db_config)
401
+ truncate_tables(db_config) unless ENV["SKIP_TEST_DATABASE_TRUNCATE"]
393
402
  else
394
403
  purge(db_config)
395
404
  load_schema(db_config, format, file)
@@ -411,11 +420,11 @@ module ActiveRecord
411
420
  case format
412
421
  when :ruby
413
422
  File.open(filename, "w:utf-8") do |file|
414
- ActiveRecord::SchemaDumper.dump(migration_connection, file)
423
+ ActiveRecord::SchemaDumper.dump(migration_connection_pool, file)
415
424
  end
416
425
  when :sql
417
426
  structure_dump(db_config, filename)
418
- if migration_connection.schema_migration.table_exists?
427
+ if migration_connection_pool.schema_migration.table_exists?
419
428
  File.open(filename, "a") do |f|
420
429
  f.puts migration_connection.dump_schema_information
421
430
  f.print "\n"
@@ -437,14 +446,26 @@ module ActiveRecord
437
446
  end
438
447
  end
439
448
 
440
- def cache_dump_filename(db_config_name, schema_cache_path: nil)
441
- filename = if ActiveRecord::Base.configurations.primary?(db_config_name)
442
- "schema_cache.yml"
449
+ def cache_dump_filename(db_config_or_name, schema_cache_path: nil)
450
+ if db_config_or_name.is_a?(DatabaseConfigurations::DatabaseConfig)
451
+ schema_cache_path ||
452
+ db_config_or_name.schema_cache_path ||
453
+ schema_cache_env ||
454
+ db_config_or_name.default_schema_cache_path(ActiveRecord::Tasks::DatabaseTasks.db_dir)
443
455
  else
444
- "#{db_config_name}_schema_cache.yml"
445
- end
456
+ ActiveRecord.deprecator.warn(<<~MSG.squish)
457
+ Passing a database name to `cache_dump_filename` is deprecated and will be removed in Rails 8.0. Pass a
458
+ `ActiveRecord::DatabaseConfigurations::DatabaseConfig` object instead.
459
+ MSG
460
+
461
+ filename = if ActiveRecord::Base.configurations.primary?(db_config_or_name)
462
+ "schema_cache.yml"
463
+ else
464
+ "#{db_config_or_name}_schema_cache.yml"
465
+ end
446
466
 
447
- schema_cache_path || ENV["SCHEMA_CACHE"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
467
+ schema_cache_path || schema_cache_env || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
468
+ end
448
469
  end
449
470
 
450
471
  def load_schema_current(format = ActiveRecord.schema_format, file = nil, environment = env)
@@ -476,29 +497,29 @@ module ActiveRecord
476
497
  # Dumps the schema cache in YAML format for the connection into the file
477
498
  #
478
499
  # ==== Examples
479
- # ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, "tmp/schema_dump.yaml")
480
- def dump_schema_cache(conn, filename)
481
- conn.schema_cache.dump_to(filename)
500
+ # ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.lease_connection, "tmp/schema_dump.yaml")
501
+ def dump_schema_cache(conn_or_pool, filename)
502
+ conn_or_pool.schema_cache.dump_to(filename)
482
503
  end
483
504
 
484
505
  def clear_schema_cache(filename)
485
506
  FileUtils.rm_f filename, verbose: false
486
507
  end
487
508
 
488
- def with_temporary_connection_for_each(env: ActiveRecord::Tasks::DatabaseTasks.env, name: nil, clobber: false, &block) # :nodoc:
509
+ def with_temporary_pool_for_each(env: ActiveRecord::Tasks::DatabaseTasks.env, name: nil, clobber: false, &block) # :nodoc:
489
510
  if name
490
511
  db_config = ActiveRecord::Base.configurations.configs_for(env_name: env, name: name)
491
- with_temporary_connection(db_config, clobber: clobber, &block)
512
+ with_temporary_pool(db_config, clobber: clobber, &block)
492
513
  else
493
514
  ActiveRecord::Base.configurations.configs_for(env_name: env, name: name).each do |db_config|
494
- with_temporary_connection(db_config, clobber: clobber, &block)
515
+ with_temporary_pool(db_config, clobber: clobber, &block)
495
516
  end
496
517
  end
497
518
  end
498
519
 
499
- def with_temporary_connection(db_config, clobber: false) # :nodoc:
520
+ def with_temporary_connection(db_config, clobber: false, &block) # :nodoc:
500
521
  with_temporary_pool(db_config, clobber: clobber) do |pool|
501
- yield pool.connection
522
+ pool.with_connection(&block)
502
523
  end
503
524
  end
504
525
 
@@ -507,10 +528,25 @@ module ActiveRecord
507
528
  end
508
529
 
509
530
  def migration_connection # :nodoc:
510
- migration_class.connection
531
+ migration_class.lease_connection
532
+ end
533
+
534
+ def migration_connection_pool # :nodoc:
535
+ migration_class.connection_pool
511
536
  end
512
537
 
513
538
  private
539
+ def schema_cache_env
540
+ if ENV["SCHEMA_CACHE"]
541
+ ActiveRecord.deprecator.warn(<<~MSG.squish)
542
+ Setting `ENV["SCHEMA_CACHE"]` is deprecated and will be removed in Rails 8.0.
543
+ Configure the `:schema_cache_path` in the database configuration instead.
544
+ MSG
545
+
546
+ nil
547
+ end
548
+ end
549
+
514
550
  def with_temporary_pool(db_config, clobber: false)
515
551
  original_db_config = migration_class.connection_db_config
516
552
  pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
@@ -552,10 +588,7 @@ module ActiveRecord
552
588
  end
553
589
 
554
590
  def each_current_configuration(environment, name = nil)
555
- environments = [environment]
556
- environments << "test" if environment == "development" && !ENV["SKIP_TEST_DATABASE"] && !ENV["DATABASE_URL"]
557
-
558
- environments.each do |env|
591
+ each_current_environment(environment) do |env|
559
592
  configs_for(env_name: env).each do |db_config|
560
593
  next if name && name != db_config.name
561
594
 
@@ -564,6 +597,12 @@ module ActiveRecord
564
597
  end
565
598
  end
566
599
 
600
+ def each_current_environment(environment, &block)
601
+ environments = [environment]
602
+ environments << "test" if environment == "development" && !ENV["SKIP_TEST_DATABASE"] && !ENV["DATABASE_URL"]
603
+ environments.each(&block)
604
+ end
605
+
567
606
  def each_local_configuration
568
607
  configs_for.each do |db_config|
569
608
  next unless db_config.database
@@ -603,11 +642,11 @@ module ActiveRecord
603
642
 
604
643
  def check_current_protected_environment!(db_config)
605
644
  with_temporary_pool(db_config) do |pool|
606
- connection = pool.connection
607
- current = connection.migration_context.current_environment
608
- stored = connection.migration_context.last_stored_environment
645
+ migration_context = pool.migration_context
646
+ current = migration_context.current_environment
647
+ stored = migration_context.last_stored_environment
609
648
 
610
- if connection.migration_context.protected_environment?
649
+ if migration_context.protected_environment?
611
650
  raise ActiveRecord::ProtectedEnvironmentError.new(stored)
612
651
  end
613
652
 
@@ -71,7 +71,7 @@ module ActiveRecord
71
71
  attr_reader :db_config, :configuration_hash
72
72
 
73
73
  def connection
74
- ActiveRecord::Base.connection
74
+ ActiveRecord::Base.lease_connection
75
75
  end
76
76
 
77
77
  def establish_connection(config = db_config)
@@ -88,7 +88,7 @@ module ActiveRecord
88
88
  attr_reader :db_config, :configuration_hash
89
89
 
90
90
  def connection
91
- ActiveRecord::Base.connection
91
+ ActiveRecord::Base.lease_connection
92
92
  end
93
93
 
94
94
  def establish_connection(config = db_config)
@@ -66,11 +66,12 @@ module ActiveRecord
66
66
  attr_reader :db_config, :root
67
67
 
68
68
  def connection
69
- ActiveRecord::Base.connection
69
+ ActiveRecord::Base.lease_connection
70
70
  end
71
71
 
72
72
  def establish_connection(config = db_config)
73
73
  ActiveRecord::Base.establish_connection(config)
74
+ connection.connect!
74
75
  end
75
76
 
76
77
  def run_cmd(cmd, args, out)
@@ -13,6 +13,7 @@ module ActiveRecord
13
13
 
14
14
  def after_teardown # :nodoc:
15
15
  super
16
+ ensure
16
17
  teardown_fixtures
17
18
  end
18
19
 
@@ -52,20 +53,6 @@ module ActiveRecord
52
53
  self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
53
54
  end
54
55
 
55
- def fixture_path # :nodoc:
56
- ActiveRecord.deprecator.warn(<<~WARNING)
57
- TestFixtures.fixture_path is deprecated and will be removed in Rails 7.2. Use .fixture_paths instead.
58
- If multiple fixture paths have been configured with .fixture_paths, then .fixture_path will just return
59
- the first path.
60
- WARNING
61
- fixture_paths.first
62
- end
63
-
64
- def fixture_path=(path) # :nodoc:
65
- ActiveRecord.deprecator.warn("TestFixtures.fixture_path= is deprecated and will be removed in Rails 7.2. Use .fixture_paths= instead.")
66
- self.fixture_paths = Array(path)
67
- end
68
-
69
56
  def fixtures(*fixture_set_names)
70
57
  if fixture_set_names.first == :all
71
58
  raise StandardError, "No fixture path found. Please set `#{self}.fixture_paths`." if fixture_paths.blank?
@@ -78,7 +65,7 @@ module ActiveRecord
78
65
  fixture_set_names = fixture_set_names.flatten.map(&:to_s)
79
66
  end
80
67
 
81
- self.fixture_table_names |= fixture_set_names
68
+ self.fixture_table_names = (fixture_table_names | fixture_set_names).sort
82
69
  setup_fixture_accessors(fixture_set_names)
83
70
  end
84
71
 
@@ -109,45 +96,76 @@ module ActiveRecord
109
96
  end
110
97
  end
111
98
 
112
- def fixture_path # :nodoc:
113
- ActiveRecord.deprecator.warn(<<~WARNING)
114
- TestFixtures#fixture_path is deprecated and will be removed in Rails 7.2. Use #fixture_paths instead.
115
- If multiple fixture paths have been configured with #fixture_paths, then #fixture_path will just return
116
- the first path.
117
- WARNING
118
- fixture_paths.first
99
+ # Generic fixture accessor for fixture names that may conflict with other methods.
100
+ #
101
+ # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
102
+ # assert_equal "Ruby on Rails", fixture(:web_sites, :rubyonrails).name
103
+ def fixture(fixture_set_name, *fixture_names)
104
+ active_record_fixture(fixture_set_name, *fixture_names)
119
105
  end
120
106
 
121
- def run_in_transaction?
122
- use_transactional_tests &&
123
- !self.class.uses_transaction?(name)
124
- end
125
-
126
- def setup_fixtures(config = ActiveRecord::Base)
127
- if pre_loaded_fixtures && !use_transactional_tests
128
- raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
107
+ private
108
+ def run_in_transaction?
109
+ use_transactional_tests &&
110
+ !self.class.uses_transaction?(name)
129
111
  end
130
112
 
131
- @fixture_cache = {}
132
- @fixture_connections = []
133
- @@already_loaded_fixtures ||= {}
134
- @connection_subscriber = nil
135
- @saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
113
+ def setup_fixtures(config = ActiveRecord::Base)
114
+ if pre_loaded_fixtures && !use_transactional_tests
115
+ raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
116
+ end
117
+
118
+ @fixture_cache = {}
119
+ @fixture_cache_key = [self.class.fixture_table_names.dup, self.class.fixture_paths.dup, self.class.fixture_class_names.dup]
120
+ @fixture_connection_pools = []
121
+ @@already_loaded_fixtures ||= {}
122
+ @connection_subscriber = nil
123
+ @saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
124
+
125
+ if run_in_transaction?
126
+ # Load fixtures once and begin transaction.
127
+ @loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key]
128
+ unless @loaded_fixtures
129
+ @@already_loaded_fixtures.clear
130
+ @loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key] = load_fixtures(config)
131
+ end
136
132
 
137
- # Load fixtures once and begin transaction.
138
- if run_in_transaction?
139
- if @@already_loaded_fixtures[self.class]
140
- @loaded_fixtures = @@already_loaded_fixtures[self.class]
133
+ setup_transactional_fixtures
141
134
  else
135
+ # Load fixtures for every test.
136
+ ActiveRecord::FixtureSet.reset_cache
137
+ invalidate_already_loaded_fixtures
142
138
  @loaded_fixtures = load_fixtures(config)
143
- @@already_loaded_fixtures[self.class] = @loaded_fixtures
144
139
  end
145
140
 
141
+ # Instantiate fixtures for every test if requested.
142
+ instantiate_fixtures if use_instantiated_fixtures
143
+ end
144
+
145
+ def teardown_fixtures
146
+ # Rollback changes if a transaction is active.
147
+ if run_in_transaction?
148
+ teardown_transactional_fixtures
149
+ else
150
+ ActiveRecord::FixtureSet.reset_cache
151
+ invalidate_already_loaded_fixtures
152
+ end
153
+
154
+ ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
155
+ end
156
+
157
+ def invalidate_already_loaded_fixtures
158
+ @@already_loaded_fixtures.clear
159
+ end
160
+
161
+ def setup_transactional_fixtures
162
+ setup_shared_connection_pool
163
+
146
164
  # Begin transactions for connections already established
147
- @fixture_connections = enlist_fixture_connections
148
- @fixture_connections.each do |connection|
149
- connection.begin_transaction joinable: false, _lazy: false
150
- connection.pool.lock_thread = true if lock_threads
165
+ @fixture_connection_pools = ActiveRecord::Base.connection_handler.connection_pool_list(:writing)
166
+ @fixture_connection_pools.each do |pool|
167
+ pool.pin_connection!(lock_threads)
168
+ pool.lease_connection
151
169
  end
152
170
 
153
171
  # When connections are established in the future, begin a transaction too
@@ -156,59 +174,31 @@ module ActiveRecord
156
174
  shard = payload[:shard] if payload.key?(:shard)
157
175
 
158
176
  if connection_name
159
- begin
160
- connection = ActiveRecord::Base.connection_handler.retrieve_connection(connection_name, shard: shard)
161
- rescue ConnectionNotEstablished
162
- connection = nil
163
- end
164
-
165
- if connection
177
+ pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard: shard)
178
+ if pool
166
179
  setup_shared_connection_pool
167
180
 
168
- if !@fixture_connections.include?(connection)
169
- connection.begin_transaction joinable: false, _lazy: false
170
- connection.pool.lock_thread = true if lock_threads
171
- @fixture_connections << connection
181
+ unless @fixture_connection_pools.include?(pool)
182
+ pool.pin_connection!(lock_threads)
183
+ pool.lease_connection
184
+ @fixture_connection_pools << pool
172
185
  end
173
186
  end
174
187
  end
175
188
  end
176
-
177
- # Load fixtures for every test.
178
- else
179
- ActiveRecord::FixtureSet.reset_cache
180
- @@already_loaded_fixtures[self.class] = nil
181
- @loaded_fixtures = load_fixtures(config)
182
189
  end
183
190
 
184
- # Instantiate fixtures for every test if requested.
185
- instantiate_fixtures if use_instantiated_fixtures
186
- end
187
-
188
- def teardown_fixtures
189
- # Rollback changes if a transaction is active.
190
- if run_in_transaction?
191
+ def teardown_transactional_fixtures
191
192
  ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
192
- @fixture_connections.each do |connection|
193
- connection.rollback_transaction if connection.transaction_open?
194
- connection.pool.lock_thread = false
193
+ unless @fixture_connection_pools.map(&:unpin_connection!).all?
194
+ # Something caused the transaction to be committed or rolled back
195
+ # We can no longer trust the database is in a clean state.
196
+ @@already_loaded_fixtures.clear
195
197
  end
196
- @fixture_connections.clear
198
+ @fixture_connection_pools.clear
197
199
  teardown_shared_connection_pool
198
- else
199
- ActiveRecord::FixtureSet.reset_cache
200
200
  end
201
201
 
202
- ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
203
- end
204
-
205
- def enlist_fixture_connections
206
- setup_shared_connection_pool
207
-
208
- ActiveRecord::Base.connection_handler.connection_pool_list(:writing).map(&:connection)
209
- end
210
-
211
- private
212
202
  # Shares the writing connection pool with connections on
213
203
  # other handlers.
214
204
  #
@@ -271,22 +261,30 @@ module ActiveRecord
271
261
  use_instantiated_fixtures != :no_instances
272
262
  end
273
263
 
274
- def method_missing(name, *args, **kwargs, &block)
275
- if fs_name = fixture_sets[name.to_s]
276
- access_fixture(fs_name, *args, **kwargs, &block)
264
+ def method_missing(method, ...)
265
+ if fixture_sets.key?(method.name)
266
+ active_record_fixture(method, ...)
277
267
  else
278
268
  super
279
269
  end
280
270
  end
281
271
 
282
- def respond_to_missing?(name, include_private = false)
283
- if include_private && fixture_sets.key?(name.to_s)
272
+ def respond_to_missing?(method, include_private = false)
273
+ if include_private && fixture_sets.key?(method.name)
284
274
  true
285
275
  else
286
276
  super
287
277
  end
288
278
  end
289
279
 
280
+ def active_record_fixture(fixture_set_name, *fixture_names)
281
+ if fs_name = fixture_sets[fixture_set_name.name]
282
+ access_fixture(fs_name, *fixture_names)
283
+ else
284
+ raise StandardError, "No fixture set named '#{fixture_set_name.inspect}'"
285
+ end
286
+ end
287
+
290
288
  def access_fixture(fs_name, *fixture_names)
291
289
  force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
292
290
  return_single_record = fixture_names.size == 1