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
@@ -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.
@@ -130,7 +140,7 @@ module ActiveRecord
130
140
  # silently cast unsupported types to +String+:
131
141
  #
132
142
  # >> JSON.parse(JSON.dump(Struct.new(:foo)))
133
- # => "#<Class:0x000000013090b4c0>"
143
+ # # => "#<Class:0x000000013090b4c0>"
134
144
  #
135
145
  # ==== Examples
136
146
  #
@@ -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
@@ -200,7 +210,7 @@ module ActiveRecord
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 || {}))
@@ -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
@@ -84,7 +84,7 @@ module ActiveRecord
84
84
  attribute_method_patterns_cache.clear
85
85
  end
86
86
 
87
- def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
87
+ def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
88
88
  old_name = old_name.to_s
89
89
 
90
90
  if !abstract_class? && !has_attribute?(old_name)
@@ -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,27 @@ 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, ...)
488
- end
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 = begin
484
+ self.class.public_instance_method(name)
485
+ rescue NameError
486
+ nil
489
487
  end
490
488
 
491
- super
489
+ # The method might be explicitly defined in the model, but call a generated
490
+ # method with super. So we must resume the call chain at the right step.
491
+ method = method.super_method while method && !method.owner.is_a?(GeneratedAttributeMethods)
492
+ if method
493
+ method.bind_call(self, ...)
494
+ else
495
+ super
496
+ end
492
497
  end
493
498
 
494
499
  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
@@ -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
@@ -338,19 +372,29 @@ module ActiveRecord
338
372
  return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
339
373
 
340
374
  context = validation_context if custom_validation_context?
375
+ return true if record.valid?(context)
341
376
 
342
- unless valid = record.valid?(context)
343
- if association.options[:autosave]
344
- record.errors.each { |error|
345
- self.errors.objects.append(
346
- Associations::NestedError.new(association, error)
347
- )
348
- }
349
- else
350
- errors.add(association.reflection.name)
351
- 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) }
352
383
  end
353
- 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?
354
398
  end
355
399
 
356
400
  # Is used as an around_save callback to check while saving a collection
@@ -431,33 +475,34 @@ module ActiveRecord
431
475
  return unless association && association.loaded?
432
476
 
433
477
  record = association.load_target
478
+ return unless record && !record.destroyed?
434
479
 
435
- if record && !record.destroyed?
436
- autosave = reflection.options[:autosave]
437
-
438
- if autosave && record.marked_for_destruction?
439
- record.destroy
440
- elsif autosave != false
441
- primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
442
- primary_key_value = primary_key.map { |key| _read_attribute(key) }
480
+ autosave = reflection.options[:autosave]
443
481
 
444
- if (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
445
- unless reflection.through_reflection
446
- foreign_key = Array(reflection.foreign_key)
447
- 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)
448
488
 
449
- primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
450
- association_id = _read_attribute(primary_key)
451
- record[foreign_key] = association_id unless record[foreign_key] == association_id
452
- end
453
- association.set_inverse_instance(record)
454
- end
489
+ unless reflection.through_reflection
490
+ foreign_key = Array(reflection.foreign_key)
491
+ primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
455
492
 
456
- saved = record.save(validate: !autosave)
457
- raise ActiveRecord::Rollback if !saved && autosave
458
- 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
459
496
  end
497
+ association.set_inverse_instance(record)
460
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
461
506
  end
462
507
  end
463
508
 
@@ -482,8 +527,7 @@ module ActiveRecord
482
527
  return false unless reflection.inverse_of&.polymorphic?
483
528
 
484
529
  class_name = record._read_attribute(reflection.inverse_of.foreign_type)
485
-
486
- reflection.active_record != record.class.polymorphic_class_for(class_name)
530
+ reflection.active_record.polymorphic_name != class_name
487
531
  end
488
532
 
489
533
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
@@ -502,7 +546,15 @@ module ActiveRecord
502
546
  foreign_key.each { |key| self[key] = nil }
503
547
  record.destroy
504
548
  elsif autosave != false
505
- 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
506
558
 
507
559
  if association.updated?
508
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"
@@ -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 = "_"
@@ -1,14 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/json"
4
+
3
5
  module ActiveRecord
4
6
  module Coders # :nodoc:
5
- module JSON # :nodoc:
6
- def self.dump(obj)
7
- ActiveSupport::JSON.encode(obj)
7
+ class JSON # :nodoc:
8
+ DEFAULT_OPTIONS = { escape: false }.freeze
9
+
10
+ def initialize(options = nil)
11
+ @options = options ? DEFAULT_OPTIONS.merge(options) : DEFAULT_OPTIONS
12
+ @encoder = ActiveSupport::JSON::Encoding.json_encoder.new(options)
13
+ end
14
+
15
+ def dump(obj)
16
+ @encoder.encode(obj)
8
17
  end
9
18
 
10
- def self.load(json)
11
- ActiveSupport::JSON.decode(json) unless json.blank?
19
+ def load(json)
20
+ ActiveSupport::JSON.decode(json, @options) unless json.blank?
12
21
  end
13
22
  end
14
23
  end