activerecord 7.1.5.1 → 7.2.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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +645 -2329
  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 +14 -7
  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 +7 -1
  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 +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +59 -292
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +1 -13
  31. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  33. data/lib/active_record/attribute_methods.rb +54 -63
  34. data/lib/active_record/attributes.rb +61 -47
  35. data/lib/active_record/autosave_association.rb +12 -29
  36. data/lib/active_record/base.rb +2 -3
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -65
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +189 -74
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +15 -6
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -44
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +40 -10
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  58. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  60. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -11
  61. data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
  62. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  63. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  64. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  65. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  66. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  67. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  70. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +125 -75
  71. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  72. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  73. data/lib/active_record/connection_adapters.rb +121 -0
  74. data/lib/active_record/connection_handling.rb +56 -41
  75. data/lib/active_record/core.rb +85 -37
  76. data/lib/active_record/counter_cache.rb +18 -9
  77. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  78. data/lib/active_record/database_configurations/database_config.rb +19 -4
  79. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  80. data/lib/active_record/database_configurations/url_config.rb +20 -1
  81. data/lib/active_record/database_configurations.rb +1 -1
  82. data/lib/active_record/delegated_type.rb +24 -0
  83. data/lib/active_record/dynamic_matchers.rb +2 -2
  84. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  85. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  86. data/lib/active_record/encryption/encryptor.rb +18 -3
  87. data/lib/active_record/encryption/key_provider.rb +1 -1
  88. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  89. data/lib/active_record/encryption/message_serializer.rb +4 -0
  90. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  91. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  92. data/lib/active_record/enum.rb +18 -1
  93. data/lib/active_record/errors.rb +46 -20
  94. data/lib/active_record/explain.rb +13 -24
  95. data/lib/active_record/fixtures.rb +37 -31
  96. data/lib/active_record/future_result.rb +8 -4
  97. data/lib/active_record/gem_version.rb +2 -2
  98. data/lib/active_record/inheritance.rb +4 -2
  99. data/lib/active_record/insert_all.rb +18 -15
  100. data/lib/active_record/integration.rb +4 -1
  101. data/lib/active_record/internal_metadata.rb +48 -34
  102. data/lib/active_record/locking/optimistic.rb +7 -6
  103. data/lib/active_record/log_subscriber.rb +0 -21
  104. data/lib/active_record/message_pack.rb +1 -1
  105. data/lib/active_record/migration/command_recorder.rb +2 -3
  106. data/lib/active_record/migration/compatibility.rb +5 -3
  107. data/lib/active_record/migration/default_strategy.rb +4 -5
  108. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  109. data/lib/active_record/migration.rb +85 -76
  110. data/lib/active_record/model_schema.rb +31 -68
  111. data/lib/active_record/nested_attributes.rb +11 -3
  112. data/lib/active_record/normalization.rb +3 -7
  113. data/lib/active_record/persistence.rb +30 -352
  114. data/lib/active_record/query_cache.rb +19 -8
  115. data/lib/active_record/query_logs.rb +15 -0
  116. data/lib/active_record/querying.rb +21 -9
  117. data/lib/active_record/railtie.rb +37 -55
  118. data/lib/active_record/railties/controller_runtime.rb +13 -4
  119. data/lib/active_record/railties/databases.rake +40 -43
  120. data/lib/active_record/reflection.rb +98 -36
  121. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  122. data/lib/active_record/relation/batches.rb +14 -8
  123. data/lib/active_record/relation/calculations.rb +96 -63
  124. data/lib/active_record/relation/delegation.rb +8 -11
  125. data/lib/active_record/relation/finder_methods.rb +16 -2
  126. data/lib/active_record/relation/merger.rb +4 -6
  127. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  128. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  129. data/lib/active_record/relation/predicate_builder.rb +3 -3
  130. data/lib/active_record/relation/query_methods.rb +224 -58
  131. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  132. data/lib/active_record/relation/spawn_methods.rb +2 -18
  133. data/lib/active_record/relation/where_clause.rb +7 -19
  134. data/lib/active_record/relation.rb +496 -72
  135. data/lib/active_record/result.rb +31 -44
  136. data/lib/active_record/runtime_registry.rb +39 -0
  137. data/lib/active_record/sanitization.rb +24 -19
  138. data/lib/active_record/schema.rb +8 -6
  139. data/lib/active_record/schema_dumper.rb +19 -9
  140. data/lib/active_record/schema_migration.rb +30 -14
  141. data/lib/active_record/scoping/named.rb +1 -0
  142. data/lib/active_record/signed_id.rb +20 -1
  143. data/lib/active_record/statement_cache.rb +7 -7
  144. data/lib/active_record/table_metadata.rb +1 -10
  145. data/lib/active_record/tasks/database_tasks.rb +69 -41
  146. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  147. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  148. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  149. data/lib/active_record/test_fixtures.rb +86 -89
  150. data/lib/active_record/testing/query_assertions.rb +121 -0
  151. data/lib/active_record/timestamp.rb +2 -2
  152. data/lib/active_record/token_for.rb +22 -12
  153. data/lib/active_record/touch_later.rb +1 -1
  154. data/lib/active_record/transaction.rb +132 -0
  155. data/lib/active_record/transactions.rb +70 -14
  156. data/lib/active_record/translation.rb +0 -2
  157. data/lib/active_record/type/serialized.rb +1 -3
  158. data/lib/active_record/type_caster/connection.rb +4 -4
  159. data/lib/active_record/validations/associated.rb +9 -3
  160. data/lib/active_record/validations/uniqueness.rb +15 -10
  161. data/lib/active_record/validations.rb +4 -1
  162. data/lib/active_record.rb +148 -39
  163. data/lib/arel/alias_predication.rb +1 -1
  164. data/lib/arel/collectors/bind.rb +2 -0
  165. data/lib/arel/collectors/composite.rb +7 -0
  166. data/lib/arel/collectors/sql_string.rb +1 -1
  167. data/lib/arel/collectors/substitute_binds.rb +1 -1
  168. data/lib/arel/nodes/binary.rb +0 -6
  169. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  170. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  171. data/lib/arel/nodes/node.rb +4 -3
  172. data/lib/arel/nodes/sql_literal.rb +7 -0
  173. data/lib/arel/nodes.rb +2 -2
  174. data/lib/arel/predications.rb +1 -1
  175. data/lib/arel/select_manager.rb +1 -1
  176. data/lib/arel/tree_manager.rb +3 -2
  177. data/lib/arel/update_manager.rb +2 -1
  178. data/lib/arel/visitors/dot.rb +1 -0
  179. data/lib/arel/visitors/mysql.rb +9 -4
  180. data/lib/arel/visitors/postgresql.rb +1 -12
  181. data/lib/arel/visitors/sqlite.rb +25 -0
  182. data/lib/arel/visitors/to_sql.rb +29 -16
  183. data/lib/arel.rb +7 -3
  184. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  185. metadata +16 -10
@@ -18,74 +18,45 @@ module ActiveRecord
18
18
  # Returns the primary key column's value. If the primary key is composite,
19
19
  # returns an array of the primary key column values.
20
20
  def id
21
- return _read_attribute(@primary_key) unless @primary_key.is_a?(Array)
22
-
23
- @primary_key.map { |pk| _read_attribute(pk) }
21
+ _read_attribute(@primary_key)
24
22
  end
25
23
 
26
24
  def primary_key_values_present? # :nodoc:
27
- return id.all? if self.class.composite_primary_key?
28
-
29
25
  !!id
30
26
  end
31
27
 
32
28
  # Sets the primary key column's value. If the primary key is composite,
33
29
  # raises TypeError when the set value not enumerable.
34
30
  def id=(value)
35
- if self.class.composite_primary_key?
36
- raise TypeError, "Expected value matching #{self.class.primary_key.inspect}, got #{value.inspect}." unless value.is_a?(Enumerable)
37
- @primary_key.zip(value) { |attr, value| _write_attribute(attr, value) }
38
- else
39
- _write_attribute(@primary_key, value)
40
- end
31
+ _write_attribute(@primary_key, value)
41
32
  end
42
33
 
43
34
  # Queries the primary key column's value. If the primary key is composite,
44
35
  # all primary key column values must be queryable.
45
36
  def id?
46
- if self.class.composite_primary_key?
47
- @primary_key.all? { |col| _query_attribute(col) }
48
- else
49
- _query_attribute(@primary_key)
50
- end
37
+ _query_attribute(@primary_key)
51
38
  end
52
39
 
53
40
  # Returns the primary key column's value before type cast. If the primary key is composite,
54
41
  # returns an array of primary key column values before type cast.
55
42
  def id_before_type_cast
56
- if self.class.composite_primary_key?
57
- @primary_key.map { |col| attribute_before_type_cast(col) }
58
- else
59
- attribute_before_type_cast(@primary_key)
60
- end
43
+ attribute_before_type_cast(@primary_key)
61
44
  end
62
45
 
63
46
  # Returns the primary key column's previous value. If the primary key is composite,
64
47
  # returns an array of primary key column previous values.
65
48
  def id_was
66
- if self.class.composite_primary_key?
67
- @primary_key.map { |col| attribute_was(col) }
68
- else
69
- attribute_was(@primary_key)
70
- end
49
+ attribute_was(@primary_key)
71
50
  end
72
51
 
73
52
  # Returns the primary key column's value from the database. If the primary key is composite,
74
53
  # returns an array of primary key column values from database.
75
54
  def id_in_database
76
- if self.class.composite_primary_key?
77
- @primary_key.map { |col| attribute_in_database(col) }
78
- else
79
- attribute_in_database(@primary_key)
80
- end
55
+ attribute_in_database(@primary_key)
81
56
  end
82
57
 
83
58
  def id_for_database # :nodoc:
84
- if self.class.composite_primary_key?
85
- @primary_key.map { |col| @attributes[col].value_for_database }
86
- else
87
- @attributes[@primary_key].value_for_database
88
- end
59
+ @attributes[@primary_key].value_for_database
89
60
  end
90
61
 
91
62
  private
@@ -109,20 +80,19 @@ module ActiveRecord
109
80
  # Overwriting will negate any effect of the +primary_key_prefix_type+
110
81
  # setting, though.
111
82
  def primary_key
112
- if PRIMARY_KEY_NOT_SET.equal?(@primary_key)
113
- @primary_key = reset_primary_key
114
- end
83
+ reset_primary_key if PRIMARY_KEY_NOT_SET.equal?(@primary_key)
115
84
  @primary_key
116
85
  end
117
86
 
118
87
  def composite_primary_key? # :nodoc:
119
- primary_key.is_a?(Array)
88
+ reset_primary_key if PRIMARY_KEY_NOT_SET.equal?(@primary_key)
89
+ @composite_primary_key
120
90
  end
121
91
 
122
92
  # Returns a quoted version of the primary key name, used to construct
123
93
  # SQL statements.
124
94
  def quoted_primary_key
125
- @quoted_primary_key ||= connection.quote_column_name(primary_key)
95
+ @quoted_primary_key ||= adapter_class.quote_column_name(primary_key)
126
96
  end
127
97
 
128
98
  def reset_primary_key # :nodoc:
@@ -138,12 +108,10 @@ module ActiveRecord
138
108
  base_name.foreign_key(false)
139
109
  elsif base_name && primary_key_prefix_type == :table_name_with_underscore
140
110
  base_name.foreign_key
111
+ elsif ActiveRecord::Base != self && table_exists?
112
+ schema_cache.primary_keys(table_name)
141
113
  else
142
- if ActiveRecord::Base != self && table_exists?
143
- connection.schema_cache.primary_keys(table_name)
144
- else
145
- "id"
146
- end
114
+ "id"
147
115
  end
148
116
  end
149
117
 
@@ -163,25 +131,25 @@ module ActiveRecord
163
131
  #
164
132
  # Project.primary_key # => "foo_id"
165
133
  def primary_key=(value)
166
- @primary_key = derive_primary_key(value)
134
+ @primary_key = if value.is_a?(Array)
135
+ @composite_primary_key = true
136
+ include CompositePrimaryKey
137
+ @primary_key = value.map { |v| -v.to_s }.freeze
138
+ elsif value
139
+ -value.to_s
140
+ end
167
141
  @quoted_primary_key = nil
168
142
  @attributes_builder = nil
169
143
  end
170
144
 
171
145
  private
172
- def derive_primary_key(value)
173
- return unless value
174
-
175
- return -value.to_s unless value.is_a?(Array)
176
-
177
- value.map { |v| -v.to_s }.freeze
178
- end
179
-
180
146
  def inherited(base)
181
147
  super
182
148
  base.class_eval do
183
149
  @primary_key = PRIMARY_KEY_NOT_SET
150
+ @composite_primary_key = false
184
151
  @quoted_primary_key = nil
152
+ @attributes_builder = nil
185
153
  end
186
154
  end
187
155
  end
@@ -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
@@ -73,14 +73,15 @@ module ActiveRecord
73
73
  end
74
74
 
75
75
  module ClassMethods # :nodoc:
76
- def define_attribute(name, cast_type, **)
77
- if create_time_zone_conversion_attribute?(name, cast_type)
78
- cast_type = TimeZoneConverter.new(cast_type)
76
+ private
77
+ def hook_attribute_type(name, cast_type)
78
+ if create_time_zone_conversion_attribute?(name, cast_type)
79
+ cast_type = TimeZoneConverter.new(cast_type)
80
+ end
81
+
82
+ super
79
83
  end
80
- super
81
- end
82
84
 
83
- private
84
85
  def create_time_zone_conversion_attribute?(name, cast_type)
85
86
  enabled_for_column = time_zone_aware_attributes &&
86
87
  !skip_time_zone_conversion_for_attributes.include?(name.to_sym)
@@ -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,25 +77,6 @@ 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 false 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
82
- end
83
- true
84
- end
85
-
86
80
  def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc:
87
81
  attribute_method_patterns.each do |pattern|
88
82
  alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
@@ -90,64 +84,64 @@ module ActiveRecord
90
84
  attribute_method_patterns_cache.clear
91
85
  end
92
86
 
93
- def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
94
- method_name = pattern.method_name(new_name).to_s
95
- target_name = pattern.method_name(old_name).to_s
87
+ def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
96
88
  old_name = old_name.to_s
97
89
 
98
- method_defined = method_defined?(target_name) || private_method_defined?(target_name)
99
- manually_defined = method_defined &&
100
- !self.instance_method(target_name).owner.is_a?(GeneratedAttributeMethods)
101
- reserved_method_name = ::ActiveRecord::AttributeMethods.dangerous_attribute_methods.include?(target_name)
102
-
103
90
  if !abstract_class? && !has_attribute?(old_name)
104
- # We only need to issue this deprecation warning once, so we issue it when defining the original reader method.
105
- should_warn = target_name == old_name
106
- if should_warn
107
- ActiveRecord.deprecator.warn(
108
- "#{self} model aliases `#{old_name}`, but `#{old_name}` is not an attribute. " \
109
- "Starting in Rails 7.2, alias_attribute with non-attribute targets will raise. " \
110
- "Use `alias_method :#{new_name}, :#{old_name}` or define the method manually."
111
- )
112
- end
113
- super
114
- elsif manually_defined && !reserved_method_name
115
- aliased_method_redefined_as_well = method_defined_within?(method_name, self)
116
- return if aliased_method_redefined_as_well
117
-
118
- ActiveRecord.deprecator.warn(
119
- "#{self} model aliases `#{old_name}` and has a method called `#{target_name}` defined. " \
120
- "Starting in Rails 7.2 `#{method_name}` will not be calling `#{target_name}` anymore. " \
121
- "You may want to additionally define `#{method_name}` to preserve the current behavior."
122
- )
123
- 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."
124
93
  else
125
94
  define_attribute_method_pattern(pattern, old_name, owner: code_generator, as: new_name, override: true)
126
95
  end
127
96
  end
128
97
 
98
+ def attribute_methods_generated? # :nodoc:
99
+ @attribute_methods_generated
100
+ end
101
+
129
102
  # Generates all the attribute related methods for columns in the database
130
103
  # accessors, mutators and query methods.
131
104
  def define_attribute_methods # :nodoc:
132
105
  return false if @attribute_methods_generated
133
106
  # Use a mutex; we don't want two threads simultaneously trying to define
134
107
  # attribute methods.
135
- generated_attribute_methods.synchronize do
108
+ GeneratedAttributeMethods::LOCK.synchronize do
136
109
  return false if @attribute_methods_generated
110
+
137
111
  superclass.define_attribute_methods unless base_class?
138
- 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
+
139
119
  @attribute_methods_generated = true
120
+
121
+ generate_alias_attributes
140
122
  end
141
123
  true
142
124
  end
143
125
 
144
- def attribute_methods_generated? # :nodoc:
145
- @attribute_methods_generated && @alias_attributes_mass_generated
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
137
+ end
138
+
139
+ @alias_attributes_mass_generated = true
146
140
  end
147
141
 
148
142
  def undefine_attribute_methods # :nodoc:
149
- generated_attribute_methods.synchronize do
150
- super if defined?(@attribute_methods_generated) && @attribute_methods_generated
143
+ GeneratedAttributeMethods::LOCK.synchronize do
144
+ super if @attribute_methods_generated
151
145
  @attribute_methods_generated = false
152
146
  @alias_attributes_mass_generated = false
153
147
  end
@@ -298,9 +292,7 @@ module ActiveRecord
298
292
 
299
293
  # If the result is true then check for the select case.
300
294
  # For queries selecting a subset of columns, return false for unselected columns.
301
- # We check defined?(@attributes) not to issue warnings if called on objects that
302
- # have been allocated but not yet initialized.
303
- if defined?(@attributes)
295
+ if @attributes
304
296
  if name = self.class.symbol_column_to_string(name.to_sym)
305
297
  return _has_attribute?(name)
306
298
  end
@@ -483,14 +475,14 @@ module ActiveRecord
483
475
  unless self.class.attribute_methods_generated?
484
476
  if self.class.method_defined?(name)
485
477
  # The method is explicitly defined in the model, but calls a generated
486
- # method with super. So we must resume the call chain at the right setp.
478
+ # method with super. So we must resume the call chain at the right step.
487
479
  last_method = method(name)
488
480
  last_method = last_method.super_method while last_method.super_method
489
481
  self.class.define_attribute_methods
490
482
  if last_method.super_method
491
483
  return last_method.super_method.call(...)
492
484
  end
493
- elsif self.class.define_attribute_methods | self.class.generate_alias_attributes
485
+ elsif self.class.define_attribute_methods
494
486
  # Some attribute methods weren't generated yet, we retry the call
495
487
  return public_send(name, ...)
496
488
  end
@@ -500,8 +492,7 @@ module ActiveRecord
500
492
  end
501
493
 
502
494
  def attribute_method?(attr_name)
503
- # We check defined? because Syck calls respond_to? before actually calling initialize.
504
- defined?(@attributes) && @attributes.key?(attr_name)
495
+ @attributes&.key?(attr_name)
505
496
  end
506
497
 
507
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,12 @@ 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
- # This is the low level API which sits beneath +attribute+. It only
236
- # 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
215
+ # This API only accepts type objects, and will do its work immediately instead of
216
+ # waiting for the schema to load. While this method
239
217
  # is provided so it can be used by plugin authors, application code
240
218
  # should probably use ClassMethods#attribute.
241
219
  #
@@ -260,14 +238,38 @@ module ActiveRecord
260
238
  define_default_attribute(name, default, cast_type, from_user: user_provided_default)
261
239
  end
262
240
 
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)
241
+ def _default_attributes # :nodoc:
242
+ @default_attributes ||= begin
243
+ attributes_hash = with_connection do |connection|
244
+ columns_hash.transform_values do |column|
245
+ ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
246
+ end
247
+ end
248
+
249
+ attribute_set = ActiveModel::AttributeSet.new(attributes_hash)
250
+ apply_pending_attribute_modifications(attribute_set)
251
+ attribute_set
268
252
  end
269
253
  end
270
254
 
255
+ ##
256
+ # :method: type_for_attribute
257
+ # :call-seq: type_for_attribute(attribute_name, &block)
258
+ #
259
+ # See ActiveModel::Attributes::ClassMethods#type_for_attribute.
260
+ #
261
+ # This method will access the database and load the model's schema if
262
+ # necessary.
263
+ #--
264
+ # Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute.
265
+
266
+ ##
267
+ protected
268
+ def reload_schema_from_cache(*)
269
+ reset_default_attributes!
270
+ super
271
+ end
272
+
271
273
  private
272
274
  NO_DEFAULT_PROVIDED = Object.new # :nodoc:
273
275
  private_constant :NO_DEFAULT_PROVIDED
@@ -287,6 +289,18 @@ module ActiveRecord
287
289
  end
288
290
  _default_attributes[name] = default_attribute
289
291
  end
292
+
293
+ def reset_default_attributes
294
+ reload_schema_from_cache
295
+ end
296
+
297
+ def resolve_type_name(name, **options)
298
+ Type.lookup(name, **options, adapter: Type.adapter_name_from(self))
299
+ end
300
+
301
+ def type_for_column(connection, column)
302
+ hook_attribute_type(column.name, super)
303
+ end
290
304
  end
291
305
  end
292
306
  end