activerecord 8.0.0 → 8.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1381 -241
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -17
  7. data/lib/active_record/associations/association_scope.rb +1 -1
  8. data/lib/active_record/associations/belongs_to_association.rb +20 -4
  9. data/lib/active_record/associations/builder/association.rb +16 -5
  10. data/lib/active_record/associations/builder/belongs_to.rb +36 -8
  11. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  12. data/lib/active_record/associations/builder/has_one.rb +1 -1
  13. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  14. data/lib/active_record/associations/collection_association.rb +9 -5
  15. data/lib/active_record/associations/collection_proxy.rb +26 -4
  16. data/lib/active_record/associations/deprecation.rb +88 -0
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/foreign_association.rb +3 -1
  19. data/lib/active_record/associations/has_one_association.rb +1 -0
  20. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  21. data/lib/active_record/associations/join_dependency.rb +5 -4
  22. data/lib/active_record/associations/preloader/association.rb +1 -1
  23. data/lib/active_record/associations/preloader/batch.rb +7 -1
  24. data/lib/active_record/associations/preloader/branch.rb +1 -0
  25. data/lib/active_record/associations.rb +159 -21
  26. data/lib/active_record/attribute_methods/primary_key.rb +2 -1
  27. data/lib/active_record/attribute_methods/query.rb +34 -0
  28. data/lib/active_record/attribute_methods/serialization.rb +19 -6
  29. data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
  30. data/lib/active_record/attribute_methods.rb +24 -17
  31. data/lib/active_record/attributes.rb +40 -26
  32. data/lib/active_record/autosave_association.rb +22 -12
  33. data/lib/active_record/base.rb +3 -4
  34. data/lib/active_record/callbacks.rb +1 -2
  35. data/lib/active_record/coders/column_serializer.rb +1 -1
  36. data/lib/active_record/coders/json.rb +16 -6
  37. data/lib/active_record/coders/yaml_column.rb +2 -2
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +19 -18
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -12
  41. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +474 -112
  42. data/lib/active_record/connection_adapters/abstract/database_statements.rb +68 -41
  43. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -9
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  45. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  46. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +31 -35
  47. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  48. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +94 -28
  49. data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
  50. data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -64
  51. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +133 -40
  52. data/lib/active_record/connection_adapters/column.rb +14 -4
  53. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  54. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
  55. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  56. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +40 -25
  58. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +66 -15
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
  60. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  61. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  62. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +29 -33
  63. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  64. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  65. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  66. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  67. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
  69. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -33
  70. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +70 -32
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +99 -54
  72. data/lib/active_record/connection_adapters/postgresql_adapter.rb +40 -12
  73. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  74. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -23
  75. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  76. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +15 -3
  77. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +7 -14
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +68 -37
  79. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  80. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +8 -3
  81. data/lib/active_record/connection_adapters/trilogy_adapter.rb +16 -2
  82. data/lib/active_record/connection_adapters.rb +1 -0
  83. data/lib/active_record/connection_handling.rb +15 -10
  84. data/lib/active_record/core.rb +44 -12
  85. data/lib/active_record/counter_cache.rb +36 -9
  86. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  87. data/lib/active_record/database_configurations/database_config.rb +5 -1
  88. data/lib/active_record/database_configurations/hash_config.rb +66 -9
  89. data/lib/active_record/database_configurations/url_config.rb +15 -3
  90. data/lib/active_record/database_configurations.rb +7 -3
  91. data/lib/active_record/delegated_type.rb +19 -19
  92. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  93. data/lib/active_record/dynamic_matchers.rb +54 -69
  94. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  95. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  96. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  97. data/lib/active_record/encryption/encryptor.rb +39 -25
  98. data/lib/active_record/encryption/scheme.rb +1 -1
  99. data/lib/active_record/enum.rb +37 -20
  100. data/lib/active_record/errors.rb +23 -7
  101. data/lib/active_record/explain.rb +1 -1
  102. data/lib/active_record/explain_registry.rb +51 -2
  103. data/lib/active_record/filter_attribute_handler.rb +76 -0
  104. data/lib/active_record/fixture_set/table_row.rb +19 -2
  105. data/lib/active_record/fixtures.rb +2 -2
  106. data/lib/active_record/future_result.rb +5 -3
  107. data/lib/active_record/gem_version.rb +2 -2
  108. data/lib/active_record/inheritance.rb +9 -4
  109. data/lib/active_record/insert_all.rb +14 -9
  110. data/lib/active_record/locking/optimistic.rb +7 -0
  111. data/lib/active_record/locking/pessimistic.rb +5 -0
  112. data/lib/active_record/log_subscriber.rb +30 -22
  113. data/lib/active_record/message_pack.rb +13 -0
  114. data/lib/active_record/middleware/shard_selector.rb +34 -17
  115. data/lib/active_record/migration/command_recorder.rb +32 -6
  116. data/lib/active_record/migration/compatibility.rb +34 -24
  117. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  118. data/lib/active_record/migration.rb +31 -21
  119. data/lib/active_record/model_schema.rb +38 -12
  120. data/lib/active_record/nested_attributes.rb +10 -6
  121. data/lib/active_record/persistence.rb +35 -3
  122. data/lib/active_record/query_cache.rb +22 -15
  123. data/lib/active_record/query_logs.rb +7 -7
  124. data/lib/active_record/querying.rb +17 -5
  125. data/lib/active_record/railtie.rb +35 -6
  126. data/lib/active_record/railties/controller_runtime.rb +11 -6
  127. data/lib/active_record/railties/databases.rake +24 -20
  128. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  129. data/lib/active_record/railties/job_runtime.rb +10 -11
  130. data/lib/active_record/reflection.rb +36 -1
  131. data/lib/active_record/relation/batches.rb +25 -11
  132. data/lib/active_record/relation/calculations.rb +62 -41
  133. data/lib/active_record/relation/delegation.rb +0 -1
  134. data/lib/active_record/relation/finder_methods.rb +76 -45
  135. data/lib/active_record/relation/merger.rb +4 -2
  136. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  137. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  138. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
  139. data/lib/active_record/relation/predicate_builder.rb +10 -8
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +106 -45
  142. data/lib/active_record/relation/spawn_methods.rb +6 -6
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +63 -31
  145. data/lib/active_record/result.rb +44 -21
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +2 -0
  148. data/lib/active_record/schema.rb +1 -1
  149. data/lib/active_record/schema_dumper.rb +41 -21
  150. data/lib/active_record/scoping.rb +0 -1
  151. data/lib/active_record/secure_token.rb +3 -3
  152. data/lib/active_record/signed_id.rb +48 -19
  153. data/lib/active_record/statement_cache.rb +16 -12
  154. data/lib/active_record/store.rb +46 -19
  155. data/lib/active_record/structured_event_subscriber.rb +85 -0
  156. data/lib/active_record/table_metadata.rb +5 -20
  157. data/lib/active_record/tasks/abstract_tasks.rb +83 -0
  158. data/lib/active_record/tasks/database_tasks.rb +44 -45
  159. data/lib/active_record/tasks/mysql_database_tasks.rb +4 -56
  160. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  161. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  162. data/lib/active_record/test_databases.rb +18 -4
  163. data/lib/active_record/test_fixtures.rb +40 -6
  164. data/lib/active_record/testing/query_assertions.rb +8 -2
  165. data/lib/active_record/timestamp.rb +4 -2
  166. data/lib/active_record/transaction.rb +2 -5
  167. data/lib/active_record/transactions.rb +58 -32
  168. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  169. data/lib/active_record/type/internal/timezone.rb +7 -0
  170. data/lib/active_record/type/json.rb +13 -2
  171. data/lib/active_record/type/serialized.rb +16 -4
  172. data/lib/active_record/type/type_map.rb +1 -1
  173. data/lib/active_record/type_caster/connection.rb +2 -1
  174. data/lib/active_record/validations/associated.rb +1 -1
  175. data/lib/active_record.rb +72 -7
  176. data/lib/arel/alias_predication.rb +2 -0
  177. data/lib/arel/collectors/bind.rb +1 -1
  178. data/lib/arel/collectors/sql_string.rb +1 -1
  179. data/lib/arel/collectors/substitute_binds.rb +2 -2
  180. data/lib/arel/crud.rb +8 -11
  181. data/lib/arel/delete_manager.rb +5 -0
  182. data/lib/arel/nodes/binary.rb +1 -1
  183. data/lib/arel/nodes/count.rb +2 -2
  184. data/lib/arel/nodes/delete_statement.rb +4 -2
  185. data/lib/arel/nodes/function.rb +4 -10
  186. data/lib/arel/nodes/named_function.rb +2 -2
  187. data/lib/arel/nodes/node.rb +2 -2
  188. data/lib/arel/nodes/sql_literal.rb +1 -1
  189. data/lib/arel/nodes/update_statement.rb +4 -2
  190. data/lib/arel/nodes.rb +0 -2
  191. data/lib/arel/predications.rb +1 -3
  192. data/lib/arel/select_manager.rb +13 -4
  193. data/lib/arel/update_manager.rb +5 -0
  194. data/lib/arel/visitors/dot.rb +2 -3
  195. data/lib/arel/visitors/postgresql.rb +55 -0
  196. data/lib/arel/visitors/sqlite.rb +55 -8
  197. data/lib/arel/visitors/to_sql.rb +7 -23
  198. data/lib/arel.rb +3 -1
  199. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  200. metadata +16 -15
  201. data/lib/active_record/explain_subscriber.rb +0 -34
  202. data/lib/active_record/normalization.rb +0 -163
@@ -113,13 +113,14 @@ module ActiveRecord
113
113
  unless abstract_class?
114
114
  load_schema
115
115
  super(attribute_names)
116
- alias_attribute :id_value, :id if _has_attribute?("id")
116
+ alias_attribute :id_value, :id if _has_attribute?("id") && !_has_attribute?("id_value")
117
117
  end
118
118
 
119
- @attribute_methods_generated = true
120
-
121
119
  generate_alias_attributes
120
+
121
+ @attribute_methods_generated = true
122
122
  end
123
+
123
124
  true
124
125
  end
125
126
 
@@ -472,23 +473,29 @@ module ActiveRecord
472
473
  end
473
474
 
474
475
  def method_missing(name, ...)
475
- unless self.class.attribute_methods_generated?
476
- if self.class.method_defined?(name)
477
- # The method is explicitly defined in the model, but calls a generated
478
- # method with super. So we must resume the call chain at the right step.
479
- last_method = method(name)
480
- last_method = last_method.super_method while last_method.super_method
481
- self.class.define_attribute_methods
482
- if last_method.super_method
483
- return last_method.super_method.call(...)
484
- end
485
- elsif self.class.define_attribute_methods
486
- # Some attribute methods weren't generated yet, we retry the call
487
- return public_send(name, ...)
476
+ # We can't know whether some method was defined or not because
477
+ # multiple thread might be concurrently be in this code path.
478
+ # So the first one would define the methods and the others would
479
+ # appear to already have them.
480
+ self.class.define_attribute_methods
481
+
482
+ # So in all cases we must behave as if the method was just defined.
483
+ method = if self.class.public_method_defined?(name)
484
+ begin
485
+ self.class.public_instance_method(name)
486
+ rescue NameError
487
+ nil
488
488
  end
489
489
  end
490
490
 
491
- super
491
+ # The method might be explicitly defined in the model, but call a generated
492
+ # method with super. So we must resume the call chain at the right step.
493
+ method = method.super_method while method && !method.owner.is_a?(GeneratedAttributeMethods)
494
+ if method
495
+ method.bind_call(self, ...)
496
+ else
497
+ super
498
+ end
492
499
  end
493
500
 
494
501
  def attribute_method?(attr_name)
@@ -7,6 +7,7 @@ module ActiveRecord
7
7
  module Attributes
8
8
  extend ActiveSupport::Concern
9
9
  include ActiveModel::AttributeRegistration
10
+ include ActiveModel::Attributes::Normalization
10
11
 
11
12
  # = Active Record \Attributes
12
13
  module ClassMethods
@@ -21,28 +22,34 @@ module ActiveRecord
21
22
  # your domain objects across much of Active Record, without having to
22
23
  # rely on implementation details or monkey patching.
23
24
  #
24
- # +name+ The name of the methods to define attribute methods for, and the
25
- # column which this will persist to.
25
+ # ==== Parameters
26
26
  #
27
- # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
28
- # to be used for this attribute. If this parameter is not passed, the previously
29
- # defined type (if any) will be used.
30
- # Otherwise, the type will be ActiveModel::Type::Value.
31
- # See the examples below for more information about providing custom type objects.
27
+ # [+name+]
28
+ # The name of the methods to define attribute methods for, and the
29
+ # column which this will persist to.
32
30
  #
33
- # ==== Options
31
+ # [+cast_type+]
32
+ # A symbol such as +:string+ or +:integer+, or a type object to be used
33
+ # for this attribute. If this parameter is not passed, the previously
34
+ # defined type (if any) will be used. Otherwise, the type will be
35
+ # ActiveModel::Type::Value. See the examples below for more information
36
+ # about providing custom type objects.
34
37
  #
35
- # The following options are accepted:
38
+ # ==== Options
36
39
  #
37
- # +default+ The default value to use when no value is provided. If this option
38
- # is not passed, the previously defined default value (if any) on the superclass or in the schema will be used.
39
- # Otherwise, the default will be +nil+.
40
+ # [+:default+]
41
+ # The default value to use when no value is provided. If this option is
42
+ # not passed, the previously defined default value (if any) on the
43
+ # superclass or in the schema will be used. Otherwise, the default will
44
+ # be +nil+.
40
45
  #
41
- # +array+ (PostgreSQL only) specifies that the type should be an array (see the
42
- # examples below).
46
+ # [+:array+]
47
+ # (PostgreSQL only) Specifies that the type should be an array. See the
48
+ # examples below.
43
49
  #
44
- # +range+ (PostgreSQL only) specifies that the type should be a range (see the
45
- # examples below).
50
+ # [+:range+]
51
+ # (PostgreSQL only) Specifies that the type should be a range. See the
52
+ # examples below.
46
53
  #
47
54
  # When using a symbol for +cast_type+, extra options are forwarded to the
48
55
  # constructor of the type object.
@@ -178,8 +185,8 @@ module ActiveRecord
178
185
  # @currency_converter = currency_converter
179
186
  # end
180
187
  #
181
- # # value will be the result of +deserialize+ or
182
- # # +cast+. Assumed to be an instance of +Money+ in
188
+ # # value will be the result of #deserialize or
189
+ # # #cast. Assumed to be an instance of Money in
183
190
  # # this case.
184
191
  # def serialize(value)
185
192
  # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
@@ -217,17 +224,22 @@ module ActiveRecord
217
224
  # is provided so it can be used by plugin authors, application code
218
225
  # should probably use ClassMethods#attribute.
219
226
  #
220
- # +name+ The name of the attribute being defined. Expected to be a +String+.
227
+ # ==== Parameters
228
+ #
229
+ # [+name+]
230
+ # The name of the attribute being defined. Expected to be a +String+.
221
231
  #
222
- # +cast_type+ The type object to use for this attribute.
232
+ # [+cast_type+]
233
+ # The type object to use for this attribute.
223
234
  #
224
- # +default+ The default value to use when no value is provided. If this option
225
- # is not passed, the previous default value (if any) will be used.
226
- # Otherwise, the default will be +nil+. A proc can also be passed, and
227
- # will be called once each time a new value is needed.
235
+ # [+default+]
236
+ # The default value to use when no value is provided. If this option
237
+ # is not passed, the previous default value (if any) will be used.
238
+ # Otherwise, the default will be +nil+. A proc can also be passed, and
239
+ # will be called once each time a new value is needed.
228
240
  #
229
- # +user_provided_default+ Whether the default value should be cast using
230
- # +cast+ or +deserialize+.
241
+ # [+user_provided_default+]
242
+ # Whether the default value should be cast using +cast+ or +deserialize+.
231
243
  def define_attribute(
232
244
  name,
233
245
  cast_type,
@@ -240,6 +252,7 @@ module ActiveRecord
240
252
 
241
253
  def _default_attributes # :nodoc:
242
254
  @default_attributes ||= begin
255
+ # TODO: Remove the need for a connection after we release 8.1.
243
256
  attributes_hash = with_connection do |connection|
244
257
  columns_hash.transform_values do |column|
245
258
  ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
@@ -299,6 +312,7 @@ module ActiveRecord
299
312
  end
300
313
 
301
314
  def type_for_column(connection, column)
315
+ # TODO: Remove the need for a connection after we release 8.1.
302
316
  hook_attribute_type(column.name, super)
303
317
  end
304
318
  end
@@ -372,19 +372,29 @@ module ActiveRecord
372
372
  return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
373
373
 
374
374
  context = validation_context if custom_validation_context?
375
+ return true if record.valid?(context)
375
376
 
376
- unless valid = record.valid?(context)
377
- if association.options[:autosave]
378
- record.errors.each { |error|
379
- self.errors.objects.append(
380
- Associations::NestedError.new(association, error)
381
- )
382
- }
383
- else
384
- errors.add(association.reflection.name)
385
- end
377
+ if context || record.changed_for_autosave?
378
+ associated_errors = record.errors.objects
379
+ else
380
+ # If there are existing invalid records in the DB, we should still be able to reference them.
381
+ # Unless a record (no matter where in the association chain) is invalid and is being changed.
382
+ associated_errors = record.errors.objects.select { |error| error.is_a?(Associations::NestedError) }
386
383
  end
387
- valid
384
+
385
+ if association.options[:autosave]
386
+ return if equal?(record)
387
+
388
+ associated_errors.each { |error|
389
+ errors.objects.append(
390
+ Associations::NestedError.new(association, error)
391
+ )
392
+ }
393
+ elsif associated_errors.any?
394
+ errors.add(association.reflection.name)
395
+ end
396
+
397
+ errors.any?
388
398
  end
389
399
 
390
400
  # Is used as an around_save callback to check while saving a collection
@@ -517,7 +527,7 @@ module ActiveRecord
517
527
  return false unless reflection.inverse_of&.polymorphic?
518
528
 
519
529
  class_name = record._read_attribute(reflection.inverse_of.foreign_type)
520
- reflection.active_record != record.class.polymorphic_class_for(class_name)
530
+ reflection.active_record.polymorphic_name != class_name
521
531
  end
522
532
 
523
533
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
@@ -6,7 +6,7 @@ require "active_support/descendants_tracker"
6
6
  require "active_support/time"
7
7
  require "active_support/core_ext/class/subclasses"
8
8
  require "active_record/log_subscriber"
9
- require "active_record/explain_subscriber"
9
+ require "active_record/structured_event_subscriber"
10
10
  require "active_record/relation/delegation"
11
11
  require "active_record/attributes"
12
12
  require "active_record/type_caster"
@@ -256,13 +256,13 @@ module ActiveRecord # :nodoc:
256
256
  # * AssociationTypeMismatch - The object assigned to the association wasn't of the type
257
257
  # specified in the association definition.
258
258
  # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
259
- # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
259
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=] method.
260
260
  # You can inspect the +attribute+ property of the exception object to determine which attribute
261
261
  # triggered the error.
262
262
  # * ConnectionNotEstablished - No connection has been established.
263
263
  # Use {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] before querying.
264
264
  # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
265
- # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
265
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=] method.
266
266
  # The +errors+ property of this exception contains an array of
267
267
  # AttributeAssignmentError
268
268
  # objects that should be inspected to determine which attributes triggered the errors.
@@ -328,7 +328,6 @@ module ActiveRecord # :nodoc:
328
328
  include TokenFor
329
329
  include SignedId
330
330
  include Suppressor
331
- include Normalization
332
331
  include Marshalling::Methods
333
332
 
334
333
  self.param_delimiter = "_"
@@ -173,8 +173,7 @@ module ActiveRecord
173
173
  #
174
174
  # If a <tt>before_*</tt> callback throws +:abort+, all the later callbacks and
175
175
  # the associated action are cancelled.
176
- # \Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
177
- # methods on the model, which are called last.
176
+ # \Callbacks are run in the order they are defined.
178
177
  #
179
178
  # == Ordering callbacks
180
179
  #
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
- module Coders # :nodoc:
4
+ module Coders
5
5
  class ColumnSerializer # :nodoc:
6
6
  attr_reader :object_class
7
7
  attr_reader :coder
@@ -1,14 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/json"
4
+
3
5
  module ActiveRecord
4
- module Coders # :nodoc:
5
- module JSON # :nodoc:
6
- def self.dump(obj)
7
- ActiveSupport::JSON.encode(obj)
6
+ module Coders
7
+ class JSON # :nodoc:
8
+ DEFAULT_ENCODE_OPTIONS = { escape: false }.freeze
9
+
10
+ def initialize(encode_options: nil, decode_options: nil)
11
+ encode_options = encode_options ? DEFAULT_ENCODE_OPTIONS.merge(encode_options) : DEFAULT_ENCODE_OPTIONS
12
+ @decode_options = decode_options
13
+ @encoder = ActiveSupport::JSON::Encoding.json_encoder.new(encode_options)
14
+ end
15
+
16
+ def dump(obj)
17
+ @encoder.encode(obj)
8
18
  end
9
19
 
10
- def self.load(json)
11
- ActiveSupport::JSON.decode(json) unless json.blank?
20
+ def load(json)
21
+ ActiveSupport::JSON.decode(json, @decode_options) unless json.blank?
12
22
  end
13
23
  end
14
24
  end
@@ -3,9 +3,9 @@
3
3
  require "yaml"
4
4
 
5
5
  module ActiveRecord
6
- module Coders # :nodoc:
6
+ module Coders
7
7
  class YAMLColumn < ColumnSerializer # :nodoc:
8
- class SafeCoder
8
+ class SafeCoder # :nodoc:
9
9
  def initialize(permitted_classes: [], unsafe_load: nil)
10
10
  @permitted_classes = permitted_classes
11
11
  @unsafe_load = unsafe_load
@@ -54,19 +54,22 @@ module ActiveRecord
54
54
  # about the model. The model needs to pass a connection specification name to the handler,
55
55
  # in order to look up the correct connection pool.
56
56
  class ConnectionHandler
57
- class StringConnectionName # :nodoc:
58
- attr_reader :name
59
-
60
- def initialize(name)
57
+ class ConnectionDescriptor # :nodoc:
58
+ def initialize(name, primary = false)
61
59
  @name = name
60
+ @primary = primary
61
+ end
62
+
63
+ def name
64
+ primary_class? ? "ActiveRecord::Base" : @name
62
65
  end
63
66
 
64
67
  def primary_class?
65
- false
68
+ @primary
66
69
  end
67
70
 
68
71
  def current_preventing_writes
69
- false
72
+ ActiveRecord::Base.preventing_writes?(@name)
70
73
  end
71
74
  end
72
75
 
@@ -115,7 +118,7 @@ module ActiveRecord
115
118
  pool_config = resolve_pool_config(config, owner_name, role, shard)
116
119
  db_config = pool_config.db_config
117
120
 
118
- pool_manager = set_pool_manager(pool_config.connection_name)
121
+ pool_manager = set_pool_manager(pool_config.connection_descriptor)
119
122
 
120
123
  # If there is an existing pool with the same values as the pool_config
121
124
  # don't remove the connection. Connections should only be removed if we are
@@ -127,8 +130,8 @@ module ActiveRecord
127
130
  # Update the pool_config's connection class if it differs. This is used
128
131
  # for ensuring that ActiveRecord::Base and the primary_abstract_class use
129
132
  # the same pool. Without this granular swapping will not work correctly.
130
- if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name)
131
- existing_pool_config.connection_class = owner_name
133
+ if owner_name.primary_class? && (existing_pool_config.connection_descriptor != owner_name)
134
+ existing_pool_config.connection_descriptor = owner_name
132
135
  end
133
136
 
134
137
  existing_pool_config.pool
@@ -137,7 +140,7 @@ module ActiveRecord
137
140
  pool_manager.set_pool_config(role, shard, pool_config)
138
141
 
139
142
  payload = {
140
- connection_name: pool_config.connection_name,
143
+ connection_name: pool_config.connection_descriptor.name,
141
144
  role: role,
142
145
  shard: shard,
143
146
  config: db_config.configuration_hash
@@ -155,9 +158,7 @@ module ActiveRecord
155
158
  each_connection_pool(role).any?(&:active_connection?)
156
159
  end
157
160
 
158
- # Returns any connections in use by the current thread back to the pool,
159
- # and also returns connections to the pool cached by threads that are no
160
- # longer alive.
161
+ # Returns any connections in use by the current thread back to the pool.
161
162
  def clear_active_connections!(role = nil)
162
163
  each_connection_pool(role).each do |pool|
163
164
  pool.release_connection
@@ -165,7 +166,7 @@ module ActiveRecord
165
166
  end
166
167
  end
167
168
 
168
- # Clears the cache which maps classes.
169
+ # Clears reloadable connection caches in all connection pools.
169
170
  #
170
171
  # See ConnectionPool#clear_reloadable_connections! for details.
171
172
  def clear_reloadable_connections!(role = nil)
@@ -242,8 +243,8 @@ module ActiveRecord
242
243
  end
243
244
 
244
245
  # Get the existing pool manager or initialize and assign a new one.
245
- def set_pool_manager(connection_name)
246
- connection_name_to_pool_manager[connection_name] ||= PoolManager.new
246
+ def set_pool_manager(connection_descriptor)
247
+ connection_name_to_pool_manager[connection_descriptor.name] ||= PoolManager.new
247
248
  end
248
249
 
249
250
  def pool_managers
@@ -278,9 +279,9 @@ module ActiveRecord
278
279
 
279
280
  def determine_owner_name(owner_name, config)
280
281
  if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
281
- StringConnectionName.new(owner_name.to_s)
282
+ ConnectionDescriptor.new(owner_name.to_s)
282
283
  elsif config.is_a?(Symbol)
283
- StringConnectionName.new(config.to_s)
284
+ ConnectionDescriptor.new(config.to_s)
284
285
  else
285
286
  owner_name
286
287
  end
@@ -40,6 +40,14 @@ module ActiveRecord
40
40
  end
41
41
  end
42
42
 
43
+ # Add +element+ to the back of the queue. Never blocks.
44
+ def add_back(element)
45
+ synchronize do
46
+ @queue.unshift element
47
+ @cond.signal
48
+ end
49
+ end
50
+
43
51
  # If +element+ is in the queue, remove and return it, or +nil+.
44
52
  def delete(element)
45
53
  synchronize do
@@ -54,6 +62,13 @@ module ActiveRecord
54
62
  end
55
63
  end
56
64
 
65
+ # Number of elements in the queue.
66
+ def size
67
+ synchronize do
68
+ @queue.size
69
+ end
70
+ end
71
+
57
72
  # Remove the head of the queue.
58
73
  #
59
74
  # If +timeout+ is not given, remove and return the head of the
@@ -114,9 +129,7 @@ module ActiveRecord
114
129
  t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
115
130
  elapsed = 0
116
131
  loop do
117
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
118
- @cond.wait(timeout - elapsed)
119
- end
132
+ @cond.wait(timeout - elapsed)
120
133
 
121
134
  return remove if any?
122
135
 
@@ -7,12 +7,29 @@ module ActiveRecord
7
7
  class ConnectionPool
8
8
  # = Active Record Connection Pool \Reaper
9
9
  #
10
- # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
11
- # +pool+. A reaper instantiated with a zero frequency will never reap
12
- # the connection pool.
10
+ # The reaper is a singleton that exists in the background of the process
11
+ # and is responsible for general maintenance of all the connection pools.
13
12
  #
14
- # Configure the frequency by setting +reaping_frequency+ in your database
15
- # YAML file (default 60 seconds).
13
+ # It will reclaim connections that are leased to now-dead threads,
14
+ # ensuring that a bad thread can't leak a pool slot forever. By definition,
15
+ # this involves touching currently-leased connections, but that is safe
16
+ # because the owning thread is known to be dead.
17
+ #
18
+ # Beyond that, it manages the health of available / unleased connections:
19
+ # * retiring connections that have been idle[1] for too long
20
+ # * creating occasional activity on inactive[1] connections
21
+ # * keeping the pool prepopulated up to its minimum size
22
+ # * proactively connecting to the target database from any pooled
23
+ # connections that had lazily deferred that step
24
+ # * resetting or replacing connections that are known to be broken
25
+ #
26
+ #
27
+ # [1]: "idle" and "inactive" here distinguish between connections that
28
+ # have not been requested by the application in a while (idle) and those
29
+ # that have not spoken to their remote server in a while (inactive). The
30
+ # former is a desirable opportunity to reduce our connection count
31
+ # (`idle_timeout`); the latter is a risk that the server or a firewall may
32
+ # drop a connection we still anticipate using (avoided by `keepalive`).
16
33
  class Reaper
17
34
  attr_reader :pool, :frequency
18
35
 
@@ -36,6 +53,32 @@ module ActiveRecord
36
53
  end
37
54
  end
38
55
 
56
+ def discard_pool(pool) # :nodoc:
57
+ @mutex.synchronize do
58
+ @pools.each do |frequency, refs|
59
+ refs.reject! do |ref|
60
+ ref.__getobj__ == pool
61
+ rescue WeakRef::RefError
62
+ true
63
+ end
64
+
65
+ if refs.empty?
66
+ @pools.delete(frequency)
67
+ @threads.delete(frequency)&.tap { |t| t.kill; t.join }
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ def pools(refs = nil) # :nodoc:
74
+ refs ||= @mutex.synchronize { @pools.values.flatten(1) }
75
+
76
+ refs.filter_map do |ref|
77
+ ref.__getobj__ if ref.weakref_alive?
78
+ rescue WeakRef::RefError
79
+ end.select(&:maintainable?)
80
+ end
81
+
39
82
  private
40
83
  def spawn_thread(frequency)
41
84
  Thread.new(frequency) do |t|
@@ -46,23 +89,36 @@ module ActiveRecord
46
89
  running = true
47
90
  while running
48
91
  sleep t
92
+
93
+ refs = nil
94
+
49
95
  @mutex.synchronize do
50
- @pools[frequency].select! do |pool|
51
- pool.weakref_alive? && !pool.discarded?
52
- end
96
+ refs = @pools[frequency]
53
97
 
54
- @pools[frequency].each do |p|
55
- p.reap
56
- p.flush
98
+ refs.select! do |pool|
99
+ pool.weakref_alive? && !pool.discarded?
57
100
  rescue WeakRef::RefError
58
101
  end
59
102
 
60
- if @pools[frequency].empty?
103
+ if refs.empty?
61
104
  @pools.delete(frequency)
62
105
  @threads.delete(frequency)
63
106
  running = false
64
107
  end
65
108
  end
109
+
110
+ if running
111
+ pools(refs).each do |pool|
112
+ pool.reaper_lock do
113
+ pool.reap
114
+ pool.flush
115
+ pool.prepopulate
116
+ pool.retire_old_connections
117
+ pool.keep_alive
118
+ pool.preconnect
119
+ end
120
+ end
121
+ end
66
122
  end
67
123
  end
68
124
  end