activerecord 7.2.2.1 → 8.1.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +564 -753
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -11
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +10 -8
  14. data/lib/active_record/associations/collection_proxy.rb +22 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  20. data/lib/active_record/associations/join_dependency.rb +4 -2
  21. data/lib/active_record/associations/preloader/association.rb +2 -2
  22. data/lib/active_record/associations/preloader/batch.rb +7 -1
  23. data/lib/active_record/associations/preloader/branch.rb +1 -0
  24. data/lib/active_record/associations/singular_association.rb +8 -3
  25. data/lib/active_record/associations.rb +192 -24
  26. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  27. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  28. data/lib/active_record/attribute_methods/query.rb +34 -0
  29. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  31. data/lib/active_record/attribute_methods.rb +24 -19
  32. data/lib/active_record/attributes.rb +40 -26
  33. data/lib/active_record/autosave_association.rb +91 -39
  34. data/lib/active_record/base.rb +3 -4
  35. data/lib/active_record/coders/json.rb +14 -5
  36. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  37. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  38. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  39. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
  40. data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
  41. data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
  42. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  43. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  45. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
  47. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  48. data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
  49. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
  50. data/lib/active_record/connection_adapters/column.rb +17 -4
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  53. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  54. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  59. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  60. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  62. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  65. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  66. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  67. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
  68. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  69. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
  70. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
  71. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  72. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  73. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  74. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  75. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  76. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
  78. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  79. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  80. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
  81. data/lib/active_record/connection_adapters.rb +1 -56
  82. data/lib/active_record/connection_handling.rb +37 -10
  83. data/lib/active_record/core.rb +61 -25
  84. data/lib/active_record/counter_cache.rb +34 -9
  85. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  86. data/lib/active_record/database_configurations/database_config.rb +9 -1
  87. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  88. data/lib/active_record/database_configurations/url_config.rb +13 -3
  89. data/lib/active_record/database_configurations.rb +7 -3
  90. data/lib/active_record/delegated_type.rb +19 -19
  91. data/lib/active_record/dynamic_matchers.rb +54 -69
  92. data/lib/active_record/encryption/config.rb +3 -1
  93. data/lib/active_record/encryption/encryptable_record.rb +9 -9
  94. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  95. data/lib/active_record/encryption/encryptor.rb +49 -28
  96. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  97. data/lib/active_record/encryption/scheme.rb +9 -2
  98. data/lib/active_record/enum.rb +46 -42
  99. data/lib/active_record/errors.rb +36 -12
  100. data/lib/active_record/explain.rb +1 -1
  101. data/lib/active_record/explain_registry.rb +51 -2
  102. data/lib/active_record/filter_attribute_handler.rb +73 -0
  103. data/lib/active_record/fixture_set/table_row.rb +19 -2
  104. data/lib/active_record/fixtures.rb +2 -4
  105. data/lib/active_record/future_result.rb +13 -9
  106. data/lib/active_record/gem_version.rb +3 -3
  107. data/lib/active_record/inheritance.rb +1 -1
  108. data/lib/active_record/insert_all.rb +12 -7
  109. data/lib/active_record/locking/optimistic.rb +8 -1
  110. data/lib/active_record/locking/pessimistic.rb +5 -0
  111. data/lib/active_record/log_subscriber.rb +3 -13
  112. data/lib/active_record/middleware/shard_selector.rb +34 -17
  113. data/lib/active_record/migration/command_recorder.rb +44 -11
  114. data/lib/active_record/migration/compatibility.rb +37 -24
  115. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  116. data/lib/active_record/migration.rb +50 -43
  117. data/lib/active_record/model_schema.rb +38 -13
  118. data/lib/active_record/nested_attributes.rb +6 -6
  119. data/lib/active_record/persistence.rb +162 -133
  120. data/lib/active_record/query_cache.rb +22 -15
  121. data/lib/active_record/query_logs.rb +104 -52
  122. data/lib/active_record/query_logs_formatter.rb +17 -28
  123. data/lib/active_record/querying.rb +12 -12
  124. data/lib/active_record/railtie.rb +37 -32
  125. data/lib/active_record/railties/controller_runtime.rb +11 -6
  126. data/lib/active_record/railties/databases.rake +26 -37
  127. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  128. data/lib/active_record/railties/job_runtime.rb +10 -11
  129. data/lib/active_record/reflection.rb +53 -21
  130. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  131. data/lib/active_record/relation/batches.rb +147 -73
  132. data/lib/active_record/relation/calculations.rb +80 -63
  133. data/lib/active_record/relation/delegation.rb +25 -15
  134. data/lib/active_record/relation/finder_methods.rb +54 -37
  135. data/lib/active_record/relation/merger.rb +8 -8
  136. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  137. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  138. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  139. data/lib/active_record/relation/predicate_builder.rb +22 -7
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +156 -95
  142. data/lib/active_record/relation/spawn_methods.rb +7 -7
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +122 -80
  145. data/lib/active_record/result.rb +109 -24
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +9 -6
  148. data/lib/active_record/schema_dumper.rb +47 -22
  149. data/lib/active_record/schema_migration.rb +2 -1
  150. data/lib/active_record/scoping/named.rb +5 -2
  151. data/lib/active_record/scoping.rb +0 -1
  152. data/lib/active_record/secure_token.rb +3 -3
  153. data/lib/active_record/signed_id.rb +47 -18
  154. data/lib/active_record/statement_cache.rb +24 -20
  155. data/lib/active_record/store.rb +51 -22
  156. data/lib/active_record/structured_event_subscriber.rb +85 -0
  157. data/lib/active_record/table_metadata.rb +6 -23
  158. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  159. data/lib/active_record/tasks/database_tasks.rb +85 -85
  160. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  161. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  162. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  163. data/lib/active_record/test_databases.rb +14 -4
  164. data/lib/active_record/test_fixtures.rb +39 -2
  165. data/lib/active_record/testing/query_assertions.rb +8 -2
  166. data/lib/active_record/timestamp.rb +4 -2
  167. data/lib/active_record/token_for.rb +1 -1
  168. data/lib/active_record/transaction.rb +2 -5
  169. data/lib/active_record/transactions.rb +39 -16
  170. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  171. data/lib/active_record/type/internal/timezone.rb +7 -0
  172. data/lib/active_record/type/json.rb +15 -2
  173. data/lib/active_record/type/serialized.rb +11 -4
  174. data/lib/active_record/type/type_map.rb +1 -1
  175. data/lib/active_record/type_caster/connection.rb +2 -1
  176. data/lib/active_record/validations/associated.rb +1 -1
  177. data/lib/active_record/validations/uniqueness.rb +8 -8
  178. data/lib/active_record.rb +85 -50
  179. data/lib/arel/alias_predication.rb +2 -0
  180. data/lib/arel/collectors/bind.rb +2 -2
  181. data/lib/arel/collectors/sql_string.rb +1 -1
  182. data/lib/arel/collectors/substitute_binds.rb +2 -2
  183. data/lib/arel/crud.rb +8 -11
  184. data/lib/arel/delete_manager.rb +5 -0
  185. data/lib/arel/nodes/binary.rb +1 -1
  186. data/lib/arel/nodes/count.rb +2 -2
  187. data/lib/arel/nodes/delete_statement.rb +4 -2
  188. data/lib/arel/nodes/function.rb +4 -10
  189. data/lib/arel/nodes/named_function.rb +2 -2
  190. data/lib/arel/nodes/node.rb +2 -2
  191. data/lib/arel/nodes/sql_literal.rb +1 -1
  192. data/lib/arel/nodes/update_statement.rb +4 -2
  193. data/lib/arel/nodes.rb +0 -2
  194. data/lib/arel/select_manager.rb +13 -4
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/update_manager.rb +5 -0
  197. data/lib/arel/visitors/dot.rb +2 -3
  198. data/lib/arel/visitors/postgresql.rb +55 -0
  199. data/lib/arel/visitors/sqlite.rb +55 -8
  200. data/lib/arel/visitors/to_sql.rb +6 -22
  201. data/lib/arel.rb +3 -1
  202. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  203. metadata +17 -17
  204. data/lib/active_record/explain_subscriber.rb +0 -34
  205. data/lib/active_record/normalization.rb +0 -163
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -87,6 +87,8 @@ module ActiveRecord
87
87
 
88
88
  connections = []
89
89
 
90
+ @shard_keys = shards.keys
91
+
90
92
  if shards.empty?
91
93
  shards[:default] = database
92
94
  end
@@ -98,7 +100,8 @@ module ActiveRecord
98
100
  db_config = resolve_config_for_connection(database_key)
99
101
 
100
102
  self.connection_class = true
101
- connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard.to_sym)
103
+ shard = shard.to_sym unless shard.is_a? Integer
104
+ connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard)
102
105
  end
103
106
  end
104
107
 
@@ -170,9 +173,23 @@ module ActiveRecord
170
173
  prevent_writes = true if role == ActiveRecord.reading_role
171
174
 
172
175
  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes)
173
- yield
174
- ensure
175
- connected_to_stack.pop
176
+ begin
177
+ yield
178
+ ensure
179
+ connected_to_stack.pop
180
+ end
181
+ end
182
+
183
+ # Passes the block to +connected_to+ for every +shard+ the
184
+ # model is configured to connect to (if any), and returns the
185
+ # results in an array.
186
+ #
187
+ # Optionally, +role+ and/or +prevent_writes+ can be passed which
188
+ # will be forwarded to each +connected_to+ call.
189
+ def connected_to_all_shards(role: nil, prevent_writes: false, &blk)
190
+ shard_keys.map do |shard|
191
+ connected_to(shard: shard, role: role, prevent_writes: prevent_writes, &blk)
192
+ end
176
193
  end
177
194
 
178
195
  # Use a specified connection.
@@ -287,7 +304,7 @@ module ActiveRecord
287
304
 
288
305
  # Checkouts a connection from the pool, yield it and then check it back in.
289
306
  # If a connection was already leased via #lease_connection or a parent call to
290
- # #with_connection, that same connection is yieled.
307
+ # #with_connection, that same connection is yielded.
291
308
  # If #lease_connection is called inside the block, the connection won't be checked
292
309
  # back in.
293
310
  # If #connection is called inside the block, the connection won't be checked back in
@@ -359,6 +376,14 @@ module ActiveRecord
359
376
  connection_pool.schema_cache.clear!
360
377
  end
361
378
 
379
+ def shard_keys
380
+ connection_class_for_self.instance_variable_get(:@shard_keys) || []
381
+ end
382
+
383
+ def sharded?
384
+ shard_keys.any?
385
+ end
386
+
362
387
  private
363
388
  def resolve_config_for_connection(config_or_env)
364
389
  raise "Anonymous class is not allowed." unless name
@@ -373,11 +398,13 @@ module ActiveRecord
373
398
  prevent_writes = true if role == ActiveRecord.reading_role
374
399
 
375
400
  append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
376
- return_value = yield
377
- return_value.load if return_value.is_a? ActiveRecord::Relation
378
- return_value
379
- ensure
380
- self.connected_to_stack.pop
401
+ begin
402
+ return_value = yield
403
+ return_value.load if return_value.is_a? ActiveRecord::Relation
404
+ return_value
405
+ ensure
406
+ self.connected_to_stack.pop
407
+ end
381
408
  end
382
409
 
383
410
  def append_to_connected_to_stack(entry)
@@ -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
 
@@ -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
 
@@ -111,7 +111,7 @@ module ActiveRecord
111
111
  # Post.attributes_for_inspect = [:id, :title]
112
112
  # Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">"
113
113
  #
114
- # When set to `:all` inspect will list all the record's attributes:
114
+ # When set to +:all+ inspect will list all the record's attributes:
115
115
  #
116
116
  # Post.attributes_for_inspect = :all
117
117
  # Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
@@ -201,6 +201,17 @@ module ActiveRecord
201
201
  false
202
202
  end
203
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
+
204
215
  def self.connected_to_stack # :nodoc:
205
216
  if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack]
206
217
  connected_to_stack
@@ -265,7 +276,7 @@ module ActiveRecord
265
276
  return super if StatementCache.unsupported_value?(id)
266
277
 
267
278
  cached_find_by([primary_key], [id]) ||
268
- raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", name, primary_key, id))
279
+ raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id.inspect}", name, primary_key, id))
269
280
  end
270
281
 
271
282
  def find_by(*args) # :nodoc:
@@ -346,6 +357,8 @@ module ActiveRecord
346
357
  def filter_attributes=(filter_attributes)
347
358
  @inspection_filter = nil
348
359
  @filter_attributes = filter_attributes
360
+
361
+ FilterAttributeHandler.sensitive_attribute_was_declared(self, filter_attributes)
349
362
  end
350
363
 
351
364
  def inspection_filter # :nodoc:
@@ -381,7 +394,7 @@ module ActiveRecord
381
394
  end
382
395
 
383
396
  def predicate_builder # :nodoc:
384
- @predicate_builder ||= PredicateBuilder.new(table_metadata)
397
+ @predicate_builder ||= PredicateBuilder.new(TableMetadata.new(self, arel_table))
385
398
  end
386
399
 
387
400
  def type_caster # :nodoc:
@@ -426,10 +439,6 @@ module ActiveRecord
426
439
  end
427
440
  end
428
441
 
429
- def table_metadata
430
- TableMetadata.new(self, arel_table)
431
- end
432
-
433
442
  def cached_find_by(keys, values)
434
443
  with_connection do |connection|
435
444
  statement = cached_find_by_statement(connection, keys) { |params|
@@ -443,8 +452,8 @@ module ActiveRecord
443
452
  where(wheres).limit(1)
444
453
  }
445
454
 
446
- begin
447
- statement.execute(values.flatten, connection, allow_retry: true).first
455
+ statement.execute(values.flatten, connection).then do |r|
456
+ r.first
448
457
  rescue TypeError
449
458
  raise ActiveRecord::StatementInvalid
450
459
  end
@@ -540,12 +549,7 @@ module ActiveRecord
540
549
 
541
550
  ##
542
551
  def initialize_dup(other) # :nodoc:
543
- @attributes = @attributes.deep_dup
544
- if self.class.composite_primary_key?
545
- @primary_key.each { |key| @attributes.reset(key) }
546
- else
547
- @attributes.reset(@primary_key)
548
- end
552
+ @attributes = init_attributes(other)
549
553
 
550
554
  _run_initialize_callbacks
551
555
 
@@ -557,6 +561,18 @@ module ActiveRecord
557
561
  super
558
562
  end
559
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
+
560
576
  # Populate +coder+ with attributes about this record that should be
561
577
  # serialized. The structure of +coder+ defined in this method is
562
578
  # guaranteed to match the structure of +coder+ passed to the #init_with
@@ -585,7 +601,7 @@ module ActiveRecord
585
601
  #
586
602
  # topic = Topic.new(title: "Budget", author_name: "Jason")
587
603
  # topic.slice(:title, :author_name)
588
- # => { "title" => "Budget", "author_name" => "Jason" }
604
+ # # => { "title" => "Budget", "author_name" => "Jason" }
589
605
  #
590
606
  #--
591
607
  # Implemented by ActiveModel::Access#slice.
@@ -599,7 +615,7 @@ module ActiveRecord
599
615
  #
600
616
  # topic = Topic.new(title: "Budget", author_name: "Jason")
601
617
  # topic.values_at(:title, :author_name)
602
- # => ["Budget", "Jason"]
618
+ # # => ["Budget", "Jason"]
603
619
  #
604
620
  #--
605
621
  # Implemented by ActiveModel::Access#values_at.
@@ -626,7 +642,7 @@ module ActiveRecord
626
642
  def hash
627
643
  id = self.id
628
644
 
629
- if primary_key_values_present?
645
+ if self.class.composite_primary_key? ? primary_key_values_present? : id
630
646
  self.class.hash ^ id.hash
631
647
  else
632
648
  super
@@ -676,12 +692,14 @@ module ActiveRecord
676
692
  # Sets the record to strict_loading mode. This will raise an error
677
693
  # if the record tries to lazily load an association.
678
694
  #
695
+ # NOTE: Strict loading is disabled during validation in order to let the record validate its association.
696
+ #
679
697
  # user = User.first
680
698
  # user.strict_loading! # => true
681
699
  # user.address.city
682
- # => ActiveRecord::StrictLoadingViolationError
700
+ # # => ActiveRecord::StrictLoadingViolationError
683
701
  # user.comments.to_a
684
- # => ActiveRecord::StrictLoadingViolationError
702
+ # # => ActiveRecord::StrictLoadingViolationError
685
703
  #
686
704
  # ==== Parameters
687
705
  #
@@ -701,7 +719,7 @@ module ActiveRecord
701
719
  # user.address.city # => "Tatooine"
702
720
  # user.comments.to_a # => [#<Comment:0x00...]
703
721
  # user.comments.first.ratings.to_a
704
- # => ActiveRecord::StrictLoadingViolationError
722
+ # # => ActiveRecord::StrictLoadingViolationError
705
723
  def strict_loading!(value = true, mode: :all)
706
724
  unless [:all, :n_plus_one_only].include?(mode)
707
725
  raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided."
@@ -723,11 +741,29 @@ module ActiveRecord
723
741
  @strict_loading_mode == :all
724
742
  end
725
743
 
726
- # 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
727
749
  #
728
750
  # customer = Customer.first
729
751
  # customer.readonly!
730
- # customer.save # Raises an ActiveRecord::ReadOnlyRecord
752
+ # customer.update(name: 'New Name') # raises ActiveRecord::ReadOnlyRecord
753
+ #
754
+ # Read-only records cannot be deleted from the database either:
755
+ #
756
+ # customer = Customer.first
757
+ # customer.readonly!
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.
731
767
  def readonly!
732
768
  @readonly = true
733
769
  end
@@ -810,7 +846,7 @@ module ActiveRecord
810
846
 
811
847
  @primary_key = klass.primary_key
812
848
  @strict_loading = klass.strict_loading_by_default
813
- @strict_loading_mode = :all
849
+ @strict_loading_mode = klass.strict_loading_mode
814
850
 
815
851
  klass.define_attribute_methods
816
852
  end
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  #
18
18
  # ==== Parameters
19
19
  #
20
- # * +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.
21
21
  # * +counters+ - One or more association counters to reset. Association name or counter name can be given.
22
22
  # * <tt>:touch</tt> - Touch timestamp columns when updating.
23
23
  # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
@@ -28,13 +28,25 @@ module ActiveRecord
28
28
  # # For the Post with id #1, reset the comments_count
29
29
  # Post.reset_counters(1, :comments)
30
30
  #
31
- # # 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
32
35
  # # attributes.
33
36
  # Post.reset_counters(1, :comments, touch: true)
34
37
  def reset_counters(id, *counters, touch: nil)
35
- 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] = {} }
36
49
 
37
- updates = {}
38
50
  counters.each do |counter_association|
39
51
  has_many_association = _reflect_on_association(counter_association)
40
52
  unless has_many_association
@@ -48,14 +60,22 @@ module ActiveRecord
48
60
  has_many_association = has_many_association.through_reflection
49
61
  end
50
62
 
63
+ counter_association = counter_association.to_sym
51
64
  foreign_key = has_many_association.foreign_key.to_s
52
65
  child_class = has_many_association.klass
53
66
  reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
54
67
  counter_name = reflection.counter_cache_column
55
68
 
56
- count_was = object.send(counter_name)
57
- count = object.send(counter_association).count(:all)
58
- 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
59
79
  end
60
80
 
61
81
  if touch
@@ -63,10 +83,15 @@ module ActiveRecord
63
83
  names = Array.wrap(names)
64
84
  options = names.extract_options!
65
85
  touch_updates = touch_attributes_with_time(*names, **options)
66
- updates.merge!(touch_updates)
86
+
87
+ updates.each_value do |record_updates|
88
+ record_updates.merge!(touch_updates)
89
+ end
67
90
  end
68
91
 
69
- 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
70
95
 
71
96
  true
72
97
  end
@@ -81,7 +81,9 @@ module ActiveRecord
81
81
 
82
82
  def resolved_adapter
83
83
  adapter = uri.scheme && @uri.scheme.tr("-", "_")
84
- adapter = ActiveRecord.protocol_adapters[adapter] || adapter
84
+ if adapter && ActiveRecord.protocol_adapters[adapter]
85
+ adapter = ActiveRecord.protocol_adapters[adapter]
86
+ end
85
87
  adapter
86
88
  end
87
89
 
@@ -48,7 +48,11 @@ module ActiveRecord
48
48
  raise NotImplementedError
49
49
  end
50
50
 
51
- def pool
51
+ def min_connections
52
+ raise NotImplementedError
53
+ end
54
+
55
+ def max_connections
52
56
  raise NotImplementedError
53
57
  end
54
58
 
@@ -99,6 +103,10 @@ module ActiveRecord
99
103
  def use_metadata_table?
100
104
  raise NotImplementedError
101
105
  end
106
+
107
+ def seeds?
108
+ raise NotImplementedError
109
+ end
102
110
  end
103
111
  end
104
112
  end
@@ -38,6 +38,7 @@ module ActiveRecord
38
38
  def initialize(env_name, name, configuration_hash)
39
39
  super(env_name, name)
40
40
  @configuration_hash = configuration_hash.symbolize_keys.freeze
41
+ validate_configuration!
41
42
  end
42
43
 
43
44
  # Determines whether a database configuration is for a replica / readonly
@@ -69,16 +70,35 @@ module ActiveRecord
69
70
  @configuration_hash = configuration_hash.merge(database: database).freeze
70
71
  end
71
72
 
72
- def pool
73
- (configuration_hash[:pool] || 5).to_i
73
+ def max_connections
74
+ max_connections = configuration_hash.fetch(:max_connections) {
75
+ configuration_hash.fetch(:pool, 5)
76
+ }&.to_i
77
+ max_connections if max_connections && max_connections >= 0
74
78
  end
75
79
 
80
+ def min_connections
81
+ (configuration_hash[:min_connections] || 0).to_i
82
+ end
83
+
84
+ alias :pool :max_connections
85
+ deprecate pool: :max_connections, deprecator: ActiveRecord.deprecator
86
+
76
87
  def min_threads
77
88
  (configuration_hash[:min_threads] || 0).to_i
78
89
  end
79
90
 
80
91
  def max_threads
81
- (configuration_hash[:max_threads] || pool).to_i
92
+ (configuration_hash[:max_threads] || (max_connections || 5).clamp(0, 5)).to_i
93
+ end
94
+
95
+ def max_age
96
+ v = configuration_hash[:max_age]&.to_i
97
+ if v && v > 0
98
+ v
99
+ else
100
+ Float::INFINITY
101
+ end
82
102
  end
83
103
 
84
104
  def query_cache
@@ -93,10 +113,8 @@ module ActiveRecord
93
113
  (configuration_hash[:checkout_timeout] || 5).to_f
94
114
  end
95
115
 
96
- # `reaping_frequency` is configurable mostly for historical reasons, but it
97
- # could also be useful if someone wants a very low `idle_timeout`.
98
- def reaping_frequency
99
- configuration_hash.fetch(:reaping_frequency, 60)&.to_f
116
+ def reaping_frequency # :nodoc:
117
+ configuration_hash.fetch(:reaping_frequency, default_reaping_frequency)&.to_f
100
118
  end
101
119
 
102
120
  def idle_timeout
@@ -104,6 +122,11 @@ module ActiveRecord
104
122
  timeout if timeout > 0
105
123
  end
106
124
 
125
+ def keepalive
126
+ keepalive = (configuration_hash[:keepalive] || 600).to_f
127
+ keepalive if keepalive > 0
128
+ end
129
+
107
130
  def adapter
108
131
  configuration_hash[:adapter]&.to_s
109
132
  end
@@ -130,6 +153,14 @@ module ActiveRecord
130
153
  Base.configurations.primary?(name)
131
154
  end
132
155
 
156
+ # Determines whether the db:prepare task should seed the database from db/seeds.rb.
157
+ #
158
+ # If the `seeds` key is present in the config, `seeds?` will return its value. Otherwise, it
159
+ # will return `true` for the primary database and `false` for all other configs.
160
+ def seeds?
161
+ configuration_hash.fetch(:seeds, primary?)
162
+ end
163
+
133
164
  # Determines whether to dump the schema/structure files and the filename that
134
165
  # should be used.
135
166
  #
@@ -138,7 +169,7 @@ module ActiveRecord
138
169
  #
139
170
  # If the config option is set that will be used. Otherwise Rails will generate
140
171
  # the filename from the database config name.
141
- def schema_dump(format = ActiveRecord.schema_format)
172
+ def schema_dump(format = schema_format)
142
173
  if configuration_hash.key?(:schema_dump)
143
174
  if config = configuration_hash[:schema_dump]
144
175
  config
@@ -150,6 +181,12 @@ module ActiveRecord
150
181
  end
151
182
  end
152
183
 
184
+ def schema_format # :nodoc:
185
+ format = configuration_hash.fetch(:schema_format, ActiveRecord.schema_format).to_sym
186
+ raise "Invalid schema format" unless [:ruby, :sql].include?(format)
187
+ format
188
+ end
189
+
153
190
  def database_tasks? # :nodoc:
154
191
  !replica? && !!configuration_hash.fetch(:database_tasks, true)
155
192
  end
@@ -160,13 +197,34 @@ module ActiveRecord
160
197
 
161
198
  private
162
199
  def schema_file_type(format)
163
- case format
200
+ case format.to_sym
164
201
  when :ruby
165
202
  "schema.rb"
166
203
  when :sql
167
204
  "structure.sql"
168
205
  end
169
206
  end
207
+
208
+ def default_reaping_frequency
209
+ # Reap every 20 seconds by default, but run more often as necessary to
210
+ # meet other configured timeouts.
211
+ [20, idle_timeout, max_age, keepalive].compact.min
212
+ end
213
+
214
+ def validate_configuration!
215
+ if configuration_hash[:pool] && configuration_hash[:max_connections]
216
+ pool_val = configuration_hash[:pool].to_i
217
+ max_conn_val = configuration_hash[:max_connections].to_i
218
+
219
+ if pool_val != max_conn_val
220
+ raise "Ambiguous configuration: 'pool' (#{pool_val}) and 'max_connections' (#{max_conn_val}) are set to different values. Prefer just 'max_connections'."
221
+ end
222
+ end
223
+
224
+ if configuration_hash[:pool] && configuration_hash[:min_connections]
225
+ raise "Ambiguous configuration: when setting 'min_connections', use 'max_connections' instead of 'pool'."
226
+ end
227
+ end
170
228
  end
171
229
  end
172
230
  end
@@ -47,9 +47,8 @@ module ActiveRecord
47
47
  @configuration_hash[:schema_dump] = false
48
48
  end
49
49
 
50
- if @configuration_hash[:query_cache] == "false"
51
- @configuration_hash[:query_cache] = false
52
- end
50
+ query_cache = parse_query_cache
51
+ @configuration_hash[:query_cache] = query_cache unless query_cache.nil?
53
52
 
54
53
  to_boolean!(@configuration_hash, :replica)
55
54
  to_boolean!(@configuration_hash, :database_tasks)
@@ -58,6 +57,17 @@ module ActiveRecord
58
57
  end
59
58
 
60
59
  private
60
+ def parse_query_cache
61
+ case value = @configuration_hash[:query_cache]
62
+ when /\A\d+\z/
63
+ value.to_i
64
+ when "false"
65
+ false
66
+ else
67
+ value
68
+ end
69
+ end
70
+
61
71
  def to_boolean!(configuration_hash, key)
62
72
  if configuration_hash[key].is_a?(String)
63
73
  configuration_hash[key] = configuration_hash[key] != "false"
@@ -36,9 +36,11 @@ module ActiveRecord
36
36
  # to respond to `sharded?`. To implement this define the following in an
37
37
  # initializer:
38
38
  #
39
- # ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
40
- # next unless config.key?(:vitess)
41
- # VitessConfig.new(env_name, name, config)
39
+ # ActiveSupport.on_load(:active_record_database_configurations) do
40
+ # ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
41
+ # next unless config.key?(:vitess)
42
+ # VitessConfig.new(env_name, name, config)
43
+ # end
42
44
  # end
43
45
  #
44
46
  # Note: applications must handle the condition in which custom config should be
@@ -306,4 +308,6 @@ module ActiveRecord
306
308
  url
307
309
  end
308
310
  end
311
+
312
+ ActiveSupport.run_load_hooks(:active_record_database_configurations, DatabaseConfigurations)
309
313
  end