activerecord 7.1.5.2 → 8.1.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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  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/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/enumerable"
4
- require "active_support/core_ext/module/delegation"
5
4
  require "active_support/parameter_filter"
6
5
  require "concurrent/map"
7
6
 
@@ -73,7 +72,7 @@ module ActiveRecord
73
72
  end
74
73
  self.configurations = {}
75
74
 
76
- # Returns fully resolved ActiveRecord::DatabaseConfigurations object
75
+ # Returns a fully resolved ActiveRecord::DatabaseConfigurations object.
77
76
  def self.configurations
78
77
  @@configurations
79
78
  end
@@ -89,6 +88,7 @@ module ActiveRecord
89
88
  class_attribute :belongs_to_required_by_default, instance_accessor: false
90
89
 
91
90
  class_attribute :strict_loading_by_default, instance_accessor: false, default: false
91
+ class_attribute :strict_loading_mode, instance_accessor: false, default: :all
92
92
 
93
93
  class_attribute :has_many_inversing, instance_accessor: false, default: false
94
94
 
@@ -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
@@ -186,6 +201,17 @@ module ActiveRecord
186
201
  false
187
202
  end
188
203
 
204
+ # Intended to behave like `.current_preventing_writes` given the class name as input.
205
+ # See PoolConfig and ConnectionHandler::ConnectionDescriptor.
206
+ def self.preventing_writes?(class_name) # :nodoc:
207
+ connected_to_stack.reverse_each do |hash|
208
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
209
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].any? { |klass| klass.name == class_name }
210
+ end
211
+
212
+ false
213
+ end
214
+
189
215
  def self.connected_to_stack # :nodoc:
190
216
  if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack]
191
217
  connected_to_stack
@@ -331,6 +357,8 @@ module ActiveRecord
331
357
  def filter_attributes=(filter_attributes)
332
358
  @inspection_filter = nil
333
359
  @filter_attributes = filter_attributes
360
+
361
+ FilterAttributeHandler.sensitive_attribute_was_declared(self, filter_attributes)
334
362
  end
335
363
 
336
364
  def inspection_filter # :nodoc:
@@ -350,8 +378,8 @@ module ActiveRecord
350
378
  super
351
379
  elsif abstract_class?
352
380
  "#{super}(abstract)"
353
- elsif !connected?
354
- "#{super} (call '#{super}.connection' to establish a connection)"
381
+ elsif !schema_loaded? && !connected?
382
+ "#{super} (call '#{super}.load_schema' to load schema informations)"
355
383
  elsif table_exists?
356
384
  attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
357
385
  "#{super}(#{attr_list})"
@@ -366,14 +394,14 @@ module ActiveRecord
366
394
  end
367
395
 
368
396
  def predicate_builder # :nodoc:
369
- @predicate_builder ||= PredicateBuilder.new(table_metadata)
397
+ @predicate_builder ||= PredicateBuilder.new(TableMetadata.new(self, arel_table))
370
398
  end
371
399
 
372
400
  def type_caster # :nodoc:
373
401
  TypeCaster::Map.new(self)
374
402
  end
375
403
 
376
- def cached_find_by_statement(key, &block) # :nodoc:
404
+ def cached_find_by_statement(connection, key, &block) # :nodoc:
377
405
  cache = @find_by_statement_cache[connection.prepared_statements]
378
406
  cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
379
407
  end
@@ -411,26 +439,24 @@ module ActiveRecord
411
439
  end
412
440
  end
413
441
 
414
- def table_metadata
415
- TableMetadata.new(self, arel_table)
416
- end
417
-
418
442
  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
443
+ with_connection do |connection|
444
+ statement = cached_find_by_statement(connection, keys) { |params|
445
+ wheres = keys.index_with do |key|
446
+ if key.is_a?(Array)
447
+ [key.map { params.bind }]
448
+ else
449
+ params.bind
450
+ end
425
451
  end
426
- end
427
- where(wheres).limit(1)
428
- }
452
+ where(wheres).limit(1)
453
+ }
429
454
 
430
- begin
431
- statement.execute(values.flatten, connection).first
432
- rescue TypeError
433
- raise ActiveRecord::StatementInvalid
455
+ statement.execute(values.flatten, connection).then do |r|
456
+ r.first
457
+ rescue TypeError
458
+ raise ActiveRecord::StatementInvalid
459
+ end
434
460
  end
435
461
  end
436
462
  end
@@ -450,7 +476,7 @@ module ActiveRecord
450
476
  init_internals
451
477
  initialize_internals_callback
452
478
 
453
- assign_attributes(attributes) if attributes
479
+ super
454
480
 
455
481
  yield self if block_given?
456
482
  _run_initialize_callbacks
@@ -523,12 +549,7 @@ module ActiveRecord
523
549
 
524
550
  ##
525
551
  def initialize_dup(other) # :nodoc:
526
- @attributes = @attributes.deep_dup
527
- if self.class.composite_primary_key?
528
- @primary_key.each { |key| @attributes.reset(key) }
529
- else
530
- @attributes.reset(@primary_key)
531
- end
552
+ @attributes = init_attributes(other)
532
553
 
533
554
  _run_initialize_callbacks
534
555
 
@@ -540,6 +561,18 @@ module ActiveRecord
540
561
  super
541
562
  end
542
563
 
564
+ def init_attributes(_) # :nodoc:
565
+ attrs = @attributes.deep_dup
566
+
567
+ if self.class.composite_primary_key?
568
+ @primary_key.each { |key| attrs.reset(key) }
569
+ else
570
+ attrs.reset(@primary_key)
571
+ end
572
+
573
+ attrs
574
+ end
575
+
543
576
  # Populate +coder+ with attributes about this record that should be
544
577
  # serialized. The structure of +coder+ defined in this method is
545
578
  # guaranteed to match the structure of +coder+ passed to the #init_with
@@ -568,7 +601,7 @@ module ActiveRecord
568
601
  #
569
602
  # topic = Topic.new(title: "Budget", author_name: "Jason")
570
603
  # topic.slice(:title, :author_name)
571
- # => { "title" => "Budget", "author_name" => "Jason" }
604
+ # # => { "title" => "Budget", "author_name" => "Jason" }
572
605
  #
573
606
  #--
574
607
  # Implemented by ActiveModel::Access#slice.
@@ -582,7 +615,7 @@ module ActiveRecord
582
615
  #
583
616
  # topic = Topic.new(title: "Budget", author_name: "Jason")
584
617
  # topic.values_at(:title, :author_name)
585
- # => ["Budget", "Jason"]
618
+ # # => ["Budget", "Jason"]
586
619
  #
587
620
  #--
588
621
  # Implemented by ActiveModel::Access#values_at.
@@ -609,7 +642,7 @@ module ActiveRecord
609
642
  def hash
610
643
  id = self.id
611
644
 
612
- if primary_key_values_present?
645
+ if self.class.composite_primary_key? ? primary_key_values_present? : id
613
646
  self.class.hash ^ id.hash
614
647
  else
615
648
  super
@@ -659,12 +692,14 @@ module ActiveRecord
659
692
  # Sets the record to strict_loading mode. This will raise an error
660
693
  # if the record tries to lazily load an association.
661
694
  #
695
+ # NOTE: Strict loading is disabled during validation in order to let the record validate its association.
696
+ #
662
697
  # user = User.first
663
698
  # user.strict_loading! # => true
664
699
  # user.address.city
665
- # => ActiveRecord::StrictLoadingViolationError
700
+ # # => ActiveRecord::StrictLoadingViolationError
666
701
  # user.comments.to_a
667
- # => ActiveRecord::StrictLoadingViolationError
702
+ # # => ActiveRecord::StrictLoadingViolationError
668
703
  #
669
704
  # ==== Parameters
670
705
  #
@@ -684,7 +719,7 @@ module ActiveRecord
684
719
  # user.address.city # => "Tatooine"
685
720
  # user.comments.to_a # => [#<Comment:0x00...]
686
721
  # user.comments.first.ratings.to_a
687
- # => ActiveRecord::StrictLoadingViolationError
722
+ # # => ActiveRecord::StrictLoadingViolationError
688
723
  def strict_loading!(value = true, mode: :all)
689
724
  unless [:all, :n_plus_one_only].include?(mode)
690
725
  raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided."
@@ -706,11 +741,29 @@ module ActiveRecord
706
741
  @strict_loading_mode == :all
707
742
  end
708
743
 
709
- # Marks this record as read only.
744
+ # Prevents records from being written to the database:
745
+ #
746
+ # customer = Customer.new
747
+ # customer.readonly!
748
+ # customer.save # raises ActiveRecord::ReadOnlyRecord
749
+ #
750
+ # customer = Customer.first
751
+ # customer.readonly!
752
+ # customer.update(name: 'New Name') # raises ActiveRecord::ReadOnlyRecord
753
+ #
754
+ # Read-only records cannot be deleted from the database either:
710
755
  #
711
756
  # customer = Customer.first
712
757
  # customer.readonly!
713
- # customer.save # Raises an ActiveRecord::ReadOnlyRecord
758
+ # customer.destroy # raises ActiveRecord::ReadOnlyRecord
759
+ #
760
+ # Please, note that the objects themselves are still mutable in memory:
761
+ #
762
+ # customer = Customer.new
763
+ # customer.readonly!
764
+ # customer.name = 'New Name' # OK
765
+ #
766
+ # but you won't be able to persist the changes.
714
767
  def readonly!
715
768
  @readonly = true
716
769
  end
@@ -719,21 +772,28 @@ module ActiveRecord
719
772
  self.class.connection_handler
720
773
  end
721
774
 
722
- # Returns the contents of the record as a nicely formatted string.
775
+ # Returns the attributes of the record as a nicely formatted string.
776
+ #
777
+ # Post.first.inspect
778
+ # #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
779
+ #
780
+ # The attributes can be limited by setting <tt>.attributes_for_inspect</tt>.
781
+ #
782
+ # Post.attributes_for_inspect = [:id, :title]
783
+ # Post.first.inspect
784
+ # #=> "#<Post id: 1, title: "Hello, World!">"
723
785
  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
786
+ inspect_with_attributes(attributes_for_inspect)
787
+ end
735
788
 
736
- "#<#{self.class} #{inspection}>"
789
+ # Returns all attributes of the record as a nicely formatted string,
790
+ # ignoring <tt>.attributes_for_inspect</tt>.
791
+ #
792
+ # Post.first.full_inspect
793
+ # #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
794
+ #
795
+ def full_inspect
796
+ inspect_with_attributes(all_attributes_for_inspect)
737
797
  end
738
798
 
739
799
  # Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
@@ -741,17 +801,17 @@ module ActiveRecord
741
801
  def pretty_print(pp)
742
802
  return super if custom_inspect_method_defined?
743
803
  pp.object_address_group(self) do
744
- if defined?(@attributes) && @attributes
745
- attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) }
804
+ if @attributes
805
+ attr_names = attributes_for_inspect.select { |name| _has_attribute?(name.to_s) }
746
806
  pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
807
+ attr_name = attr_name.to_s
747
808
  pp.breakable " "
748
809
  pp.group(1) do
749
810
  pp.text attr_name
750
811
  pp.text ":"
751
812
  pp.breakable
752
- value = _read_attribute(attr_name)
753
- value = inspection_filter.filter_param(attr_name, value) unless value.nil?
754
- pp.pp value
813
+ value = attribute_for_inspect(attr_name)
814
+ pp.text value
755
815
  end
756
816
  end
757
817
  else
@@ -786,10 +846,9 @@ module ActiveRecord
786
846
 
787
847
  @primary_key = klass.primary_key
788
848
  @strict_loading = klass.strict_loading_by_default
789
- @strict_loading_mode = :all
849
+ @strict_loading_mode = klass.strict_loading_mode
790
850
 
791
851
  klass.define_attribute_methods
792
- klass.generate_alias_attributes
793
852
  end
794
853
 
795
854
  def initialize_internals_callback
@@ -809,5 +868,30 @@ module ActiveRecord
809
868
  def inspection_filter
810
869
  self.class.inspection_filter
811
870
  end
871
+
872
+ def inspect_with_attributes(attributes_to_list)
873
+ inspection = if @attributes
874
+ attributes_to_list.filter_map do |name|
875
+ name = name.to_s
876
+ if _has_attribute?(name)
877
+ "#{name}: #{attribute_for_inspect(name)}"
878
+ end
879
+ end.join(", ")
880
+ else
881
+ "not initialized"
882
+ end
883
+
884
+ "#<#{self.class} #{inspection}>"
885
+ end
886
+
887
+ def attributes_for_inspect
888
+ self.class.attributes_for_inspect == :all ? all_attributes_for_inspect : self.class.attributes_for_inspect
889
+ end
890
+
891
+ def all_attributes_for_inspect
892
+ return [] unless @attributes
893
+
894
+ attribute_names
895
+ end
812
896
  end
813
897
  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
@@ -16,7 +17,7 @@ module ActiveRecord
16
17
  #
17
18
  # ==== Parameters
18
19
  #
19
- # * +id+ - The id of the object you wish to reset a counter on.
20
+ # * +id+ - The id of the object you wish to reset a counter on or an array of ids.
20
21
  # * +counters+ - One or more association counters to reset. Association name or counter name can be given.
21
22
  # * <tt>:touch</tt> - Touch timestamp columns when updating.
22
23
  # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
@@ -27,13 +28,25 @@ 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
+ # # For posts with ids #1 and #2, reset the comments_count
32
+ # Post.reset_counters([1, 2], :comments)
33
+ #
34
+ # # Like above, but also touch the updated_at and/or updated_on
31
35
  # # attributes.
32
36
  # Post.reset_counters(1, :comments, touch: true)
33
37
  def reset_counters(id, *counters, touch: nil)
34
- object = find(id)
38
+ ids = if composite_primary_key?
39
+ if id.first.is_a?(Array)
40
+ id
41
+ else
42
+ [id]
43
+ end
44
+ else
45
+ Array(id)
46
+ end
47
+
48
+ updates = Hash.new { |h, k| h[k] = {} }
35
49
 
36
- updates = {}
37
50
  counters.each do |counter_association|
38
51
  has_many_association = _reflect_on_association(counter_association)
39
52
  unless has_many_association
@@ -47,14 +60,22 @@ module ActiveRecord
47
60
  has_many_association = has_many_association.through_reflection
48
61
  end
49
62
 
63
+ counter_association = counter_association.to_sym
50
64
  foreign_key = has_many_association.foreign_key.to_s
51
65
  child_class = has_many_association.klass
52
66
  reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
53
67
  counter_name = reflection.counter_cache_column
54
68
 
55
- count_was = object.send(counter_name)
56
- count = object.send(counter_association).count(:all)
57
- updates[counter_name] = count if count != count_was
69
+ counts =
70
+ unscoped
71
+ .joins(counter_association)
72
+ .where(primary_key => ids)
73
+ .group(primary_key)
74
+ .count(:all)
75
+
76
+ ids.each do |id|
77
+ updates[id].merge!(counter_name => counts[id] || 0)
78
+ end
58
79
  end
59
80
 
60
81
  if touch
@@ -62,10 +83,15 @@ module ActiveRecord
62
83
  names = Array.wrap(names)
63
84
  options = names.extract_options!
64
85
  touch_updates = touch_attributes_with_time(*names, **options)
65
- updates.merge!(touch_updates)
86
+
87
+ updates.each_value do |record_updates|
88
+ record_updates.merge!(touch_updates)
89
+ end
66
90
  end
67
91
 
68
- unscoped.where(primary_key => [object.id]).update_all(updates) if updates.any?
92
+ updates.each do |id, record_updates|
93
+ unscoped.where(primary_key => [id]).update_all(record_updates)
94
+ end
69
95
 
70
96
  true
71
97
  end
@@ -181,14 +207,26 @@ module ActiveRecord
181
207
  def counter_cache_column?(name) # :nodoc:
182
208
  _counter_cache_columns.include?(name)
183
209
  end
210
+
211
+ def load_schema! # :nodoc:
212
+ super
213
+
214
+ association_names = _reflections.filter_map do |name, reflection|
215
+ next unless reflection.belongs_to? && reflection.counter_cache_column
216
+
217
+ name.to_sym
218
+ end
219
+
220
+ self.counter_cached_association_names |= association_names
221
+ end
184
222
  end
185
223
 
186
224
  private
187
225
  def _create_record(attribute_names = self.attribute_names)
188
226
  id = super
189
227
 
190
- each_counter_cached_associations do |association|
191
- association.increment_counters
228
+ counter_cached_association_names.each do |association_name|
229
+ association(association_name).increment_counters
192
230
  end
193
231
 
194
232
  id
@@ -198,7 +236,9 @@ module ActiveRecord
198
236
  affected_rows = super
199
237
 
200
238
  if affected_rows > 0
201
- each_counter_cached_associations do |association|
239
+ counter_cached_association_names.each do |association_name|
240
+ association = association(association_name)
241
+
202
242
  unless destroyed_by_association && _foreign_keys_equal?(destroyed_by_association.foreign_key, association.reflection.foreign_key)
203
243
  association.decrement_counters
204
244
  end
@@ -208,12 +248,6 @@ module ActiveRecord
208
248
  affected_rows
209
249
  end
210
250
 
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
251
  def _foreign_keys_equal?(fkey1, fkey2)
218
252
  fkey1 == fkey2 || Array(fkey1).map(&:to_sym) == Array(fkey2).map(&:to_sym)
219
253
  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"
@@ -11,14 +11,25 @@ module ActiveRecord
11
11
  def initialize(env_name, name)
12
12
  @env_name = env_name
13
13
  @name = name
14
+ @adapter_class = nil
14
15
  end
15
16
 
16
- def adapter_method
17
- "#{adapter}_connection"
17
+ def adapter_class
18
+ @adapter_class ||= ActiveRecord::ConnectionAdapters.resolve(adapter)
18
19
  end
19
20
 
20
- def adapter_class_method
21
- "#{adapter}_adapter_class"
21
+ def inspect # :nodoc:
22
+ "#<#{self.class.name} env_name=#{@env_name} name=#{@name} adapter_class=#{adapter_class}>"
23
+ end
24
+
25
+ def new_connection
26
+ adapter_class.new(configuration_hash)
27
+ end
28
+
29
+ def validate!
30
+ adapter_class if adapter
31
+
32
+ true
22
33
  end
23
34
 
24
35
  def host
@@ -37,7 +48,11 @@ module ActiveRecord
37
48
  raise NotImplementedError
38
49
  end
39
50
 
40
- def pool
51
+ def min_connections
52
+ raise NotImplementedError
53
+ end
54
+
55
+ def max_connections
41
56
  raise NotImplementedError
42
57
  end
43
58
 
@@ -84,6 +99,14 @@ module ActiveRecord
84
99
  def schema_cache_path
85
100
  raise NotImplementedError
86
101
  end
102
+
103
+ def use_metadata_table?
104
+ raise NotImplementedError
105
+ end
106
+
107
+ def seeds?
108
+ raise NotImplementedError
109
+ end
87
110
  end
88
111
  end
89
112
  end