activerecord 7.1.5.2 → 8.1.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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -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)
@@ -91,63 +85,64 @@ module ActiveRecord
91
85
  end
92
86
 
93
87
  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
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") && !_has_attribute?("id_value")
117
+ end
118
+
119
+ generate_alias_attributes
120
+
139
121
  @attribute_methods_generated = true
140
122
  end
123
+
141
124
  true
142
125
  end
143
126
 
144
- def attribute_methods_generated? # :nodoc:
145
- @attribute_methods_generated && @alias_attributes_mass_generated
127
+ def generate_alias_attributes # :nodoc:
128
+ superclass.generate_alias_attributes unless superclass == Base
129
+
130
+ return if @alias_attributes_mass_generated
131
+
132
+ ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
133
+ aliases_by_attribute_name.each do |old_name, new_names|
134
+ new_names.each do |new_name|
135
+ generate_alias_attribute_methods(code_generator, new_name, old_name)
136
+ end
137
+ end
138
+ end
139
+
140
+ @alias_attributes_mass_generated = true
146
141
  end
147
142
 
148
143
  def undefine_attribute_methods # :nodoc:
149
- generated_attribute_methods.synchronize do
150
- super if defined?(@attribute_methods_generated) && @attribute_methods_generated
144
+ GeneratedAttributeMethods::LOCK.synchronize do
145
+ super if @attribute_methods_generated
151
146
  @attribute_methods_generated = false
152
147
  @alias_attributes_mass_generated = false
153
148
  end
@@ -298,9 +293,7 @@ module ActiveRecord
298
293
 
299
294
  # If the result is true then check for the select case.
300
295
  # 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)
296
+ if @attributes
304
297
  if name = self.class.symbol_column_to_string(name.to_sym)
305
298
  return _has_attribute?(name)
306
299
  end
@@ -480,28 +473,31 @@ module ActiveRecord
480
473
  end
481
474
 
482
475
  def method_missing(name, ...)
483
- unless self.class.attribute_methods_generated?
484
- if self.class.method_defined?(name)
485
- # 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.
487
- last_method = method(name)
488
- last_method = last_method.super_method while last_method.super_method
489
- self.class.define_attribute_methods
490
- if last_method.super_method
491
- return last_method.super_method.call(...)
492
- end
493
- elsif self.class.define_attribute_methods | self.class.generate_alias_attributes
494
- # Some attribute methods weren't generated yet, we retry the call
495
- return public_send(name, ...)
496
- 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
497
487
  end
498
488
 
499
- 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
500
497
  end
501
498
 
502
499
  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)
500
+ @attributes&.key?(attr_name)
505
501
  end
506
502
 
507
503
  def attributes_with_values(attribute_names)
@@ -6,12 +6,14 @@ module ActiveRecord
6
6
  # See ActiveRecord::Attributes::ClassMethods for documentation
7
7
  module Attributes
8
8
  extend ActiveSupport::Concern
9
+ include ActiveModel::AttributeRegistration
10
+ include ActiveModel::Attributes::Normalization
9
11
 
10
- included do
11
- class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
12
- end
13
12
  # = Active Record \Attributes
14
13
  module ClassMethods
14
+ # :method: attribute
15
+ # :call-seq: attribute(name, cast_type = nil, **options)
16
+ #
15
17
  # Defines an attribute with a type on this model. It will override the
16
18
  # type of existing attributes if needed. This allows control over how
17
19
  # values are converted to and from SQL when assigned to a model. It also
@@ -20,26 +22,34 @@ module ActiveRecord
20
22
  # your domain objects across much of Active Record, without having to
21
23
  # rely on implementation details or monkey patching.
22
24
  #
23
- # +name+ The name of the methods to define attribute methods for, and the
24
- # column which this will persist to.
25
+ # ==== Parameters
25
26
  #
26
- # +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.
27
+ # [+name+]
28
+ # The name of the methods to define attribute methods for, and the
29
+ # column which this will persist to.
29
30
  #
30
- # ==== 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.
31
37
  #
32
- # The following options are accepted:
38
+ # ==== Options
33
39
  #
34
- # +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.
36
- # 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+.
37
45
  #
38
- # +array+ (PostgreSQL only) specifies that the type should be an array (see the
39
- # examples below).
46
+ # [+:array+]
47
+ # (PostgreSQL only) Specifies that the type should be an array. See the
48
+ # examples below.
40
49
  #
41
- # +range+ (PostgreSQL only) specifies that the type should be a range (see the
42
- # examples below).
50
+ # [+:range+]
51
+ # (PostgreSQL only) Specifies that the type should be a range. See the
52
+ # examples below.
43
53
  #
44
54
  # When using a symbol for +cast_type+, extra options are forwarded to the
45
55
  # constructor of the type object.
@@ -134,7 +144,7 @@ module ActiveRecord
134
144
  # expected API. It is recommended that your type objects inherit from an
135
145
  # existing type, or from ActiveRecord::Type::Value
136
146
  #
137
- # class MoneyType < ActiveRecord::Type::Integer
147
+ # class PriceType < ActiveRecord::Type::Integer
138
148
  # def cast(value)
139
149
  # if !value.kind_of?(Numeric) && value.include?('$')
140
150
  # price_in_dollars = value.gsub(/\$/, '').to_f
@@ -146,11 +156,11 @@ module ActiveRecord
146
156
  # end
147
157
  #
148
158
  # # config/initializers/types.rb
149
- # ActiveRecord::Type.register(:money, MoneyType)
159
+ # ActiveRecord::Type.register(:price, PriceType)
150
160
  #
151
161
  # # app/models/store_listing.rb
152
162
  # class StoreListing < ActiveRecord::Base
153
- # attribute :price_in_cents, :money
163
+ # attribute :price_in_cents, :price
154
164
  # end
155
165
  #
156
166
  # store_listing = StoreListing.new(price_in_cents: '$10.00')
@@ -170,13 +180,13 @@ module ActiveRecord
170
180
  # class Money < Struct.new(:amount, :currency)
171
181
  # end
172
182
  #
173
- # class MoneyType < ActiveRecord::Type::Value
183
+ # class PriceType < ActiveRecord::Type::Value
174
184
  # def initialize(currency_converter:)
175
185
  # @currency_converter = currency_converter
176
186
  # end
177
187
  #
178
- # # value will be the result of +deserialize+ or
179
- # # +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
180
190
  # # this case.
181
191
  # def serialize(value)
182
192
  # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
@@ -185,12 +195,12 @@ module ActiveRecord
185
195
  # end
186
196
  #
187
197
  # # config/initializers/types.rb
188
- # ActiveRecord::Type.register(:money, MoneyType)
198
+ # ActiveRecord::Type.register(:price, PriceType)
189
199
  #
190
200
  # # app/models/product.rb
191
201
  # class Product < ActiveRecord::Base
192
202
  # currency_converter = ConversionRatesFromTheInternet.new
193
- # attribute :price_in_bitcoins, :money, currency_converter: currency_converter
203
+ # attribute :price_in_bitcoins, :price, currency_converter: currency_converter
194
204
  # end
195
205
  #
196
206
  # Product.where(price_in_bitcoins: Money.new(5, "USD"))
@@ -205,51 +215,31 @@ module ActiveRecord
205
215
  # tracking is performed. The methods +changed?+ and +changed_in_place?+
206
216
  # will be called from ActiveModel::Dirty. See the documentation for those
207
217
  # 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
218
+ #
219
+ #--
220
+ # Implemented by ActiveModel::AttributeRegistration#attribute.
234
221
 
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
222
+ # This API only accepts type objects, and will do its work immediately instead of
223
+ # waiting for the schema to load. While this method
239
224
  # is provided so it can be used by plugin authors, application code
240
225
  # should probably use ClassMethods#attribute.
241
226
  #
242
- # +name+ The name of the attribute being defined. Expected to be a +String+.
227
+ # ==== Parameters
243
228
  #
244
- # +cast_type+ The type object to use for this attribute.
229
+ # [+name+]
230
+ # The name of the attribute being defined. Expected to be a +String+.
245
231
  #
246
- # +default+ The default value to use when no value is provided. If this option
247
- # is not passed, the previous default value (if any) will be used.
248
- # Otherwise, the default will be +nil+. A proc can also be passed, and
249
- # will be called once each time a new value is needed.
232
+ # [+cast_type+]
233
+ # The type object to use for this attribute.
250
234
  #
251
- # +user_provided_default+ Whether the default value should be cast using
252
- # +cast+ or +deserialize+.
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.
240
+ #
241
+ # [+user_provided_default+]
242
+ # Whether the default value should be cast using +cast+ or +deserialize+.
253
243
  def define_attribute(
254
244
  name,
255
245
  cast_type,
@@ -260,14 +250,39 @@ module ActiveRecord
260
250
  define_default_attribute(name, default, cast_type, from_user: user_provided_default)
261
251
  end
262
252
 
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)
253
+ def _default_attributes # :nodoc:
254
+ @default_attributes ||= begin
255
+ # TODO: Remove the need for a connection after we release 8.1.
256
+ attributes_hash = with_connection do |connection|
257
+ columns_hash.transform_values do |column|
258
+ ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
259
+ end
260
+ end
261
+
262
+ attribute_set = ActiveModel::AttributeSet.new(attributes_hash)
263
+ apply_pending_attribute_modifications(attribute_set)
264
+ attribute_set
268
265
  end
269
266
  end
270
267
 
268
+ ##
269
+ # :method: type_for_attribute
270
+ # :call-seq: type_for_attribute(attribute_name, &block)
271
+ #
272
+ # See ActiveModel::Attributes::ClassMethods#type_for_attribute.
273
+ #
274
+ # This method will access the database and load the model's schema if
275
+ # necessary.
276
+ #--
277
+ # Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute.
278
+
279
+ ##
280
+ protected
281
+ def reload_schema_from_cache(*)
282
+ reset_default_attributes!
283
+ super
284
+ end
285
+
271
286
  private
272
287
  NO_DEFAULT_PROVIDED = Object.new # :nodoc:
273
288
  private_constant :NO_DEFAULT_PROVIDED
@@ -287,6 +302,19 @@ module ActiveRecord
287
302
  end
288
303
  _default_attributes[name] = default_attribute
289
304
  end
305
+
306
+ def reset_default_attributes
307
+ reload_schema_from_cache
308
+ end
309
+
310
+ def resolve_type_name(name, **options)
311
+ Type.lookup(name, **options, adapter: Type.adapter_name_from(self))
312
+ end
313
+
314
+ def type_for_column(connection, column)
315
+ # TODO: Remove the need for a connection after we release 8.1.
316
+ hook_attribute_type(column.name, super)
317
+ end
290
318
  end
291
319
  end
292
320
  end