activerecord 7.0.8.7 → 7.1.5.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 (237) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1795 -1424
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +20 -4
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +19 -13
  15. data/lib/active_record/associations/collection_proxy.rb +15 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  21. data/lib/active_record/associations/join_dependency.rb +10 -10
  22. data/lib/active_record/associations/preloader/association.rb +31 -7
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +319 -217
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +21 -8
  33. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  34. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
  35. data/lib/active_record/attribute_methods/write.rb +6 -6
  36. data/lib/active_record/attribute_methods.rb +145 -21
  37. data/lib/active_record/attributes.rb +3 -3
  38. data/lib/active_record/autosave_association.rb +59 -10
  39. data/lib/active_record/base.rb +7 -2
  40. data/lib/active_record/callbacks.rb +10 -24
  41. data/lib/active_record/coders/column_serializer.rb +61 -0
  42. data/lib/active_record/coders/json.rb +1 -1
  43. data/lib/active_record/coders/yaml_column.rb +70 -42
  44. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  47. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  48. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  49. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  50. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  51. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  52. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +296 -127
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -92
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +244 -121
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  79. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  80. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  81. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +364 -61
  83. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  84. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  85. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  86. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  87. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  88. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  89. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  90. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -81
  91. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  92. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  93. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  94. data/lib/active_record/connection_adapters.rb +3 -1
  95. data/lib/active_record/connection_handling.rb +72 -95
  96. data/lib/active_record/core.rb +181 -154
  97. data/lib/active_record/counter_cache.rb +52 -27
  98. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  99. data/lib/active_record/database_configurations/database_config.rb +9 -3
  100. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  101. data/lib/active_record/database_configurations/url_config.rb +17 -11
  102. data/lib/active_record/database_configurations.rb +86 -33
  103. data/lib/active_record/delegated_type.rb +15 -10
  104. data/lib/active_record/deprecator.rb +7 -0
  105. data/lib/active_record/destroy_association_async_job.rb +3 -1
  106. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  107. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  108. data/lib/active_record/encryption/config.rb +25 -1
  109. data/lib/active_record/encryption/configurable.rb +12 -19
  110. data/lib/active_record/encryption/context.rb +10 -3
  111. data/lib/active_record/encryption/contexts.rb +5 -1
  112. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  113. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  114. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  115. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  116. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  117. data/lib/active_record/encryption/key_generator.rb +12 -1
  118. data/lib/active_record/encryption/message_serializer.rb +2 -0
  119. data/lib/active_record/encryption/properties.rb +3 -3
  120. data/lib/active_record/encryption/scheme.rb +22 -21
  121. data/lib/active_record/encryption.rb +3 -0
  122. data/lib/active_record/enum.rb +112 -28
  123. data/lib/active_record/errors.rb +112 -18
  124. data/lib/active_record/explain.rb +23 -3
  125. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  126. data/lib/active_record/fixture_set/render_context.rb +2 -0
  127. data/lib/active_record/fixture_set/table_row.rb +29 -8
  128. data/lib/active_record/fixtures.rb +135 -71
  129. data/lib/active_record/future_result.rb +40 -5
  130. data/lib/active_record/gem_version.rb +4 -4
  131. data/lib/active_record/inheritance.rb +30 -16
  132. data/lib/active_record/insert_all.rb +57 -10
  133. data/lib/active_record/integration.rb +8 -8
  134. data/lib/active_record/internal_metadata.rb +120 -30
  135. data/lib/active_record/locking/optimistic.rb +1 -1
  136. data/lib/active_record/locking/pessimistic.rb +5 -2
  137. data/lib/active_record/log_subscriber.rb +29 -12
  138. data/lib/active_record/marshalling.rb +59 -0
  139. data/lib/active_record/message_pack.rb +124 -0
  140. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  141. data/lib/active_record/middleware/database_selector.rb +6 -8
  142. data/lib/active_record/middleware/shard_selector.rb +3 -1
  143. data/lib/active_record/migration/command_recorder.rb +104 -5
  144. data/lib/active_record/migration/compatibility.rb +145 -5
  145. data/lib/active_record/migration/default_strategy.rb +23 -0
  146. data/lib/active_record/migration/execution_strategy.rb +19 -0
  147. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  148. data/lib/active_record/migration.rb +219 -111
  149. data/lib/active_record/model_schema.rb +69 -44
  150. data/lib/active_record/nested_attributes.rb +37 -8
  151. data/lib/active_record/normalization.rb +167 -0
  152. data/lib/active_record/persistence.rb +188 -37
  153. data/lib/active_record/promise.rb +84 -0
  154. data/lib/active_record/query_cache.rb +4 -22
  155. data/lib/active_record/query_logs.rb +77 -52
  156. data/lib/active_record/query_logs_formatter.rb +41 -0
  157. data/lib/active_record/querying.rb +15 -2
  158. data/lib/active_record/railtie.rb +107 -45
  159. data/lib/active_record/railties/controller_runtime.rb +12 -6
  160. data/lib/active_record/railties/databases.rake +144 -150
  161. data/lib/active_record/railties/job_runtime.rb +23 -0
  162. data/lib/active_record/readonly_attributes.rb +32 -5
  163. data/lib/active_record/reflection.rb +181 -45
  164. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  165. data/lib/active_record/relation/batches.rb +190 -61
  166. data/lib/active_record/relation/calculations.rb +187 -63
  167. data/lib/active_record/relation/delegation.rb +23 -9
  168. data/lib/active_record/relation/finder_methods.rb +77 -16
  169. data/lib/active_record/relation/merger.rb +2 -0
  170. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  171. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  172. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  173. data/lib/active_record/relation/predicate_builder.rb +26 -14
  174. data/lib/active_record/relation/query_attribute.rb +2 -1
  175. data/lib/active_record/relation/query_methods.rb +371 -68
  176. data/lib/active_record/relation/spawn_methods.rb +18 -1
  177. data/lib/active_record/relation.rb +103 -37
  178. data/lib/active_record/result.rb +19 -5
  179. data/lib/active_record/runtime_registry.rb +24 -1
  180. data/lib/active_record/sanitization.rb +51 -11
  181. data/lib/active_record/schema.rb +2 -3
  182. data/lib/active_record/schema_dumper.rb +46 -7
  183. data/lib/active_record/schema_migration.rb +68 -33
  184. data/lib/active_record/scoping/default.rb +15 -5
  185. data/lib/active_record/scoping/named.rb +2 -2
  186. data/lib/active_record/scoping.rb +2 -1
  187. data/lib/active_record/secure_password.rb +60 -0
  188. data/lib/active_record/secure_token.rb +21 -3
  189. data/lib/active_record/signed_id.rb +7 -5
  190. data/lib/active_record/store.rb +8 -8
  191. data/lib/active_record/suppressor.rb +3 -1
  192. data/lib/active_record/table_metadata.rb +10 -1
  193. data/lib/active_record/tasks/database_tasks.rb +152 -108
  194. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  195. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  196. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  197. data/lib/active_record/test_fixtures.rb +114 -96
  198. data/lib/active_record/timestamp.rb +30 -16
  199. data/lib/active_record/token_for.rb +113 -0
  200. data/lib/active_record/touch_later.rb +11 -6
  201. data/lib/active_record/transactions.rb +36 -10
  202. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  203. data/lib/active_record/type/internal/timezone.rb +7 -2
  204. data/lib/active_record/type/time.rb +4 -0
  205. data/lib/active_record/validations/absence.rb +1 -1
  206. data/lib/active_record/validations/numericality.rb +5 -4
  207. data/lib/active_record/validations/presence.rb +5 -28
  208. data/lib/active_record/validations/uniqueness.rb +47 -2
  209. data/lib/active_record/validations.rb +8 -4
  210. data/lib/active_record/version.rb +1 -1
  211. data/lib/active_record.rb +122 -17
  212. data/lib/arel/errors.rb +10 -0
  213. data/lib/arel/factory_methods.rb +4 -0
  214. data/lib/arel/nodes/binary.rb +6 -1
  215. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  216. data/lib/arel/nodes/cte.rb +36 -0
  217. data/lib/arel/nodes/fragments.rb +35 -0
  218. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  219. data/lib/arel/nodes/leading_join.rb +8 -0
  220. data/lib/arel/nodes/node.rb +111 -2
  221. data/lib/arel/nodes/sql_literal.rb +6 -0
  222. data/lib/arel/nodes/table_alias.rb +4 -0
  223. data/lib/arel/nodes.rb +4 -0
  224. data/lib/arel/predications.rb +2 -0
  225. data/lib/arel/table.rb +9 -5
  226. data/lib/arel/tree_manager.rb +5 -1
  227. data/lib/arel/visitors/mysql.rb +8 -1
  228. data/lib/arel/visitors/to_sql.rb +83 -18
  229. data/lib/arel/visitors/visitor.rb +2 -2
  230. data/lib/arel.rb +16 -2
  231. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  232. data/lib/rails/generators/active_record/migration.rb +3 -1
  233. data/lib/rails/generators/active_record/model/USAGE +113 -0
  234. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  235. metadata +46 -10
  236. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  237. data/lib/active_record/null_relation.rb +0 -63
@@ -2,6 +2,7 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module AttributeMethods
5
+ # = Active Record Attribute Methods \Serialization
5
6
  module Serialization
6
7
  extend ActiveSupport::Concern
7
8
 
@@ -15,6 +16,10 @@ module ActiveRecord
15
16
  end
16
17
  end
17
18
 
19
+ included do
20
+ class_attribute :default_column_serializer, instance_accessor: false, default: Coders::YAMLColumn
21
+ end
22
+
18
23
  module ClassMethods
19
24
  # If you have an attribute that needs to be saved to the database as a
20
25
  # serialized object, and retrieved by deserializing into the same object,
@@ -36,21 +41,19 @@ module ActiveRecord
36
41
  # ==== Parameters
37
42
  #
38
43
  # * +attr_name+ - The name of the attribute to serialize.
39
- # * +class_name_or_coder+ - Optional. May be one of the following:
40
- # * <em>default</em> - The attribute value will be serialized as YAML.
41
- # The attribute value must respond to +to_yaml+.
42
- # * +Array+ - The attribute value will be serialized as YAML, but an
43
- # empty +Array+ will be serialized as +NULL+. The attribute value
44
- # must be an +Array+.
45
- # * +Hash+ - The attribute value will be serialized as YAML, but an
46
- # empty +Hash+ will be serialized as +NULL+. The attribute value
47
- # must be a +Hash+.
48
- # * +JSON+ - The attribute value will be serialized as JSON. The
49
- # attribute value must respond to +to_json+.
50
- # * <em>custom coder</em> - The attribute value will be serialized
44
+ # * +coder+ The serializer implementation to use, e.g. +JSON+.
45
+ # * The attribute value will be serialized
51
46
  # using the coder's <tt>dump(value)</tt> method, and will be
52
47
  # deserialized using the coder's <tt>load(string)</tt> method. The
53
48
  # +dump+ method may return +nil+ to serialize the value as +NULL+.
49
+ # * +type+ - Optional. What the type of the serialized object should be.
50
+ # * Attempting to serialize another type will raise an
51
+ # ActiveRecord::SerializationTypeMismatch error.
52
+ # * If the column is +NULL+ or starting from a new record, the default value
53
+ # will set to +type.new+
54
+ # * +yaml+ - Optional. Yaml specific options. The allowed config is:
55
+ # * +:permitted_classes+ - +Array+ with the permitted classes.
56
+ # * +:unsafe_load+ - Unsafely load YAML blobs, allow YAML to load any class.
54
57
  #
55
58
  # ==== Options
56
59
  #
@@ -58,24 +61,101 @@ module ActiveRecord
58
61
  # this option is not passed, the previous default value (if any) will
59
62
  # be used. Otherwise, the default will be +nil+.
60
63
  #
64
+ # ==== Choosing a serializer
65
+ #
66
+ # While any serialization format can be used, it is recommended to carefully
67
+ # evaluate the properties of a serializer before using it, as migrating to
68
+ # another format later on can be difficult.
69
+ #
70
+ # ===== Avoid accepting arbitrary types
71
+ #
72
+ # When serializing data in a column, it is heavily recommended to make sure
73
+ # only expected types will be serialized. For instance some serializer like
74
+ # +Marshal+ or +YAML+ are capable of serializing almost any Ruby object.
75
+ #
76
+ # This can lead to unexpected types being serialized, and it is important
77
+ # that type serialization remains backward and forward compatible as long
78
+ # as some database records still contain these serialized types.
79
+ #
80
+ # class Address
81
+ # def initialize(line, city, country)
82
+ # @line, @city, @country = line, city, country
83
+ # end
84
+ # end
85
+ #
86
+ # In the above example, if any of the +Address+ attributes is renamed,
87
+ # instances that were persisted before the change will be loaded with the
88
+ # old attributes. This problem is even worse when the serialized type comes
89
+ # from a dependency which doesn't expect to be serialized this way and may
90
+ # change its internal representation without notice.
91
+ #
92
+ # As such, it is heavily recommended to instead convert these objects into
93
+ # primitives of the serialization format, for example:
94
+ #
95
+ # class Address
96
+ # attr_reader :line, :city, :country
97
+ #
98
+ # def self.load(payload)
99
+ # data = YAML.safe_load(payload)
100
+ # new(data["line"], data["city"], data["country"])
101
+ # end
102
+ #
103
+ # def self.dump(address)
104
+ # YAML.safe_dump(
105
+ # "line" => address.line,
106
+ # "city" => address.city,
107
+ # "country" => address.country,
108
+ # )
109
+ # end
110
+ #
111
+ # def initialize(line, city, country)
112
+ # @line, @city, @country = line, city, country
113
+ # end
114
+ # end
115
+ #
116
+ # class User < ActiveRecord::Base
117
+ # serialize :address, coder: Address
118
+ # end
119
+ #
120
+ # This pattern allows to be more deliberate about what is serialized, and
121
+ # to evolve the format in a backward compatible way.
122
+ #
123
+ # ===== Ensure serialization stability
124
+ #
125
+ # Some serialization methods may accept some types they don't support by
126
+ # silently casting them to other types. This can cause bugs when the
127
+ # data is deserialized.
128
+ #
129
+ # For instance the +JSON+ serializer provided in the standard library will
130
+ # silently cast unsupported types to +String+:
131
+ #
132
+ # >> JSON.parse(JSON.dump(Struct.new(:foo)))
133
+ # => "#<Class:0x000000013090b4c0>"
134
+ #
61
135
  # ==== Examples
62
136
  #
63
137
  # ===== Serialize the +preferences+ attribute using YAML
64
138
  #
65
139
  # class User < ActiveRecord::Base
66
- # serialize :preferences
140
+ # serialize :preferences, coder: YAML
67
141
  # end
68
142
  #
69
143
  # ===== Serialize the +preferences+ attribute using JSON
70
144
  #
71
145
  # class User < ActiveRecord::Base
72
- # serialize :preferences, JSON
146
+ # serialize :preferences, coder: JSON
73
147
  # end
74
148
  #
75
149
  # ===== Serialize the +preferences+ +Hash+ using YAML
76
150
  #
77
151
  # class User < ActiveRecord::Base
78
- # serialize :preferences, Hash
152
+ # serialize :preferences, type: Hash, coder: YAML
153
+ # end
154
+ #
155
+ # ===== Serializes +preferences+ to YAML, permitting select classes
156
+ #
157
+ # class User < ActiveRecord::Base
158
+ # serialize :preferences, coder: YAML, yaml: { permitted_classes: [Symbol, Time] }
79
159
  # end
80
160
  #
81
161
  # ===== Serialize the +preferences+ attribute using a custom coder
@@ -97,35 +177,74 @@ module ActiveRecord
97
177
  # end
98
178
  #
99
179
  # class User < ActiveRecord::Base
100
- # serialize :preferences, Rot13JSON
180
+ # serialize :preferences, coder: Rot13JSON
101
181
  # end
102
182
  #
103
- def serialize(attr_name, class_name_or_coder = Object, **options)
104
- # When ::JSON is used, force it to go through the Active Support JSON encoder
105
- # to ensure special objects (e.g. Active Record models) are dumped correctly
106
- # using the #as_json hook.
107
- coder = if class_name_or_coder == ::JSON
108
- Coders::JSON
109
- elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
110
- class_name_or_coder
111
- else
112
- Coders::YAMLColumn.new(attr_name, class_name_or_coder)
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
+
206
+ coder ||= default_column_serializer
207
+ unless coder
208
+ raise ArgumentError, <<~MSG.squish
209
+ missing keyword: :coder
210
+
211
+ If no default coder is configured, a coder must be provided to `serialize`.
212
+ MSG
113
213
  end
114
214
 
215
+ column_serializer = build_column_serializer(attr_name, coder, type, yaml)
216
+
115
217
  attribute(attr_name, **options) do |cast_type|
116
- if type_incompatible_with_serialize?(cast_type, class_name_or_coder)
218
+ if type_incompatible_with_serialize?(cast_type, coder, type)
117
219
  raise ColumnNotSerializableError.new(attr_name, cast_type)
118
220
  end
119
221
 
120
222
  cast_type = cast_type.subtype if Type::Serialized === cast_type
121
- Type::Serialized.new(cast_type, coder)
223
+ Type::Serialized.new(cast_type, column_serializer)
122
224
  end
123
225
  end
124
226
 
125
227
  private
126
- def type_incompatible_with_serialize?(type, class_name)
127
- type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON ||
128
- type.respond_to?(:type_cast_array, true) && class_name == ::Array
228
+ def build_column_serializer(attr_name, coder, type, yaml = nil)
229
+ # When ::JSON is used, force it to go through the Active Support JSON encoder
230
+ # to ensure special objects (e.g. Active Record models) are dumped correctly
231
+ # using the #as_json hook.
232
+ coder = Coders::JSON if coder == ::JSON
233
+
234
+ if coder == ::YAML || coder == Coders::YAMLColumn
235
+ Coders::YAMLColumn.new(attr_name, type, **(yaml || {}))
236
+ elsif coder.respond_to?(:new) && !coder.respond_to?(:load)
237
+ coder.new(attr_name, type)
238
+ elsif type && type != Object
239
+ Coders::ColumnSerializer.new(attr_name, coder, type)
240
+ else
241
+ coder
242
+ end
243
+ end
244
+
245
+ def type_incompatible_with_serialize?(cast_type, coder, type)
246
+ cast_type.is_a?(ActiveRecord::Type::Json) && coder == ::JSON ||
247
+ cast_type.respond_to?(:type_cast_array, true) && type == ::Array
129
248
  end
130
249
  end
131
250
  end
@@ -32,6 +32,10 @@ module ActiveRecord
32
32
  end
33
33
  end
34
34
 
35
+ def ==(other)
36
+ other.is_a?(self.class) && __getobj__ == other.__getobj__
37
+ end
38
+
35
39
  private
36
40
  def convert_time_to_time_zone(value)
37
41
  return if value.nil?
@@ -2,6 +2,7 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module AttributeMethods
5
+ # = Active Record Attribute Methods \Write
5
6
  module Write
6
7
  extend ActiveSupport::Concern
7
8
 
@@ -11,11 +12,11 @@ module ActiveRecord
11
12
 
12
13
  module ClassMethods # :nodoc:
13
14
  private
14
- def define_method_attribute=(name, owner:)
15
+ def define_method_attribute=(canonical_name, owner:, as: canonical_name)
15
16
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
16
- owner, name, writer: true,
17
+ owner, canonical_name, writer: true,
17
18
  ) do |temp_method_name, attr_name_expr|
18
- 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|
19
20
  batch <<
20
21
  "def #{temp_method_name}(value)" <<
21
22
  " _write_attribute(#{attr_name_expr}, value)" <<
@@ -25,9 +26,8 @@ module ActiveRecord
25
26
  end
26
27
  end
27
28
 
28
- # Updates the attribute identified by <tt>attr_name</tt> with the
29
- # specified +value+. Empty strings for Integer and Float columns are
30
- # turned into +nil+.
29
+ # Updates the attribute identified by +attr_name+ using the specified
30
+ # +value+. The attribute value will be type cast upon being read.
31
31
  def write_attribute(attr_name, value)
32
32
  name = attr_name.to_s
33
33
  name = self.class.attribute_aliases[name] || name
@@ -33,26 +33,99 @@ module ActiveRecord
33
33
  Base.instance_methods +
34
34
  Base.private_instance_methods -
35
35
  Base.superclass.instance_methods -
36
- Base.superclass.private_instance_methods
36
+ Base.superclass.private_instance_methods +
37
+ %i[__id__ dup freeze frozen? hash class clone]
37
38
  ).map { |m| -m.to_s }.to_set.freeze
38
39
  end
39
40
  end
40
41
 
41
42
  module ClassMethods
42
- def inherited(child_class) # :nodoc:
43
- child_class.initialize_generated_modules
44
- super
45
- end
46
-
47
43
  def initialize_generated_modules # :nodoc:
48
44
  @generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new)
49
45
  private_constant :GeneratedAttributeMethods
50
46
  @attribute_methods_generated = false
47
+ @alias_attributes_mass_generated = false
51
48
  include @generated_attribute_methods
52
49
 
53
50
  super
54
51
  end
55
52
 
53
+ def alias_attribute(new_name, old_name)
54
+ super
55
+
56
+ if @alias_attributes_mass_generated
57
+ ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
58
+ generate_alias_attribute_methods(code_generator, new_name, old_name)
59
+ end
60
+ end
61
+ end
62
+
63
+ def eagerly_generate_alias_attribute_methods(_new_name, _old_name) # :nodoc:
64
+ # alias attributes in Active Record are lazily generated
65
+ end
66
+
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
+ def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc:
87
+ attribute_method_patterns.each do |pattern|
88
+ alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
89
+ end
90
+ attribute_method_patterns_cache.clear
91
+ end
92
+
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
96
+ old_name = old_name.to_s
97
+
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
+ 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
124
+ else
125
+ define_attribute_method_pattern(pattern, old_name, owner: code_generator, as: new_name, override: true)
126
+ end
127
+ end
128
+
56
129
  # Generates all the attribute related methods for columns in the database
57
130
  # accessors, mutators and query methods.
58
131
  def define_attribute_methods # :nodoc:
@@ -65,12 +138,18 @@ module ActiveRecord
65
138
  super(attribute_names)
66
139
  @attribute_methods_generated = true
67
140
  end
141
+ true
142
+ end
143
+
144
+ def attribute_methods_generated? # :nodoc:
145
+ @attribute_methods_generated && @alias_attributes_mass_generated
68
146
  end
69
147
 
70
148
  def undefine_attribute_methods # :nodoc:
71
149
  generated_attribute_methods.synchronize do
72
150
  super if defined?(@attribute_methods_generated) && @attribute_methods_generated
73
151
  @attribute_methods_generated = false
152
+ @alias_attributes_mass_generated = false
74
153
  end
75
154
  end
76
155
 
@@ -186,6 +265,16 @@ module ActiveRecord
186
265
  def _has_attribute?(attr_name) # :nodoc:
187
266
  attribute_types.key?(attr_name)
188
267
  end
268
+
269
+ private
270
+ def inherited(child_class)
271
+ super
272
+ child_class.initialize_generated_modules
273
+ child_class.class_eval do
274
+ @alias_attributes_mass_generated = false
275
+ @attribute_names = nil
276
+ end
277
+ end
189
278
  end
190
279
 
191
280
  # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
@@ -309,36 +398,40 @@ module ActiveRecord
309
398
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
310
399
  end
311
400
 
312
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
313
- # "2004-12-12" in a date column is cast to a date object, like <tt>Date.new(2004, 12, 12)</tt>). It raises
314
- # ActiveModel::MissingAttributeError if the identified attribute is missing.
315
- #
316
- # Note: +:id+ is always present.
401
+ # Returns the value of the attribute identified by +attr_name+ after it has
402
+ # been type cast. (For information about specific type casting behavior, see
403
+ # the types under ActiveModel::Type.)
317
404
  #
318
405
  # class Person < ActiveRecord::Base
319
406
  # belongs_to :organization
320
407
  # end
321
408
  #
322
- # person = Person.new(name: 'Francesco', age: '22')
323
- # person[:name] # => "Francesco"
324
- # person[:age] # => 22
409
+ # person = Person.new(name: "Francesco", date_of_birth: "2004-12-12")
410
+ # person[:name] # => "Francesco"
411
+ # person[:date_of_birth] # => Date.new(2004, 12, 12)
412
+ # person[:organization_id] # => nil
413
+ #
414
+ # Raises ActiveModel::MissingAttributeError if the attribute is missing.
415
+ # Note, however, that the +id+ attribute will never be considered missing.
325
416
  #
326
- # person = Person.select('id').first
327
- # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
328
- # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
417
+ # person = Person.select(:name).first
418
+ # person[:name] # => "Francesco"
419
+ # person[:date_of_birth] # => ActiveModel::MissingAttributeError: missing attribute 'date_of_birth' for Person
420
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute 'organization_id' for Person
421
+ # person[:id] # => nil
329
422
  def [](attr_name)
330
423
  read_attribute(attr_name) { |n| missing_attribute(n, caller) }
331
424
  end
332
425
 
333
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
426
+ # Updates the attribute identified by +attr_name+ using the specified
427
+ # +value+. The attribute value will be type cast upon being read.
334
428
  #
335
429
  # class Person < ActiveRecord::Base
336
430
  # end
337
431
  #
338
432
  # person = Person.new
339
- # person[:age] = '22'
340
- # person[:age] # => 22
341
- # person[:age].class # => Integer
433
+ # person[:date_of_birth] = "2004-12-12"
434
+ # person[:date_of_birth] # => Date.new(2004, 12, 12)
342
435
  def []=(attr_name, value)
343
436
  write_attribute(attr_name, value)
344
437
  end
@@ -376,6 +469,36 @@ module ActiveRecord
376
469
  end
377
470
 
378
471
  private
472
+ def respond_to_missing?(name, include_private = false)
473
+ if self.class.define_attribute_methods
474
+ # Some methods weren't defined yet.
475
+ return true if self.class.method_defined?(name)
476
+ return true if include_private && self.class.private_method_defined?(name)
477
+ end
478
+
479
+ super
480
+ end
481
+
482
+ 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
497
+ end
498
+
499
+ super
500
+ end
501
+
379
502
  def attribute_method?(attr_name)
380
503
  # We check defined? because Syck calls respond_to? before actually calling initialize.
381
504
  defined?(@attributes) && @attributes.key?(attr_name)
@@ -390,6 +513,7 @@ module ActiveRecord
390
513
  attribute_names &= self.class.column_names
391
514
  attribute_names.delete_if do |name|
392
515
  self.class.readonly_attribute?(name) ||
516
+ self.class.counter_cache_column?(name) ||
393
517
  column_for_attribute(name).virtual?
394
518
  end
395
519
  end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  included do
11
11
  class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
12
12
  end
13
-
13
+ # = Active Record \Attributes
14
14
  module ClassMethods
15
15
  # Defines an attribute with a type on this model. It will override the
16
16
  # type of existing attributes if needed. This allows control over how
@@ -194,10 +194,10 @@ module ActiveRecord
194
194
  # end
195
195
  #
196
196
  # Product.where(price_in_bitcoins: Money.new(5, "USD"))
197
- # # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
197
+ # # SELECT * FROM products WHERE price_in_bitcoins = 0.02230
198
198
  #
199
199
  # Product.where(price_in_bitcoins: Money.new(5, "GBP"))
200
- # # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
200
+ # # SELECT * FROM products WHERE price_in_bitcoins = 0.03412
201
201
  #
202
202
  # ==== Dirty Tracking
203
203
  #