activerecord 7.1.3.2 → 7.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +570 -2094
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +18 -11
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +11 -5
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +3 -3
  18. data/lib/active_record/associations/has_one_association.rb +2 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  20. data/lib/active_record/associations/join_dependency.rb +6 -8
  21. data/lib/active_record/associations/nested_error.rb +47 -0
  22. data/lib/active_record/associations/preloader/association.rb +2 -1
  23. data/lib/active_record/associations/preloader/branch.rb +7 -1
  24. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  25. data/lib/active_record/associations/singular_association.rb +6 -0
  26. data/lib/active_record/associations/through_association.rb +1 -1
  27. data/lib/active_record/associations.rb +34 -273
  28. data/lib/active_record/attribute_assignment.rb +1 -11
  29. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  30. data/lib/active_record/attribute_methods/dirty.rb +2 -2
  31. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  32. data/lib/active_record/attribute_methods/read.rb +4 -16
  33. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  35. data/lib/active_record/attribute_methods/write.rb +3 -3
  36. data/lib/active_record/attribute_methods.rb +89 -58
  37. data/lib/active_record/attributes.rb +60 -45
  38. data/lib/active_record/autosave_association.rb +17 -31
  39. data/lib/active_record/base.rb +2 -3
  40. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  41. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +244 -58
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +188 -75
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  46. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  47. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +22 -9
  48. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  49. data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
  50. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  53. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -1
  54. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  55. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  56. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  57. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  58. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  59. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  60. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  61. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  62. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  63. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  64. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  65. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  66. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  67. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  68. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  69. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  71. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  72. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  73. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -77
  74. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  75. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  76. data/lib/active_record/connection_adapters.rb +121 -0
  77. data/lib/active_record/connection_handling.rb +56 -41
  78. data/lib/active_record/core.rb +60 -39
  79. data/lib/active_record/counter_cache.rb +23 -10
  80. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  81. data/lib/active_record/database_configurations/database_config.rb +19 -4
  82. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  83. data/lib/active_record/database_configurations/url_config.rb +20 -1
  84. data/lib/active_record/database_configurations.rb +1 -1
  85. data/lib/active_record/delegated_type.rb +30 -6
  86. data/lib/active_record/destroy_association_async_job.rb +1 -1
  87. data/lib/active_record/dynamic_matchers.rb +2 -2
  88. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  89. data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
  90. data/lib/active_record/encryption/encryptor.rb +18 -3
  91. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  92. data/lib/active_record/encryption/message_serializer.rb +4 -0
  93. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  94. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  95. data/lib/active_record/encryption/scheme.rb +8 -4
  96. data/lib/active_record/enum.rb +26 -6
  97. data/lib/active_record/errors.rb +46 -20
  98. data/lib/active_record/explain.rb +13 -24
  99. data/lib/active_record/fixtures.rb +37 -31
  100. data/lib/active_record/future_result.rb +17 -4
  101. data/lib/active_record/gem_version.rb +3 -3
  102. data/lib/active_record/inheritance.rb +4 -2
  103. data/lib/active_record/insert_all.rb +18 -15
  104. data/lib/active_record/integration.rb +4 -1
  105. data/lib/active_record/internal_metadata.rb +48 -34
  106. data/lib/active_record/locking/optimistic.rb +8 -7
  107. data/lib/active_record/log_subscriber.rb +0 -21
  108. data/lib/active_record/marshalling.rb +1 -1
  109. data/lib/active_record/message_pack.rb +2 -2
  110. data/lib/active_record/migration/command_recorder.rb +2 -3
  111. data/lib/active_record/migration/compatibility.rb +11 -3
  112. data/lib/active_record/migration/default_strategy.rb +4 -5
  113. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  114. data/lib/active_record/migration.rb +85 -76
  115. data/lib/active_record/model_schema.rb +39 -70
  116. data/lib/active_record/nested_attributes.rb +11 -3
  117. data/lib/active_record/normalization.rb +3 -7
  118. data/lib/active_record/persistence.rb +32 -354
  119. data/lib/active_record/query_cache.rb +18 -6
  120. data/lib/active_record/query_logs.rb +15 -0
  121. data/lib/active_record/query_logs_formatter.rb +1 -1
  122. data/lib/active_record/querying.rb +21 -9
  123. data/lib/active_record/railtie.rb +54 -67
  124. data/lib/active_record/railties/controller_runtime.rb +13 -4
  125. data/lib/active_record/railties/databases.rake +42 -45
  126. data/lib/active_record/reflection.rb +102 -37
  127. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  128. data/lib/active_record/relation/batches.rb +14 -8
  129. data/lib/active_record/relation/calculations.rb +95 -62
  130. data/lib/active_record/relation/delegation.rb +8 -11
  131. data/lib/active_record/relation/finder_methods.rb +16 -2
  132. data/lib/active_record/relation/merger.rb +4 -6
  133. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  134. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  135. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  136. data/lib/active_record/relation/predicate_builder.rb +3 -3
  137. data/lib/active_record/relation/query_methods.rb +212 -47
  138. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  139. data/lib/active_record/relation/spawn_methods.rb +2 -18
  140. data/lib/active_record/relation/where_clause.rb +7 -19
  141. data/lib/active_record/relation.rb +500 -66
  142. data/lib/active_record/result.rb +32 -45
  143. data/lib/active_record/runtime_registry.rb +39 -0
  144. data/lib/active_record/sanitization.rb +24 -19
  145. data/lib/active_record/schema.rb +8 -6
  146. data/lib/active_record/schema_dumper.rb +19 -9
  147. data/lib/active_record/schema_migration.rb +30 -14
  148. data/lib/active_record/scoping/named.rb +1 -0
  149. data/lib/active_record/signed_id.rb +20 -1
  150. data/lib/active_record/statement_cache.rb +7 -7
  151. data/lib/active_record/table_metadata.rb +1 -10
  152. data/lib/active_record/tasks/database_tasks.rb +87 -48
  153. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  154. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  155. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  156. data/lib/active_record/test_fixtures.rb +87 -89
  157. data/lib/active_record/testing/query_assertions.rb +121 -0
  158. data/lib/active_record/timestamp.rb +5 -3
  159. data/lib/active_record/token_for.rb +22 -12
  160. data/lib/active_record/touch_later.rb +1 -1
  161. data/lib/active_record/transaction.rb +132 -0
  162. data/lib/active_record/transactions.rb +70 -14
  163. data/lib/active_record/translation.rb +0 -2
  164. data/lib/active_record/type/serialized.rb +1 -3
  165. data/lib/active_record/type_caster/connection.rb +4 -4
  166. data/lib/active_record/validations/associated.rb +9 -3
  167. data/lib/active_record/validations/uniqueness.rb +14 -10
  168. data/lib/active_record/validations.rb +4 -1
  169. data/lib/active_record.rb +150 -41
  170. data/lib/arel/alias_predication.rb +1 -1
  171. data/lib/arel/collectors/bind.rb +2 -0
  172. data/lib/arel/collectors/composite.rb +7 -0
  173. data/lib/arel/collectors/sql_string.rb +1 -1
  174. data/lib/arel/collectors/substitute_binds.rb +1 -1
  175. data/lib/arel/nodes/binary.rb +0 -6
  176. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  177. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  178. data/lib/arel/nodes/node.rb +4 -3
  179. data/lib/arel/nodes/sql_literal.rb +7 -0
  180. data/lib/arel/nodes.rb +2 -2
  181. data/lib/arel/predications.rb +1 -1
  182. data/lib/arel/select_manager.rb +1 -1
  183. data/lib/arel/tree_manager.rb +8 -3
  184. data/lib/arel/update_manager.rb +2 -1
  185. data/lib/arel/visitors/dot.rb +1 -0
  186. data/lib/arel/visitors/mysql.rb +9 -4
  187. data/lib/arel/visitors/postgresql.rb +1 -12
  188. data/lib/arel/visitors/to_sql.rb +31 -17
  189. data/lib/arel.rb +7 -3
  190. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  191. metadata +18 -12
@@ -8,11 +8,11 @@ module ActiveRecord
8
8
 
9
9
  module ClassMethods # :nodoc:
10
10
  private
11
- def define_method_attribute(name, owner:)
11
+ def define_method_attribute(canonical_name, owner:, as: canonical_name)
12
12
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
13
- owner, name
13
+ owner, canonical_name
14
14
  ) do |temp_method_name, attr_name_expr|
15
- owner.define_cached_method(name, as: temp_method_name, namespace: :active_record) do |batch|
15
+ owner.define_cached_method(temp_method_name, as: as, namespace: :active_record) do |batch|
16
16
  batch <<
17
17
  "def #{temp_method_name}" <<
18
18
  " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
@@ -30,19 +30,7 @@ module ActiveRecord
30
30
  name = attr_name.to_s
31
31
  name = self.class.attribute_aliases[name] || name
32
32
 
33
- return @attributes.fetch_value(name, &block) unless name == "id" && @primary_key
34
-
35
- if self.class.composite_primary_key?
36
- @attributes.fetch_value("id", &block)
37
- else
38
- if @primary_key != "id"
39
- ActiveRecord.deprecator.warn(<<-MSG.squish)
40
- Using read_attribute(:id) to read the primary key value is deprecated.
41
- Use #id instead.
42
- MSG
43
- end
44
- @attributes.fetch_value(@primary_key, &block)
45
- end
33
+ @attributes.fetch_value(name, &block)
46
34
  end
47
35
 
48
36
  # This method exists to avoid the expensive primary_key check internally, without
@@ -180,29 +180,7 @@ module ActiveRecord
180
180
  # serialize :preferences, coder: Rot13JSON
181
181
  # end
182
182
  #
183
- def serialize(attr_name, class_name_or_coder = nil, coder: nil, type: Object, yaml: {}, **options)
184
- unless class_name_or_coder.nil?
185
- if class_name_or_coder == ::JSON || [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
186
- ActiveRecord.deprecator.warn(<<~MSG)
187
- Passing the coder as positional argument is deprecated and will be removed in Rails 7.2.
188
-
189
- Please pass the coder as a keyword argument:
190
-
191
- serialize #{attr_name.inspect}, coder: #{class_name_or_coder}
192
- MSG
193
- coder = class_name_or_coder
194
- else
195
- ActiveRecord.deprecator.warn(<<~MSG)
196
- Passing the class as positional argument is deprecated and will be removed in Rails 7.2.
197
-
198
- Please pass the class as a keyword argument:
199
-
200
- serialize #{attr_name.inspect}, type: #{class_name_or_coder.name}
201
- MSG
202
- type = class_name_or_coder
203
- end
204
- end
205
-
183
+ def serialize(attr_name, coder: nil, type: Object, yaml: {}, **options)
206
184
  coder ||= default_column_serializer
207
185
  unless coder
208
186
  raise ArgumentError, <<~MSG.squish
@@ -214,7 +192,9 @@ module ActiveRecord
214
192
 
215
193
  column_serializer = build_column_serializer(attr_name, coder, type, yaml)
216
194
 
217
- attribute(attr_name, **options) do |cast_type|
195
+ attribute(attr_name, **options)
196
+
197
+ decorate_attributes([attr_name]) do |attr_name, cast_type|
218
198
  if type_incompatible_with_serialize?(cast_type, coder, type)
219
199
  raise ColumnNotSerializableError.new(attr_name, cast_type)
220
200
  end
@@ -69,14 +69,15 @@ module ActiveRecord
69
69
  end
70
70
 
71
71
  module ClassMethods # :nodoc:
72
- def define_attribute(name, cast_type, **)
73
- if create_time_zone_conversion_attribute?(name, cast_type)
74
- cast_type = TimeZoneConverter.new(cast_type)
72
+ private
73
+ def hook_attribute_type(name, cast_type)
74
+ if create_time_zone_conversion_attribute?(name, cast_type)
75
+ cast_type = TimeZoneConverter.new(cast_type)
76
+ end
77
+
78
+ super
75
79
  end
76
- super
77
- end
78
80
 
79
- private
80
81
  def create_time_zone_conversion_attribute?(name, cast_type)
81
82
  enabled_for_column = time_zone_aware_attributes &&
82
83
  !skip_time_zone_conversion_for_attributes.include?(name.to_sym)
@@ -12,11 +12,11 @@ module ActiveRecord
12
12
 
13
13
  module ClassMethods # :nodoc:
14
14
  private
15
- def define_method_attribute=(name, owner:)
15
+ def define_method_attribute=(canonical_name, owner:, as: canonical_name)
16
16
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
17
- owner, name, writer: true,
17
+ owner, canonical_name, writer: true,
18
18
  ) do |temp_method_name, attr_name_expr|
19
- owner.define_cached_method("#{name}=", as: temp_method_name, namespace: :active_record) do |batch|
19
+ owner.define_cached_method(temp_method_name, as: "#{as}=", namespace: :active_record) do |batch|
20
20
  batch <<
21
21
  "def #{temp_method_name}(value)" <<
22
22
  " _write_attribute(#{attr_name_expr}, value)" <<
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mutex_m"
4
3
  require "active_support/core_ext/enumerable"
5
4
 
6
5
  module ActiveRecord
@@ -21,10 +20,10 @@ module ActiveRecord
21
20
  include Serialization
22
21
  end
23
22
 
24
- RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
23
+ RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name superclass)
25
24
 
26
25
  class GeneratedAttributeMethods < Module # :nodoc:
27
- include Mutex_m
26
+ LOCK = Monitor.new
28
27
  end
29
28
 
30
29
  class << self
@@ -50,6 +49,20 @@ module ActiveRecord
50
49
  super
51
50
  end
52
51
 
52
+ # Allows you to make aliases for attributes.
53
+ #
54
+ # class Person < ActiveRecord::Base
55
+ # alias_attribute :nickname, :name
56
+ # end
57
+ #
58
+ # person = Person.create(name: 'Bob')
59
+ # person.name # => "Bob"
60
+ # person.nickname # => "Bob"
61
+ #
62
+ # The alias can also be used for querying:
63
+ #
64
+ # Person.where(nickname: "Bob")
65
+ # # SELECT "people".* FROM "people" WHERE "people"."name" = "Bob"
53
66
  def alias_attribute(new_name, old_name)
54
67
  super
55
68
 
@@ -64,80 +77,71 @@ module ActiveRecord
64
77
  # alias attributes in Active Record are lazily generated
65
78
  end
66
79
 
67
- def generate_alias_attributes # :nodoc:
68
- superclass.generate_alias_attributes unless superclass == Base
69
- return if @alias_attributes_mass_generated
70
-
71
- generated_attribute_methods.synchronize do
72
- return if @alias_attributes_mass_generated
73
- ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
74
- aliases_by_attribute_name.each do |old_name, new_names|
75
- new_names.each do |new_name|
76
- generate_alias_attribute_methods(code_generator, new_name, old_name)
77
- end
78
- end
79
- end
80
-
81
- @alias_attributes_mass_generated = true
80
+ def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc:
81
+ attribute_method_patterns.each do |pattern|
82
+ alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
82
83
  end
84
+ attribute_method_patterns_cache.clear
83
85
  end
84
86
 
85
87
  def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
86
- method_name = pattern.method_name(new_name).to_s
87
- target_name = pattern.method_name(old_name).to_s
88
- parameters = pattern.parameters
89
88
  old_name = old_name.to_s
90
89
 
91
- method_defined = method_defined?(target_name) || private_method_defined?(target_name)
92
- manually_defined = method_defined &&
93
- !self.instance_method(target_name).owner.is_a?(GeneratedAttributeMethods)
94
- reserved_method_name = ::ActiveRecord::AttributeMethods.dangerous_attribute_methods.include?(target_name)
95
-
96
90
  if !abstract_class? && !has_attribute?(old_name)
97
- # We only need to issue this deprecation warning once, so we issue it when defining the original reader method.
98
- should_warn = target_name == old_name
99
- if should_warn
100
- ActiveRecord.deprecator.warn(
101
- "#{self} model aliases `#{old_name}`, but `#{old_name}` is not an attribute. " \
102
- "Starting in Rails 7.2, alias_attribute with non-attribute targets will raise. " \
103
- "Use `alias_method :#{new_name}, :#{old_name}` or define the method manually."
104
- )
105
- end
106
- super
107
- elsif manually_defined && !reserved_method_name
108
- aliased_method_redefined_as_well = method_defined_within?(method_name, self)
109
- return if aliased_method_redefined_as_well
110
-
111
- ActiveRecord.deprecator.warn(
112
- "#{self} model aliases `#{old_name}` and has a method called `#{target_name}` defined. " \
113
- "Starting in Rails 7.2 `#{method_name}` will not be calling `#{target_name}` anymore. " \
114
- "You may want to additionally define `#{method_name}` to preserve the current behavior."
115
- )
116
- super
91
+ raise ArgumentError, "#{self.name} model aliases `#{old_name}`, but `#{old_name}` is not an attribute. " \
92
+ "Use `alias_method :#{new_name}, :#{old_name}` or define the method manually."
117
93
  else
118
- define_proxy_call(code_generator, method_name, pattern.proxy_target, parameters, old_name,
119
- namespace: :proxy_alias_attribute
120
- )
94
+ define_attribute_method_pattern(pattern, old_name, owner: code_generator, as: new_name, override: true)
121
95
  end
122
96
  end
123
97
 
98
+ def attribute_methods_generated? # :nodoc:
99
+ @attribute_methods_generated
100
+ end
101
+
124
102
  # Generates all the attribute related methods for columns in the database
125
103
  # accessors, mutators and query methods.
126
104
  def define_attribute_methods # :nodoc:
127
105
  return false if @attribute_methods_generated
128
106
  # Use a mutex; we don't want two threads simultaneously trying to define
129
107
  # attribute methods.
130
- generated_attribute_methods.synchronize do
108
+ GeneratedAttributeMethods::LOCK.synchronize do
131
109
  return false if @attribute_methods_generated
110
+
132
111
  superclass.define_attribute_methods unless base_class?
133
- super(attribute_names)
112
+
113
+ unless abstract_class?
114
+ load_schema
115
+ super(attribute_names)
116
+ alias_attribute :id_value, :id if _has_attribute?("id")
117
+ end
118
+
134
119
  @attribute_methods_generated = true
120
+
121
+ generate_alias_attributes
122
+ end
123
+ true
124
+ end
125
+
126
+ def generate_alias_attributes # :nodoc:
127
+ superclass.generate_alias_attributes unless superclass == Base
128
+
129
+ return if @alias_attributes_mass_generated
130
+
131
+ ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
132
+ aliases_by_attribute_name.each do |old_name, new_names|
133
+ new_names.each do |new_name|
134
+ generate_alias_attribute_methods(code_generator, new_name, old_name)
135
+ end
136
+ end
135
137
  end
138
+
139
+ @alias_attributes_mass_generated = true
136
140
  end
137
141
 
138
142
  def undefine_attribute_methods # :nodoc:
139
- generated_attribute_methods.synchronize do
140
- super if defined?(@attribute_methods_generated) && @attribute_methods_generated
143
+ GeneratedAttributeMethods::LOCK.synchronize do
144
+ super if @attribute_methods_generated
141
145
  @attribute_methods_generated = false
142
146
  @alias_attributes_mass_generated = false
143
147
  end
@@ -288,9 +292,7 @@ module ActiveRecord
288
292
 
289
293
  # If the result is true then check for the select case.
290
294
  # For queries selecting a subset of columns, return false for unselected columns.
291
- # We check defined?(@attributes) not to issue warnings if called on objects that
292
- # have been allocated but not yet initialized.
293
- if defined?(@attributes)
295
+ if @attributes
294
296
  if name = self.class.symbol_column_to_string(name.to_sym)
295
297
  return _has_attribute?(name)
296
298
  end
@@ -459,9 +461,38 @@ module ActiveRecord
459
461
  end
460
462
 
461
463
  private
464
+ def respond_to_missing?(name, include_private = false)
465
+ if self.class.define_attribute_methods
466
+ # Some methods weren't defined yet.
467
+ return true if self.class.method_defined?(name)
468
+ return true if include_private && self.class.private_method_defined?(name)
469
+ end
470
+
471
+ super
472
+ end
473
+
474
+ 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
489
+ end
490
+
491
+ super
492
+ end
493
+
462
494
  def attribute_method?(attr_name)
463
- # We check defined? because Syck calls respond_to? before actually calling initialize.
464
- defined?(@attributes) && @attributes.key?(attr_name)
495
+ @attributes&.key?(attr_name)
465
496
  end
466
497
 
467
498
  def attributes_with_values(attribute_names)
@@ -6,12 +6,13 @@ module ActiveRecord
6
6
  # See ActiveRecord::Attributes::ClassMethods for documentation
7
7
  module Attributes
8
8
  extend ActiveSupport::Concern
9
+ include ActiveModel::AttributeRegistration
9
10
 
10
- included do
11
- class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
12
- end
13
11
  # = Active Record \Attributes
14
12
  module ClassMethods
13
+ # :method: attribute
14
+ # :call-seq: attribute(name, cast_type = nil, **options)
15
+ #
15
16
  # Defines an attribute with a type on this model. It will override the
16
17
  # type of existing attributes if needed. This allows control over how
17
18
  # values are converted to and from SQL when assigned to a model. It also
@@ -24,15 +25,17 @@ module ActiveRecord
24
25
  # column which this will persist to.
25
26
  #
26
27
  # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
27
- # to be used for this attribute. See the examples below for more
28
- # information about providing custom type objects.
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.
29
32
  #
30
33
  # ==== Options
31
34
  #
32
35
  # The following options are accepted:
33
36
  #
34
37
  # +default+ The default value to use when no value is provided. If this option
35
- # is not passed, the previous default value (if any) will be used.
38
+ # is not passed, the previously defined default value (if any) on the superclass or in the schema will be used.
36
39
  # Otherwise, the default will be +nil+.
37
40
  #
38
41
  # +array+ (PostgreSQL only) specifies that the type should be an array (see the
@@ -134,7 +137,7 @@ module ActiveRecord
134
137
  # expected API. It is recommended that your type objects inherit from an
135
138
  # existing type, or from ActiveRecord::Type::Value
136
139
  #
137
- # class MoneyType < ActiveRecord::Type::Integer
140
+ # class PriceType < ActiveRecord::Type::Integer
138
141
  # def cast(value)
139
142
  # if !value.kind_of?(Numeric) && value.include?('$')
140
143
  # price_in_dollars = value.gsub(/\$/, '').to_f
@@ -146,11 +149,11 @@ module ActiveRecord
146
149
  # end
147
150
  #
148
151
  # # config/initializers/types.rb
149
- # ActiveRecord::Type.register(:money, MoneyType)
152
+ # ActiveRecord::Type.register(:price, PriceType)
150
153
  #
151
154
  # # app/models/store_listing.rb
152
155
  # class StoreListing < ActiveRecord::Base
153
- # attribute :price_in_cents, :money
156
+ # attribute :price_in_cents, :price
154
157
  # end
155
158
  #
156
159
  # store_listing = StoreListing.new(price_in_cents: '$10.00')
@@ -170,7 +173,7 @@ module ActiveRecord
170
173
  # class Money < Struct.new(:amount, :currency)
171
174
  # end
172
175
  #
173
- # class MoneyType < ActiveRecord::Type::Value
176
+ # class PriceType < ActiveRecord::Type::Value
174
177
  # def initialize(currency_converter:)
175
178
  # @currency_converter = currency_converter
176
179
  # end
@@ -185,12 +188,12 @@ module ActiveRecord
185
188
  # end
186
189
  #
187
190
  # # config/initializers/types.rb
188
- # ActiveRecord::Type.register(:money, MoneyType)
191
+ # ActiveRecord::Type.register(:price, PriceType)
189
192
  #
190
193
  # # app/models/product.rb
191
194
  # class Product < ActiveRecord::Base
192
195
  # currency_converter = ConversionRatesFromTheInternet.new
193
- # attribute :price_in_bitcoins, :money, currency_converter: currency_converter
196
+ # attribute :price_in_bitcoins, :price, currency_converter: currency_converter
194
197
  # end
195
198
  #
196
199
  # Product.where(price_in_bitcoins: Money.new(5, "USD"))
@@ -205,37 +208,13 @@ module ActiveRecord
205
208
  # tracking is performed. The methods +changed?+ and +changed_in_place?+
206
209
  # will be called from ActiveModel::Dirty. See the documentation for those
207
210
  # methods in ActiveModel::Type::Value for more details.
208
- def attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options)
209
- name = name.to_s
210
- name = attribute_aliases[name] || name
211
-
212
- reload_schema_from_cache
213
-
214
- case cast_type
215
- when Symbol
216
- cast_type = Type.lookup(cast_type, **options, adapter: Type.adapter_name_from(self))
217
- when nil
218
- if (prev_cast_type, prev_default = attributes_to_define_after_schema_loads[name])
219
- default = prev_default if default == NO_DEFAULT_PROVIDED
220
- else
221
- prev_cast_type = -> subtype { subtype }
222
- end
223
-
224
- cast_type = if block_given?
225
- -> subtype { yield Proc === prev_cast_type ? prev_cast_type[subtype] : prev_cast_type }
226
- else
227
- prev_cast_type
228
- end
229
- end
230
-
231
- self.attributes_to_define_after_schema_loads =
232
- attributes_to_define_after_schema_loads.merge(name => [cast_type, default])
233
- end
211
+ #
212
+ #--
213
+ # Implemented by ActiveModel::AttributeRegistration#attribute.
234
214
 
235
215
  # This is the low level API which sits beneath +attribute+. It only
236
216
  # accepts type objects, and will do its work immediately instead of
237
- # waiting for the schema to load. Automatic schema detection and
238
- # ClassMethods#attribute both call this under the hood. While this method
217
+ # waiting for the schema to load. While this method
239
218
  # is provided so it can be used by plugin authors, application code
240
219
  # should probably use ClassMethods#attribute.
241
220
  #
@@ -260,14 +239,38 @@ module ActiveRecord
260
239
  define_default_attribute(name, default, cast_type, from_user: user_provided_default)
261
240
  end
262
241
 
263
- def load_schema! # :nodoc:
264
- super
265
- attributes_to_define_after_schema_loads.each do |name, (cast_type, default)|
266
- cast_type = cast_type[type_for_attribute(name)] if Proc === cast_type
267
- define_attribute(name, cast_type, default: default)
242
+ def _default_attributes # :nodoc:
243
+ @default_attributes ||= begin
244
+ attributes_hash = with_connection do |connection|
245
+ columns_hash.transform_values do |column|
246
+ ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
247
+ end
248
+ end
249
+
250
+ attribute_set = ActiveModel::AttributeSet.new(attributes_hash)
251
+ apply_pending_attribute_modifications(attribute_set)
252
+ attribute_set
268
253
  end
269
254
  end
270
255
 
256
+ ##
257
+ # :method: type_for_attribute
258
+ # :call-seq: type_for_attribute(attribute_name, &block)
259
+ #
260
+ # See ActiveModel::Attributes::ClassMethods#type_for_attribute.
261
+ #
262
+ # This method will access the database and load the model's schema if
263
+ # necessary.
264
+ #--
265
+ # Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute.
266
+
267
+ ##
268
+ protected
269
+ def reload_schema_from_cache(*)
270
+ reset_default_attributes!
271
+ super
272
+ end
273
+
271
274
  private
272
275
  NO_DEFAULT_PROVIDED = Object.new # :nodoc:
273
276
  private_constant :NO_DEFAULT_PROVIDED
@@ -287,6 +290,18 @@ module ActiveRecord
287
290
  end
288
291
  _default_attributes[name] = default_attribute
289
292
  end
293
+
294
+ def reset_default_attributes
295
+ reload_schema_from_cache
296
+ end
297
+
298
+ def resolve_type_name(name, **options)
299
+ Type.lookup(name, **options, adapter: Type.adapter_name_from(self))
300
+ end
301
+
302
+ def type_for_column(connection, column)
303
+ hook_attribute_type(column.name, super)
304
+ end
290
305
  end
291
306
  end
292
307
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_record/associations/nested_error"
4
+
3
5
  module ActiveRecord
4
6
  # = Active Record Autosave Association
5
7
  #
@@ -315,7 +317,7 @@ module ActiveRecord
315
317
  def validate_single_association(reflection)
316
318
  association = association_instance_get(reflection.name)
317
319
  record = association && association.reader
318
- association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
320
+ association_valid?(association, record) if record && (record.changed_for_autosave? || custom_validation_context?)
319
321
  end
320
322
 
321
323
  # Validate the associated records if <tt>:validate</tt> or
@@ -324,7 +326,7 @@ module ActiveRecord
324
326
  def validate_collection_association(reflection)
325
327
  if association = association_instance_get(reflection.name)
326
328
  if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
327
- records.each_with_index { |record, index| association_valid?(reflection, record, index) }
329
+ records.each { |record| association_valid?(association, record) }
328
330
  end
329
331
  end
330
332
  end
@@ -332,40 +334,25 @@ module ActiveRecord
332
334
  # Returns whether or not the association is valid and applies any errors to
333
335
  # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
334
336
  # enabled records if they're marked_for_destruction? or destroyed.
335
- def association_valid?(reflection, record, index = nil)
336
- return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
337
+ def association_valid?(association, record)
338
+ return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
337
339
 
338
340
  context = validation_context if custom_validation_context?
339
341
 
340
342
  unless valid = record.valid?(context)
341
- if reflection.options[:autosave]
342
- indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord.index_nested_attribute_errors)
343
-
344
- record.errors.group_by_attribute.each { |attribute, errors|
345
- attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
346
-
347
- errors.each { |error|
348
- self.errors.import(
349
- error,
350
- attribute: attribute
351
- )
352
- }
343
+ if association.options[:autosave]
344
+ record.errors.each { |error|
345
+ self.errors.objects.append(
346
+ Associations::NestedError.new(association, error)
347
+ )
353
348
  }
354
349
  else
355
- errors.add(reflection.name)
350
+ errors.add(association.reflection.name)
356
351
  end
357
352
  end
358
353
  valid
359
354
  end
360
355
 
361
- def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
362
- if indexed_attribute
363
- "#{reflection.name}[#{index}].#{attribute}"
364
- else
365
- "#{reflection.name}.#{attribute}"
366
- end
367
- end
368
-
369
356
  # Is used as an around_save callback to check while saving a collection
370
357
  # association whether or not the parent was a new record before saving.
371
358
  def around_save_collection_association
@@ -441,7 +428,9 @@ module ActiveRecord
441
428
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
442
429
  def save_has_one_association(reflection)
443
430
  association = association_instance_get(reflection.name)
444
- record = association && association.load_target
431
+ return unless association && association.loaded?
432
+
433
+ record = association.load_target
445
434
 
446
435
  if record && !record.destroyed?
447
436
  autosave = reflection.options[:autosave]
@@ -458,7 +447,8 @@ module ActiveRecord
458
447
  primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
459
448
 
460
449
  primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
461
- record[foreign_key] = _read_attribute(primary_key)
450
+ association_id = _read_attribute(primary_key)
451
+ record[foreign_key] = association_id unless record[foreign_key] == association_id
462
452
  end
463
453
  association.set_inverse_instance(record)
464
454
  end
@@ -547,10 +537,6 @@ module ActiveRecord
547
537
  end
548
538
  end
549
539
 
550
- def custom_validation_context?
551
- validation_context && [:create, :update].exclude?(validation_context)
552
- end
553
-
554
540
  def _ensure_no_duplicate_errors
555
541
  errors.uniq!
556
542
  end
@@ -233,7 +233,7 @@ module ActiveRecord # :nodoc:
233
233
  #
234
234
  # Connections are usually created through
235
235
  # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] and retrieved
236
- # by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
236
+ # by ActiveRecord::Base.lease_connection. All classes inheriting from ActiveRecord::Base will use this
237
237
  # connection. But you can also set a class-specific connection. For example, if Course is an
238
238
  # ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
239
239
  # and Course and all of its subclasses will use this connection instead.
@@ -280,7 +280,7 @@ module ActiveRecord # :nodoc:
280
280
  # So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
281
281
  # instances in the current object space.
282
282
  class Base
283
- extend ActiveModel::Naming
283
+ include ActiveModel::API
284
284
 
285
285
  extend ActiveSupport::Benchmarkable
286
286
  extend ActiveSupport::DescendantsTracker
@@ -304,7 +304,6 @@ module ActiveRecord # :nodoc:
304
304
  include Scoping
305
305
  include Sanitization
306
306
  include AttributeAssignment
307
- include ActiveModel::Conversion
308
307
  include Integration
309
308
  include Validations
310
309
  include CounterCache