activerecord 7.1.6 → 7.2.3

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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +839 -2248
  3. data/README.rdoc +16 -16
  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 +31 -23
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  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 +16 -8
  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 +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +59 -292
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +1 -13
  31. data/lib/active_record/attribute_methods/serialization.rb +5 -25
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  33. data/lib/active_record/attribute_methods.rb +51 -60
  34. data/lib/active_record/attributes.rb +93 -68
  35. data/lib/active_record/autosave_association.rb +25 -32
  36. data/lib/active_record/base.rb +4 -5
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +294 -72
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -75
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -2
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -6
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +46 -44
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +53 -15
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +19 -18
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  58. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +30 -8
  60. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +36 -26
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +133 -78
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +68 -49
  76. data/lib/active_record/core.rb +112 -44
  77. data/lib/active_record/counter_cache.rb +19 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  79. data/lib/active_record/database_configurations/database_config.rb +19 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +42 -18
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +25 -5
  87. data/lib/active_record/encryption/encryptor.rb +35 -19
  88. data/lib/active_record/encryption/key_provider.rb +1 -1
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/enum.rb +31 -13
  94. data/lib/active_record/errors.rb +49 -23
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixture_set/table_row.rb +19 -2
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +8 -4
  99. data/lib/active_record/gem_version.rb +2 -2
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +7 -6
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/message_pack.rb +1 -1
  107. data/lib/active_record/migration/command_recorder.rb +2 -3
  108. data/lib/active_record/migration/compatibility.rb +5 -3
  109. data/lib/active_record/migration/default_strategy.rb +4 -5
  110. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  111. data/lib/active_record/migration.rb +87 -77
  112. data/lib/active_record/model_schema.rb +31 -68
  113. data/lib/active_record/nested_attributes.rb +11 -3
  114. data/lib/active_record/normalization.rb +3 -7
  115. data/lib/active_record/persistence.rb +30 -352
  116. data/lib/active_record/query_cache.rb +19 -8
  117. data/lib/active_record/query_logs.rb +19 -0
  118. data/lib/active_record/querying.rb +25 -13
  119. data/lib/active_record/railtie.rb +39 -57
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +42 -44
  122. data/lib/active_record/reflection.rb +98 -36
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +14 -8
  125. data/lib/active_record/relation/calculations.rb +127 -89
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +26 -12
  128. data/lib/active_record/relation/merger.rb +4 -6
  129. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  130. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  131. data/lib/active_record/relation/predicate_builder.rb +3 -3
  132. data/lib/active_record/relation/query_attribute.rb +1 -1
  133. data/lib/active_record/relation/query_methods.rb +238 -65
  134. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  135. data/lib/active_record/relation/spawn_methods.rb +2 -18
  136. data/lib/active_record/relation/where_clause.rb +15 -21
  137. data/lib/active_record/relation.rb +508 -74
  138. data/lib/active_record/result.rb +31 -44
  139. data/lib/active_record/runtime_registry.rb +39 -0
  140. data/lib/active_record/sanitization.rb +24 -19
  141. data/lib/active_record/schema.rb +8 -6
  142. data/lib/active_record/schema_dumper.rb +48 -20
  143. data/lib/active_record/schema_migration.rb +30 -14
  144. data/lib/active_record/scoping/named.rb +1 -0
  145. data/lib/active_record/secure_token.rb +3 -3
  146. data/lib/active_record/signed_id.rb +27 -7
  147. data/lib/active_record/statement_cache.rb +7 -7
  148. data/lib/active_record/table_metadata.rb +1 -10
  149. data/lib/active_record/tasks/database_tasks.rb +69 -41
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +8 -1
  152. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  153. data/lib/active_record/test_fixtures.rb +86 -89
  154. data/lib/active_record/testing/query_assertions.rb +121 -0
  155. data/lib/active_record/timestamp.rb +2 -2
  156. data/lib/active_record/token_for.rb +22 -12
  157. data/lib/active_record/touch_later.rb +1 -1
  158. data/lib/active_record/transaction.rb +132 -0
  159. data/lib/active_record/transactions.rb +73 -15
  160. data/lib/active_record/translation.rb +0 -2
  161. data/lib/active_record/type/serialized.rb +1 -3
  162. data/lib/active_record/type_caster/connection.rb +4 -4
  163. data/lib/active_record/validations/associated.rb +9 -3
  164. data/lib/active_record/validations/uniqueness.rb +15 -10
  165. data/lib/active_record/validations.rb +4 -1
  166. data/lib/active_record.rb +148 -39
  167. data/lib/arel/alias_predication.rb +1 -1
  168. data/lib/arel/collectors/bind.rb +3 -1
  169. data/lib/arel/collectors/composite.rb +7 -0
  170. data/lib/arel/collectors/sql_string.rb +1 -1
  171. data/lib/arel/collectors/substitute_binds.rb +1 -1
  172. data/lib/arel/crud.rb +2 -0
  173. data/lib/arel/delete_manager.rb +5 -0
  174. data/lib/arel/nodes/binary.rb +0 -6
  175. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  176. data/lib/arel/nodes/delete_statement.rb +4 -2
  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/update_statement.rb +4 -2
  181. data/lib/arel/nodes.rb +2 -2
  182. data/lib/arel/predications.rb +1 -1
  183. data/lib/arel/select_manager.rb +7 -3
  184. data/lib/arel/tree_manager.rb +3 -2
  185. data/lib/arel/update_manager.rb +7 -1
  186. data/lib/arel/visitors/dot.rb +3 -0
  187. data/lib/arel/visitors/mysql.rb +9 -4
  188. data/lib/arel/visitors/postgresql.rb +1 -12
  189. data/lib/arel/visitors/sqlite.rb +25 -0
  190. data/lib/arel/visitors/to_sql.rb +31 -16
  191. data/lib/arel.rb +7 -3
  192. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  193. metadata +16 -10
@@ -1,9 +1,130 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/string/filters"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  extend ActiveSupport::Autoload
6
8
 
9
+ @adapters = {}
10
+
11
+ class << self
12
+ # Registers a custom database adapter.
13
+ #
14
+ # Can also be used to define aliases.
15
+ #
16
+ # == Example
17
+ #
18
+ # ActiveRecord::ConnectionAdapters.register("megadb", "MegaDB::ActiveRecordAdapter", "mega_db/active_record_adapter")
19
+ #
20
+ # ActiveRecord::ConnectionAdapters.register("mysql", "ActiveRecord::ConnectionAdapters::TrilogyAdapter", "active_record/connection_adapters/trilogy_adapter")
21
+ #
22
+ def register(name, class_name, path = class_name.underscore)
23
+ @adapters[name.to_s] = [class_name, path]
24
+ end
25
+
26
+ def resolve(adapter_name) # :nodoc:
27
+ # Require the adapter itself and give useful feedback about
28
+ # 1. Missing adapter gems.
29
+ # 2. Incorrectly registered adapters.
30
+ # 3. Adapter gems' missing dependencies.
31
+ class_name, path_to_adapter = @adapters[adapter_name.to_s]
32
+
33
+ unless class_name
34
+ # To provide better error messages for adapters expecting the pre-7.2 adapter registration API, we attempt
35
+ # to load the adapter file from the old location which was required by convention, and then raise an error
36
+ # describing how to upgrade the adapter to the new API.
37
+ legacy_adapter_path = "active_record/connection_adapters/#{adapter_name}_adapter"
38
+ legacy_adapter_connection_method_name = "#{adapter_name}_connection".to_sym
39
+
40
+ begin
41
+ require legacy_adapter_path
42
+ # If we reach here it means we found the found a file that may be the legacy adapter and should raise.
43
+ if ActiveRecord::ConnectionHandling.method_defined?(legacy_adapter_connection_method_name)
44
+ # If we find the connection method then we care certain it is a legacy adapter.
45
+ deprecation_message = <<~MSG.squish
46
+ Database configuration specifies '#{adapter_name}' adapter but that adapter has not been registered.
47
+ Rails 7.2 has changed the way Active Record database adapters are loaded. The adapter needs to be
48
+ updated to register itself rather than being loaded by convention.
49
+ Ensure that the adapter in the Gemfile is at the latest version. If it is, then the adapter may need to
50
+ be modified.
51
+ See:
52
+ https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-c-register
53
+ MSG
54
+
55
+ exception_message = <<~MSG.squish
56
+ Database configuration specifies '#{adapter_name}' adapter but that adapter has not been registered.
57
+ Ensure that the adapter in the Gemfile is at the latest version. If it is, then the adapter may need to
58
+ be modified.
59
+ MSG
60
+ else
61
+ # If we do not find the connection method we are much less certain it is a legacy adapter. Even though the
62
+ # file exists in the location defined by convenntion, it does not necessarily mean that file is supposed
63
+ # to define the adapter the legacy way. So raise an error that explains both possibilities.
64
+ deprecation_message = <<~MSG.squish
65
+ Database configuration specifies nonexistent '#{adapter_name}' adapter.
66
+ Available adapters are: #{@adapters.keys.sort.join(", ")}.
67
+ Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary
68
+ adapter gem to your Gemfile if it's not in the list of available adapters.
69
+ Rails 7.2 has changed the way Active Record database adapters are loaded. Ensure that the adapter in
70
+ the Gemfile is at the latest version. If it is up to date, the adapter may need to be modified.
71
+ See:
72
+ https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-c-register
73
+ MSG
74
+
75
+ exception_message = <<~MSG.squish
76
+ Database configuration specifies nonexistent '#{adapter_name}' adapter.
77
+ Available adapters are: #{@adapters.keys.sort.join(", ")}.
78
+ Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary
79
+ adapter gem to your Gemfile and that it is at its latest version. If it is up to date, the adapter may
80
+ need to be modified.
81
+ MSG
82
+ end
83
+
84
+ ActiveRecord.deprecator.warn(deprecation_message)
85
+ raise AdapterNotFound, exception_message
86
+ rescue LoadError => error
87
+ # The adapter was not found in the legacy location so fall through to the error handling for a missing adapter.
88
+ end
89
+
90
+ raise AdapterNotFound, <<~MSG.squish
91
+ Database configuration specifies nonexistent '#{adapter_name}' adapter.
92
+ Available adapters are: #{@adapters.keys.sort.join(", ")}.
93
+ Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary
94
+ adapter gem to your Gemfile if it's not in the list of available adapters.
95
+ MSG
96
+ end
97
+
98
+ unless Object.const_defined?(class_name)
99
+ begin
100
+ require path_to_adapter
101
+ rescue LoadError => error
102
+ # We couldn't require the adapter itself.
103
+ if error.path == path_to_adapter
104
+ # We can assume here that a non-builtin adapter was specified and the path
105
+ # registered by the adapter gem is incorrect.
106
+ raise LoadError, "Error loading the '#{adapter_name}' Active Record adapter. Ensure that the path registered by the adapter gem is correct. #{error.message}", error.backtrace
107
+ else
108
+ # Bubbled up from the adapter require. Prefix the exception message
109
+ # with some guidance about how to address it and reraise.
110
+ raise LoadError, "Error loading the '#{adapter_name}' Active Record adapter. Missing a gem it depends on? #{error.message}", error.backtrace
111
+ end
112
+ end
113
+ end
114
+
115
+ begin
116
+ Object.const_get(class_name)
117
+ rescue NameError => error
118
+ raise AdapterNotFound, "Could not load the #{class_name} Active Record adapter (#{error.message})."
119
+ end
120
+ end
121
+ end
122
+
123
+ register "sqlite3", "ActiveRecord::ConnectionAdapters::SQLite3Adapter", "active_record/connection_adapters/sqlite3_adapter"
124
+ register "mysql2", "ActiveRecord::ConnectionAdapters::Mysql2Adapter", "active_record/connection_adapters/mysql2_adapter"
125
+ register "trilogy", "ActiveRecord::ConnectionAdapters::TrilogyAdapter", "active_record/connection_adapters/trilogy_adapter"
126
+ register "postgresql", "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter", "active_record/connection_adapters/postgresql_adapter"
127
+
7
128
  eager_autoload do
8
129
  autoload :AbstractAdapter
9
130
  end
@@ -170,9 +170,11 @@ module ActiveRecord
170
170
  prevent_writes = true if role == ActiveRecord.reading_role
171
171
 
172
172
  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes)
173
- yield
174
- ensure
175
- connected_to_stack.pop
173
+ begin
174
+ yield
175
+ ensure
176
+ connected_to_stack.pop
177
+ end
176
178
  end
177
179
 
178
180
  # Use a specified connection.
@@ -243,22 +245,64 @@ module ActiveRecord
243
245
  # Clears the query cache for all connections associated with the current thread.
244
246
  def clear_query_caches_for_current_thread
245
247
  connection_handler.each_connection_pool do |pool|
246
- pool.connection.clear_query_cache if pool.active_connection?
248
+ pool.clear_query_cache
247
249
  end
248
250
  end
249
251
 
250
252
  # Returns the connection currently associated with the class. This can
251
253
  # also be used to "borrow" the connection to do database work unrelated
252
254
  # to any of the specific Active Records.
255
+ # The connection will remain leased for the entire duration of the request
256
+ # or job, or until +#release_connection+ is called.
257
+ def lease_connection
258
+ connection_pool.lease_connection
259
+ end
260
+
261
+ # Soft deprecated. Use +#with_connection+ or +#lease_connection+ instead.
253
262
  def connection
254
- retrieve_connection
263
+ pool = connection_pool
264
+ if pool.permanent_lease?
265
+ case ActiveRecord.permanent_connection_checkout
266
+ when :deprecated
267
+ ActiveRecord.deprecator.warn <<~MESSAGE
268
+ Called deprecated `ActiveRecord::Base.connection` method.
269
+
270
+ Either use `with_connection` or `lease_connection`.
271
+ MESSAGE
272
+ when :disallowed
273
+ raise ActiveRecordError, <<~MESSAGE
274
+ Called deprecated `ActiveRecord::Base.connection` method.
275
+
276
+ Either use `with_connection` or `lease_connection`.
277
+ MESSAGE
278
+ end
279
+ pool.lease_connection
280
+ else
281
+ pool.active_connection
282
+ end
283
+ end
284
+
285
+ # Return the currently leased connection into the pool
286
+ def release_connection
287
+ connection_pool.release_connection
288
+ end
289
+
290
+ # Checkouts a connection from the pool, yield it and then check it back in.
291
+ # If a connection was already leased via #lease_connection or a parent call to
292
+ # #with_connection, that same connection is yieled.
293
+ # If #lease_connection is called inside the block, the connection won't be checked
294
+ # back in.
295
+ # If #connection is called inside the block, the connection won't be checked back in
296
+ # unless the +prevent_permanent_checkout+ argument is set to +true+.
297
+ def with_connection(prevent_permanent_checkout: false, &block)
298
+ connection_pool.with_connection(prevent_permanent_checkout: prevent_permanent_checkout, &block)
255
299
  end
256
300
 
257
301
  attr_writer :connection_specification_name
258
302
 
259
303
  # Returns the connection specification name from the current class or its parent.
260
304
  def connection_specification_name
261
- if !defined?(@connection_specification_name) || @connection_specification_name.nil?
305
+ if @connection_specification_name.nil?
262
306
  return self == Base ? Base.name : superclass.connection_specification_name
263
307
  end
264
308
  @connection_specification_name
@@ -279,8 +323,12 @@ module ActiveRecord
279
323
  connection_pool.db_config
280
324
  end
281
325
 
326
+ def adapter_class # :nodoc:
327
+ connection_pool.db_config.adapter_class
328
+ end
329
+
282
330
  def connection_pool
283
- connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard) || raise(ConnectionNotEstablished)
331
+ connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard, strict: true)
284
332
  end
285
333
 
286
334
  def retrieve_connection
@@ -292,16 +340,9 @@ module ActiveRecord
292
340
  connection_handler.connected?(connection_specification_name, role: current_role, shard: current_shard)
293
341
  end
294
342
 
295
- def remove_connection(name = nil)
296
- if name
297
- ActiveRecord.deprecator.warn(<<-MSG.squish)
298
- The name argument for `#remove_connection` is deprecated without replacement
299
- and will be removed in Rails 7.2. `#remove_connection` should always be called
300
- on the connection class directly, which makes the name argument obsolete.
301
- MSG
302
- end
343
+ def remove_connection
344
+ name = @connection_specification_name if defined?(@connection_specification_name)
303
345
 
304
- name ||= @connection_specification_name if defined?(@connection_specification_name)
305
346
  # if removing a connection that has a pool, we reset the
306
347
  # connection_specification_name so it will use the parent
307
348
  # pool.
@@ -312,39 +353,15 @@ module ActiveRecord
312
353
  connection_handler.remove_connection_pool(name, role: current_role, shard: current_shard)
313
354
  end
314
355
 
315
- def clear_cache! # :nodoc:
316
- connection.schema_cache.clear!
317
- end
318
-
319
- def clear_active_connections!(role = nil)
320
- deprecation_for_delegation(__method__)
321
- connection_handler.clear_active_connections!(role)
356
+ def schema_cache # :nodoc:
357
+ connection_pool.schema_cache
322
358
  end
323
359
 
324
- def clear_reloadable_connections!(role = nil)
325
- deprecation_for_delegation(__method__)
326
- connection_handler.clear_reloadable_connections!(role)
327
- end
328
-
329
- def clear_all_connections!(role = nil)
330
- deprecation_for_delegation(__method__)
331
- connection_handler.clear_all_connections!(role)
332
- end
333
-
334
- def flush_idle_connections!(role = nil)
335
- deprecation_for_delegation(__method__)
336
- connection_handler.flush_idle_connections!(role)
360
+ def clear_cache! # :nodoc:
361
+ connection_pool.schema_cache.clear!
337
362
  end
338
363
 
339
364
  private
340
- def deprecation_for_delegation(method)
341
- ActiveRecord.deprecator.warn(<<-MSG.squish)
342
- Calling `ActiveRecord::Base.#{method} is deprecated. Please
343
- call the method directly on the connection handler; for
344
- example: `ActiveRecord::Base.connection_handler.#{method}`.
345
- MSG
346
- end
347
-
348
365
  def resolve_config_for_connection(config_or_env)
349
366
  raise "Anonymous class is not allowed." unless name
350
367
 
@@ -358,11 +375,13 @@ module ActiveRecord
358
375
  prevent_writes = true if role == ActiveRecord.reading_role
359
376
 
360
377
  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
361
- return_value = yield
362
- return_value.load if return_value.is_a? ActiveRecord::Relation
363
- return_value
364
- ensure
365
- self.connected_to_stack.pop
378
+ begin
379
+ return_value = yield
380
+ return_value.load if return_value.is_a? ActiveRecord::Relation
381
+ return_value
382
+ ensure
383
+ self.connected_to_stack.pop
384
+ end
366
385
  end
367
386
 
368
387
  def append_to_connected_to_stack(entry)
@@ -73,7 +73,7 @@ module ActiveRecord
73
73
  end
74
74
  self.configurations = {}
75
75
 
76
- # Returns fully resolved ActiveRecord::DatabaseConfigurations object
76
+ # Returns a fully resolved ActiveRecord::DatabaseConfigurations object.
77
77
  def self.configurations
78
78
  @@configurations
79
79
  end
@@ -102,6 +102,21 @@ module ActiveRecord
102
102
 
103
103
  class_attribute :shard_selector, instance_accessor: false, default: nil
104
104
 
105
+ ##
106
+ # :singleton-method:
107
+ #
108
+ # Specifies the attributes that will be included in the output of the
109
+ # #inspect method:
110
+ #
111
+ # Post.attributes_for_inspect = [:id, :title]
112
+ # Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">"
113
+ #
114
+ # When set to `:all` inspect will list all the record's attributes:
115
+ #
116
+ # Post.attributes_for_inspect = :all
117
+ # Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
118
+ class_attribute :attributes_for_inspect, instance_accessor: false, default: :all
119
+
105
120
  def self.application_record_class? # :nodoc:
106
121
  if ActiveRecord.application_record_class
107
122
  self == ActiveRecord.application_record_class
@@ -350,8 +365,8 @@ module ActiveRecord
350
365
  super
351
366
  elsif abstract_class?
352
367
  "#{super}(abstract)"
353
- elsif !connected?
354
- "#{super} (call '#{super}.connection' to establish a connection)"
368
+ elsif !schema_loaded? && !connected?
369
+ "#{super} (call '#{super}.load_schema' to load schema informations)"
355
370
  elsif table_exists?
356
371
  attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
357
372
  "#{super}(#{attr_list})"
@@ -373,7 +388,7 @@ module ActiveRecord
373
388
  TypeCaster::Map.new(self)
374
389
  end
375
390
 
376
- def cached_find_by_statement(key, &block) # :nodoc:
391
+ def cached_find_by_statement(connection, key, &block) # :nodoc:
377
392
  cache = @find_by_statement_cache[connection.prepared_statements]
378
393
  cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
379
394
  end
@@ -416,21 +431,23 @@ module ActiveRecord
416
431
  end
417
432
 
418
433
  def cached_find_by(keys, values)
419
- statement = cached_find_by_statement(keys) { |params|
420
- wheres = keys.index_with do |key|
421
- if key.is_a?(Array)
422
- [key.map { params.bind }]
423
- else
424
- params.bind
434
+ with_connection do |connection|
435
+ statement = cached_find_by_statement(connection, keys) { |params|
436
+ wheres = keys.index_with do |key|
437
+ if key.is_a?(Array)
438
+ [key.map { params.bind }]
439
+ else
440
+ params.bind
441
+ end
425
442
  end
426
- end
427
- where(wheres).limit(1)
428
- }
443
+ where(wheres).limit(1)
444
+ }
429
445
 
430
- begin
431
- statement.execute(values.flatten, connection).first
432
- rescue TypeError
433
- raise ActiveRecord::StatementInvalid
446
+ begin
447
+ statement.execute(values.flatten, connection, allow_retry: true).first
448
+ rescue TypeError
449
+ raise ActiveRecord::StatementInvalid
450
+ end
434
451
  end
435
452
  end
436
453
  end
@@ -450,7 +467,7 @@ module ActiveRecord
450
467
  init_internals
451
468
  initialize_internals_callback
452
469
 
453
- assign_attributes(attributes) if attributes
470
+ super
454
471
 
455
472
  yield self if block_given?
456
473
  _run_initialize_callbacks
@@ -568,7 +585,7 @@ module ActiveRecord
568
585
  #
569
586
  # topic = Topic.new(title: "Budget", author_name: "Jason")
570
587
  # topic.slice(:title, :author_name)
571
- # => { "title" => "Budget", "author_name" => "Jason" }
588
+ # # => { "title" => "Budget", "author_name" => "Jason" }
572
589
  #
573
590
  #--
574
591
  # Implemented by ActiveModel::Access#slice.
@@ -582,7 +599,7 @@ module ActiveRecord
582
599
  #
583
600
  # topic = Topic.new(title: "Budget", author_name: "Jason")
584
601
  # topic.values_at(:title, :author_name)
585
- # => ["Budget", "Jason"]
602
+ # # => ["Budget", "Jason"]
586
603
  #
587
604
  #--
588
605
  # Implemented by ActiveModel::Access#values_at.
@@ -659,12 +676,14 @@ module ActiveRecord
659
676
  # Sets the record to strict_loading mode. This will raise an error
660
677
  # if the record tries to lazily load an association.
661
678
  #
679
+ # NOTE: Strict loading is disabled during validation in order to let the record validate its association.
680
+ #
662
681
  # user = User.first
663
682
  # user.strict_loading! # => true
664
683
  # user.address.city
665
- # => ActiveRecord::StrictLoadingViolationError
684
+ # # => ActiveRecord::StrictLoadingViolationError
666
685
  # user.comments.to_a
667
- # => ActiveRecord::StrictLoadingViolationError
686
+ # # => ActiveRecord::StrictLoadingViolationError
668
687
  #
669
688
  # ==== Parameters
670
689
  #
@@ -684,7 +703,7 @@ module ActiveRecord
684
703
  # user.address.city # => "Tatooine"
685
704
  # user.comments.to_a # => [#<Comment:0x00...]
686
705
  # user.comments.first.ratings.to_a
687
- # => ActiveRecord::StrictLoadingViolationError
706
+ # # => ActiveRecord::StrictLoadingViolationError
688
707
  def strict_loading!(value = true, mode: :all)
689
708
  unless [:all, :n_plus_one_only].include?(mode)
690
709
  raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided."
@@ -706,11 +725,29 @@ module ActiveRecord
706
725
  @strict_loading_mode == :all
707
726
  end
708
727
 
709
- # Marks this record as read only.
728
+ # Prevents records from being written to the database:
729
+ #
730
+ # customer = Customer.new
731
+ # customer.readonly!
732
+ # customer.save # raises ActiveRecord::ReadOnlyRecord
733
+ #
734
+ # customer = Customer.first
735
+ # customer.readonly!
736
+ # customer.update(name: 'New Name') # raises ActiveRecord::ReadOnlyRecord
737
+ #
738
+ # Read-only records cannot be deleted from the database either:
710
739
  #
711
740
  # customer = Customer.first
712
741
  # customer.readonly!
713
- # customer.save # Raises an ActiveRecord::ReadOnlyRecord
742
+ # customer.destroy # raises ActiveRecord::ReadOnlyRecord
743
+ #
744
+ # Please, note that the objects themselves are still mutable in memory:
745
+ #
746
+ # customer = Customer.new
747
+ # customer.readonly!
748
+ # customer.name = 'New Name' # OK
749
+ #
750
+ # but you won't be able to persist the changes.
714
751
  def readonly!
715
752
  @readonly = true
716
753
  end
@@ -719,21 +756,28 @@ module ActiveRecord
719
756
  self.class.connection_handler
720
757
  end
721
758
 
722
- # Returns the contents of the record as a nicely formatted string.
759
+ # Returns the attributes of the record as a nicely formatted string.
760
+ #
761
+ # Post.first.inspect
762
+ # #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
763
+ #
764
+ # The attributes can be limited by setting <tt>.attributes_for_inspect</tt>.
765
+ #
766
+ # Post.attributes_for_inspect = [:id, :title]
767
+ # Post.first.inspect
768
+ # #=> "#<Post id: 1, title: "Hello, World!">"
723
769
  def inspect
724
- # We check defined?(@attributes) not to issue warnings if the object is
725
- # allocated but not initialized.
726
- inspection = if defined?(@attributes) && @attributes
727
- attribute_names.filter_map do |name|
728
- if _has_attribute?(name)
729
- "#{name}: #{attribute_for_inspect(name)}"
730
- end
731
- end.join(", ")
732
- else
733
- "not initialized"
734
- end
770
+ inspect_with_attributes(attributes_for_inspect)
771
+ end
735
772
 
736
- "#<#{self.class} #{inspection}>"
773
+ # Returns all attributes of the record as a nicely formatted string,
774
+ # ignoring <tt>.attributes_for_inspect</tt>.
775
+ #
776
+ # Post.first.full_inspect
777
+ # #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
778
+ #
779
+ def full_inspect
780
+ inspect_with_attributes(all_attributes_for_inspect)
737
781
  end
738
782
 
739
783
  # Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
@@ -741,17 +785,17 @@ module ActiveRecord
741
785
  def pretty_print(pp)
742
786
  return super if custom_inspect_method_defined?
743
787
  pp.object_address_group(self) do
744
- if defined?(@attributes) && @attributes
745
- attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) }
788
+ if @attributes
789
+ attr_names = attributes_for_inspect.select { |name| _has_attribute?(name.to_s) }
746
790
  pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
791
+ attr_name = attr_name.to_s
747
792
  pp.breakable " "
748
793
  pp.group(1) do
749
794
  pp.text attr_name
750
795
  pp.text ":"
751
796
  pp.breakable
752
- value = _read_attribute(attr_name)
753
- value = inspection_filter.filter_param(attr_name, value) unless value.nil?
754
- pp.pp value
797
+ value = attribute_for_inspect(attr_name)
798
+ pp.text value
755
799
  end
756
800
  end
757
801
  else
@@ -789,7 +833,6 @@ module ActiveRecord
789
833
  @strict_loading_mode = :all
790
834
 
791
835
  klass.define_attribute_methods
792
- klass.generate_alias_attributes
793
836
  end
794
837
 
795
838
  def initialize_internals_callback
@@ -809,5 +852,30 @@ module ActiveRecord
809
852
  def inspection_filter
810
853
  self.class.inspection_filter
811
854
  end
855
+
856
+ def inspect_with_attributes(attributes_to_list)
857
+ inspection = if @attributes
858
+ attributes_to_list.filter_map do |name|
859
+ name = name.to_s
860
+ if _has_attribute?(name)
861
+ "#{name}: #{attribute_for_inspect(name)}"
862
+ end
863
+ end.join(", ")
864
+ else
865
+ "not initialized"
866
+ end
867
+
868
+ "#<#{self.class} #{inspection}>"
869
+ end
870
+
871
+ def attributes_for_inspect
872
+ self.class.attributes_for_inspect == :all ? all_attributes_for_inspect : self.class.attributes_for_inspect
873
+ end
874
+
875
+ def all_attributes_for_inspect
876
+ return [] unless @attributes
877
+
878
+ attribute_names
879
+ end
812
880
  end
813
881
  end
@@ -7,6 +7,7 @@ module ActiveRecord
7
7
 
8
8
  included do
9
9
  class_attribute :_counter_cache_columns, instance_accessor: false, default: []
10
+ class_attribute :counter_cached_association_names, instance_writer: false, default: []
10
11
  end
11
12
 
12
13
  module ClassMethods
@@ -27,7 +28,7 @@ module ActiveRecord
27
28
  # # For the Post with id #1, reset the comments_count
28
29
  # Post.reset_counters(1, :comments)
29
30
  #
30
- # # Like above, but also touch the +updated_at+ and/or +updated_on+
31
+ # # Like above, but also touch the updated_at and/or updated_on
31
32
  # # attributes.
32
33
  # Post.reset_counters(1, :comments, touch: true)
33
34
  def reset_counters(id, *counters, touch: nil)
@@ -181,14 +182,26 @@ module ActiveRecord
181
182
  def counter_cache_column?(name) # :nodoc:
182
183
  _counter_cache_columns.include?(name)
183
184
  end
185
+
186
+ def load_schema! # :nodoc:
187
+ super
188
+
189
+ association_names = _reflections.filter_map do |name, reflection|
190
+ next unless reflection.belongs_to? && reflection.counter_cache_column
191
+
192
+ name.to_sym
193
+ end
194
+
195
+ self.counter_cached_association_names |= association_names
196
+ end
184
197
  end
185
198
 
186
199
  private
187
200
  def _create_record(attribute_names = self.attribute_names)
188
201
  id = super
189
202
 
190
- each_counter_cached_associations do |association|
191
- association.increment_counters
203
+ counter_cached_association_names.each do |association_name|
204
+ association(association_name).increment_counters
192
205
  end
193
206
 
194
207
  id
@@ -198,7 +211,9 @@ module ActiveRecord
198
211
  affected_rows = super
199
212
 
200
213
  if affected_rows > 0
201
- each_counter_cached_associations do |association|
214
+ counter_cached_association_names.each do |association_name|
215
+ association = association(association_name)
216
+
202
217
  unless destroyed_by_association && _foreign_keys_equal?(destroyed_by_association.foreign_key, association.reflection.foreign_key)
203
218
  association.decrement_counters
204
219
  end
@@ -208,12 +223,6 @@ module ActiveRecord
208
223
  affected_rows
209
224
  end
210
225
 
211
- def each_counter_cached_associations
212
- _reflections.each do |name, reflection|
213
- yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column
214
- end
215
- end
216
-
217
226
  def _foreign_keys_equal?(fkey1, fkey2)
218
227
  fkey1 == fkey2 || Array(fkey1).map(&:to_sym) == Array(fkey2).map(&:to_sym)
219
228
  end
@@ -25,8 +25,7 @@ module ActiveRecord
25
25
  def initialize(url)
26
26
  raise "Database URL cannot be empty" if url.blank?
27
27
  @uri = uri_parser.parse(url)
28
- @adapter = @uri.scheme && @uri.scheme.tr("-", "_")
29
- @adapter = "postgresql" if @adapter == "postgres"
28
+ @adapter = resolved_adapter
30
29
 
31
30
  if @uri.opaque
32
31
  @uri.opaque, @query = @uri.opaque.split("?", 2)
@@ -80,6 +79,14 @@ module ActiveRecord
80
79
  end
81
80
  end
82
81
 
82
+ def resolved_adapter
83
+ adapter = uri.scheme && @uri.scheme.tr("-", "_")
84
+ if adapter && ActiveRecord.protocol_adapters[adapter]
85
+ adapter = ActiveRecord.protocol_adapters[adapter]
86
+ end
87
+ adapter
88
+ end
89
+
83
90
  # Returns name of the database.
84
91
  def database_from_path
85
92
  if @adapter == "sqlite3"