activerecord 7.2.4 → 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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1013 -878
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +69 -27
  6. data/lib/active_record/associations/association_scope.rb +1 -1
  7. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +36 -8
  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 +7 -3
  14. data/lib/active_record/associations/collection_proxy.rb +26 -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/foreign_association.rb +3 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  20. data/lib/active_record/associations/has_one_association.rb +1 -0
  21. data/lib/active_record/associations/join_dependency.rb +3 -2
  22. data/lib/active_record/associations/preloader/association.rb +3 -3
  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/singular_association.rb +8 -3
  26. data/lib/active_record/associations.rb +192 -24
  27. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  28. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  29. data/lib/active_record/attribute_methods/query.rb +34 -0
  30. data/lib/active_record/attribute_methods/serialization.rb +18 -5
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  32. data/lib/active_record/attributes.rb +3 -0
  33. data/lib/active_record/autosave_association.rb +69 -27
  34. data/lib/active_record/base.rb +1 -2
  35. data/lib/active_record/callbacks.rb +1 -2
  36. data/lib/active_record/coders/column_serializer.rb +1 -1
  37. data/lib/active_record/coders/json.rb +16 -6
  38. data/lib/active_record/coders/yaml_column.rb +2 -2
  39. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  41. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
  53. data/lib/active_record/connection_adapters/column.rb +14 -4
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  56. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  57. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
  59. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  60. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  61. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  62. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  63. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
  64. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  65. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  67. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  69. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  70. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  71. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  72. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  73. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  75. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  76. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  77. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  78. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  79. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  80. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  81. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
  82. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
  83. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  84. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
  85. data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
  86. data/lib/active_record/connection_adapters.rb +1 -56
  87. data/lib/active_record/connection_handling.rb +25 -2
  88. data/lib/active_record/core.rb +33 -17
  89. data/lib/active_record/counter_cache.rb +35 -8
  90. data/lib/active_record/database_configurations/database_config.rb +9 -1
  91. data/lib/active_record/database_configurations/hash_config.rb +74 -9
  92. data/lib/active_record/database_configurations/url_config.rb +15 -3
  93. data/lib/active_record/database_configurations.rb +7 -3
  94. data/lib/active_record/delegated_type.rb +1 -1
  95. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  96. data/lib/active_record/dynamic_matchers.rb +54 -69
  97. data/lib/active_record/encryption/config.rb +3 -1
  98. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  99. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  100. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  101. data/lib/active_record/encryption/encryptor.rb +28 -8
  102. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  103. data/lib/active_record/encryption/scheme.rb +9 -2
  104. data/lib/active_record/enum.rb +33 -30
  105. data/lib/active_record/errors.rb +33 -9
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/explain_registry.rb +51 -2
  108. data/lib/active_record/filter_attribute_handler.rb +76 -0
  109. data/lib/active_record/fixtures.rb +2 -4
  110. data/lib/active_record/future_result.rb +15 -9
  111. data/lib/active_record/gem_version.rb +2 -2
  112. data/lib/active_record/inheritance.rb +9 -4
  113. data/lib/active_record/insert_all.rb +14 -9
  114. data/lib/active_record/locking/optimistic.rb +8 -1
  115. data/lib/active_record/locking/pessimistic.rb +5 -0
  116. data/lib/active_record/log_subscriber.rb +31 -29
  117. data/lib/active_record/message_pack.rb +13 -0
  118. data/lib/active_record/middleware/shard_selector.rb +34 -17
  119. data/lib/active_record/migration/command_recorder.rb +56 -13
  120. data/lib/active_record/migration/compatibility.rb +37 -24
  121. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  122. data/lib/active_record/migration.rb +48 -42
  123. data/lib/active_record/model_schema.rb +40 -15
  124. data/lib/active_record/nested_attributes.rb +14 -12
  125. data/lib/active_record/persistence.rb +163 -133
  126. data/lib/active_record/query_cache.rb +22 -15
  127. data/lib/active_record/query_logs.rb +100 -52
  128. data/lib/active_record/query_logs_formatter.rb +17 -28
  129. data/lib/active_record/querying.rb +21 -9
  130. data/lib/active_record/railtie.rb +35 -30
  131. data/lib/active_record/railties/controller_runtime.rb +11 -6
  132. data/lib/active_record/railties/databases.rake +26 -38
  133. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  134. data/lib/active_record/railties/job_runtime.rb +10 -11
  135. data/lib/active_record/reflection.rb +54 -22
  136. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  137. data/lib/active_record/relation/batches.rb +147 -73
  138. data/lib/active_record/relation/calculations.rb +60 -43
  139. data/lib/active_record/relation/delegation.rb +25 -15
  140. data/lib/active_record/relation/finder_methods.rb +73 -43
  141. data/lib/active_record/relation/merger.rb +12 -10
  142. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  143. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  144. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  145. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  146. data/lib/active_record/relation/predicate_builder.rb +23 -8
  147. data/lib/active_record/relation/query_attribute.rb +3 -1
  148. data/lib/active_record/relation/query_methods.rb +200 -96
  149. data/lib/active_record/relation/spawn_methods.rb +7 -7
  150. data/lib/active_record/relation/where_clause.rb +2 -9
  151. data/lib/active_record/relation.rb +127 -87
  152. data/lib/active_record/result.rb +109 -24
  153. data/lib/active_record/runtime_registry.rb +42 -58
  154. data/lib/active_record/sanitization.rb +9 -6
  155. data/lib/active_record/schema.rb +1 -1
  156. data/lib/active_record/schema_dumper.rb +17 -10
  157. data/lib/active_record/schema_migration.rb +2 -1
  158. data/lib/active_record/scoping/named.rb +5 -2
  159. data/lib/active_record/scoping.rb +0 -1
  160. data/lib/active_record/signed_id.rb +44 -16
  161. data/lib/active_record/statement_cache.rb +24 -20
  162. data/lib/active_record/store.rb +53 -22
  163. data/lib/active_record/structured_event_subscriber.rb +85 -0
  164. data/lib/active_record/table_metadata.rb +6 -23
  165. data/lib/active_record/tasks/abstract_tasks.rb +83 -0
  166. data/lib/active_record/tasks/database_tasks.rb +85 -85
  167. data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
  168. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  169. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  170. data/lib/active_record/test_databases.rb +18 -4
  171. data/lib/active_record/test_fixtures.rb +52 -6
  172. data/lib/active_record/testing/query_assertions.rb +8 -2
  173. data/lib/active_record/timestamp.rb +4 -2
  174. data/lib/active_record/token_for.rb +1 -1
  175. data/lib/active_record/transaction.rb +2 -5
  176. data/lib/active_record/transactions.rb +56 -32
  177. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  178. data/lib/active_record/type/internal/timezone.rb +7 -0
  179. data/lib/active_record/type/json.rb +13 -2
  180. data/lib/active_record/type/serialized.rb +16 -4
  181. data/lib/active_record/type/type_map.rb +1 -1
  182. data/lib/active_record/type_caster/connection.rb +2 -1
  183. data/lib/active_record/validations/associated.rb +1 -1
  184. data/lib/active_record/validations/uniqueness.rb +8 -8
  185. data/lib/active_record.rb +85 -50
  186. data/lib/arel/alias_predication.rb +2 -0
  187. data/lib/arel/collectors/bind.rb +2 -2
  188. data/lib/arel/collectors/sql_string.rb +1 -1
  189. data/lib/arel/collectors/substitute_binds.rb +2 -2
  190. data/lib/arel/crud.rb +6 -11
  191. data/lib/arel/nodes/binary.rb +1 -1
  192. data/lib/arel/nodes/count.rb +2 -2
  193. data/lib/arel/nodes/function.rb +4 -10
  194. data/lib/arel/nodes/named_function.rb +2 -2
  195. data/lib/arel/nodes/node.rb +2 -2
  196. data/lib/arel/nodes/sql_literal.rb +1 -1
  197. data/lib/arel/nodes.rb +0 -2
  198. data/lib/arel/predications.rb +1 -3
  199. data/lib/arel/select_manager.rb +7 -2
  200. data/lib/arel/table.rb +3 -7
  201. data/lib/arel/visitors/dot.rb +0 -3
  202. data/lib/arel/visitors/postgresql.rb +55 -0
  203. data/lib/arel/visitors/sqlite.rb +55 -8
  204. data/lib/arel/visitors/to_sql.rb +4 -22
  205. data/lib/arel.rb +3 -1
  206. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  207. metadata +15 -12
  208. data/lib/active_record/explain_subscriber.rb +0 -34
  209. data/lib/active_record/normalization.rb +0 -163
  210. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -3,6 +3,38 @@
3
3
  module ActiveRecord
4
4
  module AttributeMethods
5
5
  # = Active Record Attribute Methods \Query
6
+ #
7
+ # Adds query methods for attributes that return either +true+ or +false+
8
+ # depending on the attribute type and value.
9
+ #
10
+ # For Boolean attributes this will return +true+ if the value is present
11
+ # and return +false+ otherwise:
12
+ #
13
+ # class Product < ActiveRecord::Base
14
+ # end
15
+ #
16
+ # product = Product.new(archived: false)
17
+ # product.archived? # => false
18
+ # product.archived = true
19
+ # product.archived? # => true
20
+ #
21
+ # For Numeric attributes this will return +true+ if the value is a non-zero
22
+ # number and return +false+ otherwise:
23
+ #
24
+ # product.inventory_count = 0
25
+ # product.inventory_count? # => false
26
+ # product.inventory_count = 1
27
+ # product.inventory_count? # => true
28
+ #
29
+ # For other attributes it will return +true+ if the value is present
30
+ # and return +false+ otherwise:
31
+ #
32
+ # product.name = nil
33
+ # product.name? # => false
34
+ # product.name = " "
35
+ # product.name? # => false
36
+ # product.name = "Orange"
37
+ # product.name? # => true
6
38
  module Query
7
39
  extend ActiveSupport::Concern
8
40
 
@@ -10,6 +42,8 @@ module ActiveRecord
10
42
  attribute_method_suffix "?", parameters: false
11
43
  end
12
44
 
45
+ # Returns +true+ or +false+ for the attribute identified by +attr_name+,
46
+ # depending on the attribute type and value.
13
47
  def query_attribute(attr_name)
14
48
  value = self.public_send(attr_name)
15
49
 
@@ -51,6 +51,16 @@ module ActiveRecord
51
51
  # ActiveRecord::SerializationTypeMismatch error.
52
52
  # * If the column is +NULL+ or starting from a new record, the default value
53
53
  # will set to +type.new+
54
+ # * +comparable+ - Specify whether the deserialized object is safely comparable
55
+ # for the purpose of detecting changes. Defaults to +false+
56
+ # When set to +false+ the old and new values will be compared by their serialized
57
+ # representation (e.g. JSON or YAML), which can sometimes cause two objects that are
58
+ # semantically equal to be considered different.
59
+ # For instance two hashes with the same keys and values but a different order have a
60
+ # different serialized representation, but are semantically equal once deserialized.
61
+ # If set to +true+ the comparison will be done on the deserialized object.
62
+ # This options should only be enabled if the +type+ is known to have
63
+ # a proper <tt>==</tt> method that deeply compare the objects.
54
64
  # * +yaml+ - Optional. Yaml specific options. The allowed config is:
55
65
  # * +:permitted_classes+ - +Array+ with the permitted classes.
56
66
  # * +:unsafe_load+ - Unsafely load YAML blobs, allow YAML to load any class.
@@ -180,7 +190,7 @@ module ActiveRecord
180
190
  # serialize :preferences, coder: Rot13JSON
181
191
  # end
182
192
  #
183
- def serialize(attr_name, coder: nil, type: Object, yaml: {}, **options)
193
+ def serialize(attr_name, coder: nil, type: Object, comparable: false, yaml: {}, **options)
184
194
  coder ||= default_column_serializer
185
195
  unless coder
186
196
  raise ArgumentError, <<~MSG.squish
@@ -195,12 +205,12 @@ module ActiveRecord
195
205
  attribute(attr_name, **options)
196
206
 
197
207
  decorate_attributes([attr_name]) do |attr_name, cast_type|
198
- if type_incompatible_with_serialize?(cast_type, coder, type)
208
+ if type_incompatible_with_serialize?(cast_type, column_serializer, type)
199
209
  raise ColumnNotSerializableError.new(attr_name, cast_type)
200
210
  end
201
211
 
202
212
  cast_type = cast_type.subtype if Type::Serialized === cast_type
203
- Type::Serialized.new(cast_type, column_serializer)
213
+ Type::Serialized.new(cast_type, column_serializer, comparable: comparable)
204
214
  end
205
215
  end
206
216
 
@@ -209,7 +219,10 @@ module ActiveRecord
209
219
  # When ::JSON is used, force it to go through the Active Support JSON encoder
210
220
  # to ensure special objects (e.g. Active Record models) are dumped correctly
211
221
  # using the #as_json hook.
212
- coder = Coders::JSON if coder == ::JSON
222
+
223
+ if coder == ::JSON || coder == Coders::JSON
224
+ coder = Coders::JSON.new
225
+ end
213
226
 
214
227
  if coder == ::YAML || coder == Coders::YAMLColumn
215
228
  Coders::YAMLColumn.new(attr_name, type, **(yaml || {}))
@@ -223,7 +236,7 @@ module ActiveRecord
223
236
  end
224
237
 
225
238
  def type_incompatible_with_serialize?(cast_type, coder, type)
226
- cast_type.is_a?(ActiveRecord::Type::Json) && coder == ::JSON ||
239
+ cast_type.is_a?(ActiveRecord::Type::Json) && Coders::JSON === coder ||
227
240
  cast_type.respond_to?(:type_cast_array, true) && type == ::Array
228
241
  end
229
242
  end
@@ -21,14 +21,18 @@ module ActiveRecord
21
21
  set_time_zone_without_conversion(super)
22
22
  elsif value.respond_to?(:in_time_zone)
23
23
  begin
24
- super(user_input_in_time_zone(value)) || super
24
+ result = super(user_input_in_time_zone(value)) || super
25
+ if result && type == :time
26
+ result = result.change(year: 2000, month: 1, day: 1)
27
+ end
28
+ result
25
29
  rescue ArgumentError
26
30
  nil
27
31
  end
28
32
  elsif value.respond_to?(:infinite?) && value.infinite?
29
33
  value
30
34
  else
31
- map_avoiding_infinite_recursion(super) { |v| cast(v) }
35
+ map(super) { |v| cast(v) }
32
36
  end
33
37
  end
34
38
 
@@ -41,27 +45,21 @@ module ActiveRecord
41
45
  return if value.nil?
42
46
 
43
47
  if value.acts_like?(:time)
44
- value.in_time_zone
48
+ converted = value.in_time_zone
49
+ if type == :time && converted
50
+ converted = converted.change(year: 2000, month: 1, day: 1)
51
+ end
52
+ converted
45
53
  elsif value.respond_to?(:infinite?) && value.infinite?
46
54
  value
47
55
  else
48
- map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
56
+ map(value) { |v| convert_time_to_time_zone(v) }
49
57
  end
50
58
  end
51
59
 
52
60
  def set_time_zone_without_conversion(value)
53
61
  ::Time.zone.local_to_utc(value).try(:in_time_zone) if value
54
62
  end
55
-
56
- def map_avoiding_infinite_recursion(value)
57
- map(value) do |v|
58
- if value.equal?(v)
59
- nil
60
- else
61
- yield(v)
62
- end
63
- end
64
- end
65
63
  end
66
64
 
67
65
  extend ActiveSupport::Concern
@@ -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
@@ -251,6 +252,7 @@ module ActiveRecord
251
252
 
252
253
  def _default_attributes # :nodoc:
253
254
  @default_attributes ||= begin
255
+ # TODO: Remove the need for a connection after we release 8.1.
254
256
  attributes_hash = with_connection do |connection|
255
257
  columns_hash.transform_values do |column|
256
258
  ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
@@ -310,6 +312,7 @@ module ActiveRecord
310
312
  end
311
313
 
312
314
  def type_for_column(connection, column)
315
+ # TODO: Remove the need for a connection after we release 8.1.
313
316
  hook_attribute_type(column.name, super)
314
317
  end
315
318
  end
@@ -221,8 +221,10 @@ module ActiveRecord
221
221
  if reflection.validate? && !method_defined?(validation_method)
222
222
  if reflection.collection?
223
223
  method = :validate_collection_association
224
+ elsif reflection.has_one?
225
+ method = :validate_has_one_association
224
226
  else
225
- method = :validate_single_association
227
+ method = :validate_belongs_to_association
226
228
  end
227
229
 
228
230
  define_non_cyclic_method(validation_method) { send(method, reflection) }
@@ -274,6 +276,16 @@ module ActiveRecord
274
276
  new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
275
277
  end
276
278
 
279
+ def validating_belongs_to_for?(association)
280
+ @validating_belongs_to_for ||= {}
281
+ @validating_belongs_to_for[association]
282
+ end
283
+
284
+ def autosaving_belongs_to_for?(association)
285
+ @autosaving_belongs_to_for ||= {}
286
+ @autosaving_belongs_to_for[association]
287
+ end
288
+
277
289
  private
278
290
  def init_internals
279
291
  super
@@ -313,11 +325,33 @@ module ActiveRecord
313
325
  end
314
326
 
315
327
  # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
316
- # turned on for the association.
317
- def validate_single_association(reflection)
328
+ # turned on for the has_one association.
329
+ def validate_has_one_association(reflection)
330
+ association = association_instance_get(reflection.name)
331
+ record = association && association.reader
332
+ return unless record && (record.changed_for_autosave? || custom_validation_context?)
333
+
334
+ inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
335
+ return if inverse_association && (record.validating_belongs_to_for?(inverse_association) ||
336
+ record.autosaving_belongs_to_for?(inverse_association))
337
+
338
+ association_valid?(association, record)
339
+ end
340
+
341
+ # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
342
+ # turned on for the belongs_to association.
343
+ def validate_belongs_to_association(reflection)
318
344
  association = association_instance_get(reflection.name)
319
345
  record = association && association.reader
320
- association_valid?(association, record) if record && (record.changed_for_autosave? || custom_validation_context?)
346
+ return unless record && (record.changed_for_autosave? || custom_validation_context?)
347
+
348
+ begin
349
+ @validating_belongs_to_for ||= {}
350
+ @validating_belongs_to_for[association] = true
351
+ association_valid?(association, record)
352
+ ensure
353
+ @validating_belongs_to_for[association] = false
354
+ end
321
355
  end
322
356
 
323
357
  # Validate the associated records if <tt>:validate</tt> or
@@ -441,33 +475,34 @@ module ActiveRecord
441
475
  return unless association && association.loaded?
442
476
 
443
477
  record = association.load_target
478
+ return unless record && !record.destroyed?
444
479
 
445
- if record && !record.destroyed?
446
- autosave = reflection.options[:autosave]
447
-
448
- if autosave && record.marked_for_destruction?
449
- record.destroy
450
- elsif autosave != false
451
- primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
452
- primary_key_value = primary_key.map { |key| _read_attribute(key) }
480
+ autosave = reflection.options[:autosave]
453
481
 
454
- if (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
455
- unless reflection.through_reflection
456
- foreign_key = Array(reflection.foreign_key)
457
- primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
482
+ if autosave && record.marked_for_destruction?
483
+ record.destroy
484
+ elsif autosave != false
485
+ primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
486
+ primary_key_value = primary_key.map { |key| _read_attribute(key) }
487
+ return unless (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
458
488
 
459
- primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
460
- association_id = _read_attribute(primary_key)
461
- record[foreign_key] = association_id unless record[foreign_key] == association_id
462
- end
463
- association.set_inverse_instance(record)
464
- end
489
+ unless reflection.through_reflection
490
+ foreign_key = Array(reflection.foreign_key)
491
+ primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
465
492
 
466
- saved = record.save(validate: !autosave)
467
- raise ActiveRecord::Rollback if !saved && autosave
468
- saved
493
+ primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
494
+ association_id = _read_attribute(primary_key)
495
+ record[foreign_key] = association_id unless record[foreign_key] == association_id
469
496
  end
497
+ association.set_inverse_instance(record)
470
498
  end
499
+
500
+ inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
501
+ return if inverse_association && record.autosaving_belongs_to_for?(inverse_association)
502
+
503
+ saved = record.save(validate: !autosave)
504
+ raise ActiveRecord::Rollback if !saved && autosave
505
+ saved
471
506
  end
472
507
  end
473
508
 
@@ -492,7 +527,6 @@ module ActiveRecord
492
527
  return false unless reflection.inverse_of&.polymorphic?
493
528
 
494
529
  class_name = record._read_attribute(reflection.inverse_of.foreign_type)
495
-
496
530
  reflection.active_record.polymorphic_name != class_name
497
531
  end
498
532
 
@@ -512,7 +546,15 @@ module ActiveRecord
512
546
  foreign_key.each { |key| self[key] = nil }
513
547
  record.destroy
514
548
  elsif autosave != false
515
- saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
549
+ saved = if record.new_record? || (autosave && record.changed_for_autosave?)
550
+ begin
551
+ @autosaving_belongs_to_for ||= {}
552
+ @autosaving_belongs_to_for[association] = true
553
+ record.save(validate: !autosave)
554
+ ensure
555
+ @autosaving_belongs_to_for[association] = false
556
+ end
557
+ end
516
558
 
517
559
  if association.updated?
518
560
  primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
@@ -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"
@@ -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
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "concurrent/map"
5
4
 
6
5
  module ActiveRecord
@@ -55,19 +54,22 @@ module ActiveRecord
55
54
  # about the model. The model needs to pass a connection specification name to the handler,
56
55
  # in order to look up the correct connection pool.
57
56
  class ConnectionHandler
58
- class StringConnectionName # :nodoc:
59
- attr_reader :name
60
-
61
- def initialize(name)
57
+ class ConnectionDescriptor # :nodoc:
58
+ def initialize(name, primary = false)
62
59
  @name = name
60
+ @primary = primary
61
+ end
62
+
63
+ def name
64
+ primary_class? ? "ActiveRecord::Base" : @name
63
65
  end
64
66
 
65
67
  def primary_class?
66
- false
68
+ @primary
67
69
  end
68
70
 
69
71
  def current_preventing_writes
70
- false
72
+ ActiveRecord::Base.preventing_writes?(@name)
71
73
  end
72
74
  end
73
75
 
@@ -116,7 +118,7 @@ module ActiveRecord
116
118
  pool_config = resolve_pool_config(config, owner_name, role, shard)
117
119
  db_config = pool_config.db_config
118
120
 
119
- pool_manager = set_pool_manager(pool_config.connection_name)
121
+ pool_manager = set_pool_manager(pool_config.connection_descriptor)
120
122
 
121
123
  # If there is an existing pool with the same values as the pool_config
122
124
  # don't remove the connection. Connections should only be removed if we are
@@ -128,8 +130,8 @@ module ActiveRecord
128
130
  # Update the pool_config's connection class if it differs. This is used
129
131
  # for ensuring that ActiveRecord::Base and the primary_abstract_class use
130
132
  # the same pool. Without this granular swapping will not work correctly.
131
- if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name)
132
- 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
133
135
  end
134
136
 
135
137
  existing_pool_config.pool
@@ -138,7 +140,7 @@ module ActiveRecord
138
140
  pool_manager.set_pool_config(role, shard, pool_config)
139
141
 
140
142
  payload = {
141
- connection_name: pool_config.connection_name,
143
+ connection_name: pool_config.connection_descriptor.name,
142
144
  role: role,
143
145
  shard: shard,
144
146
  config: db_config.configuration_hash
@@ -156,9 +158,7 @@ module ActiveRecord
156
158
  each_connection_pool(role).any?(&:active_connection?)
157
159
  end
158
160
 
159
- # Returns any connections in use by the current thread back to the pool,
160
- # and also returns connections to the pool cached by threads that are no
161
- # longer alive.
161
+ # Returns any connections in use by the current thread back to the pool.
162
162
  def clear_active_connections!(role = nil)
163
163
  each_connection_pool(role).each do |pool|
164
164
  pool.release_connection
@@ -166,7 +166,7 @@ module ActiveRecord
166
166
  end
167
167
  end
168
168
 
169
- # Clears the cache which maps classes.
169
+ # Clears reloadable connection caches in all connection pools.
170
170
  #
171
171
  # See ConnectionPool#clear_reloadable_connections! for details.
172
172
  def clear_reloadable_connections!(role = nil)
@@ -210,18 +210,25 @@ module ActiveRecord
210
210
  # This makes retrieving the connection pool O(1) once the process is warm.
211
211
  # When a connection is established or removed, we invalidate the cache.
212
212
  def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
213
- pool = get_pool_manager(connection_name)&.get_pool_config(role, shard)&.pool
213
+ pool_manager = get_pool_manager(connection_name)
214
+ pool = pool_manager&.get_pool_config(role, shard)&.pool
214
215
 
215
216
  if strict && !pool
216
- if shard != ActiveRecord::Base.default_shard
217
- message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
218
- elsif role != ActiveRecord::Base.default_role
219
- message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
220
- else
221
- message = "No connection pool for '#{connection_name}' found."
222
- end
217
+ selector = [
218
+ ("'#{shard}' shard" unless shard == ActiveRecord::Base.default_shard),
219
+ ("'#{role}' role" unless role == ActiveRecord::Base.default_role),
220
+ ].compact.join(" and ")
221
+
222
+ selector = [
223
+ (connection_name unless connection_name == "ActiveRecord::Base"),
224
+ selector.presence,
225
+ ].compact.join(" with ")
226
+
227
+ selector = " for #{selector}" if selector.present?
228
+
229
+ message = "No database connection defined#{selector}."
223
230
 
224
- raise ConnectionNotEstablished, message
231
+ raise ConnectionNotDefined.new(message, connection_name: connection_name, shard: shard, role: role)
225
232
  end
226
233
 
227
234
  pool
@@ -236,8 +243,8 @@ module ActiveRecord
236
243
  end
237
244
 
238
245
  # Get the existing pool manager or initialize and assign a new one.
239
- def set_pool_manager(connection_name)
240
- 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
241
248
  end
242
249
 
243
250
  def pool_managers
@@ -272,9 +279,9 @@ module ActiveRecord
272
279
 
273
280
  def determine_owner_name(owner_name, config)
274
281
  if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
275
- StringConnectionName.new(owner_name.to_s)
282
+ ConnectionDescriptor.new(owner_name.to_s)
276
283
  elsif config.is_a?(Symbol)
277
- StringConnectionName.new(config.to_s)
284
+ ConnectionDescriptor.new(config.to_s)
278
285
  else
279
286
  owner_name
280
287
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "monitor"
5
4
 
6
5
  module ActiveRecord
@@ -41,6 +40,14 @@ module ActiveRecord
41
40
  end
42
41
  end
43
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
+
44
51
  # If +element+ is in the queue, remove and return it, or +nil+.
45
52
  def delete(element)
46
53
  synchronize do
@@ -55,6 +62,13 @@ module ActiveRecord
55
62
  end
56
63
  end
57
64
 
65
+ # Number of elements in the queue.
66
+ def size
67
+ synchronize do
68
+ @queue.size
69
+ end
70
+ end
71
+
58
72
  # Remove the head of the queue.
59
73
  #
60
74
  # If +timeout+ is not given, remove and return the head of the
@@ -115,9 +129,7 @@ module ActiveRecord
115
129
  t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
116
130
  elapsed = 0
117
131
  loop do
118
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
119
- @cond.wait(timeout - elapsed)
120
- end
132
+ @cond.wait(timeout - elapsed)
121
133
 
122
134
  return remove if any?
123
135