activerecord 7.0.8.7 → 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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +631 -1944
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +4 -4
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +54 -12
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. 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,54 @@ 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, coder: nil, type: Object, yaml: {}, **options)
184
+ coder ||= default_column_serializer
185
+ unless coder
186
+ raise ArgumentError, <<~MSG.squish
187
+ missing keyword: :coder
188
+
189
+ If no default coder is configured, a coder must be provided to `serialize`.
190
+ MSG
113
191
  end
114
192
 
115
- attribute(attr_name, **options) do |cast_type|
116
- if type_incompatible_with_serialize?(cast_type, class_name_or_coder)
193
+ column_serializer = build_column_serializer(attr_name, coder, type, yaml)
194
+
195
+ attribute(attr_name, **options)
196
+
197
+ decorate_attributes([attr_name]) do |attr_name, cast_type|
198
+ if type_incompatible_with_serialize?(cast_type, coder, type)
117
199
  raise ColumnNotSerializableError.new(attr_name, cast_type)
118
200
  end
119
201
 
120
202
  cast_type = cast_type.subtype if Type::Serialized === cast_type
121
- Type::Serialized.new(cast_type, coder)
203
+ Type::Serialized.new(cast_type, column_serializer)
122
204
  end
123
205
  end
124
206
 
125
207
  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
208
+ def build_column_serializer(attr_name, coder, type, yaml = nil)
209
+ # When ::JSON is used, force it to go through the Active Support JSON encoder
210
+ # to ensure special objects (e.g. Active Record models) are dumped correctly
211
+ # using the #as_json hook.
212
+ coder = Coders::JSON if coder == ::JSON
213
+
214
+ if coder == ::YAML || coder == Coders::YAMLColumn
215
+ Coders::YAMLColumn.new(attr_name, type, **(yaml || {}))
216
+ elsif coder.respond_to?(:new) && !coder.respond_to?(:load)
217
+ coder.new(attr_name, type)
218
+ elsif type && type != Object
219
+ Coders::ColumnSerializer.new(attr_name, coder, type)
220
+ else
221
+ coder
222
+ end
223
+ end
224
+
225
+ def type_incompatible_with_serialize?(cast_type, coder, type)
226
+ cast_type.is_a?(ActiveRecord::Type::Json) && coder == ::JSON ||
227
+ cast_type.respond_to?(:type_cast_array, true) && type == ::Array
129
228
  end
130
229
  end
131
230
  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?
@@ -69,14 +73,15 @@ module ActiveRecord
69
73
  end
70
74
 
71
75
  module ClassMethods # :nodoc:
72
- def define_attribute(name, cast_type, **)
73
- if create_time_zone_conversion_attribute?(name, cast_type)
74
- cast_type = TimeZoneConverter.new(cast_type)
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
75
83
  end
76
- super
77
- end
78
84
 
79
- private
80
85
  def create_time_zone_conversion_attribute?(name, cast_type)
81
86
  enabled_for_column = time_zone_aware_attributes &&
82
87
  !skip_time_zone_conversion_for_attributes.include?(name.to_sym)
@@ -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
@@ -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
@@ -33,44 +32,118 @@ module ActiveRecord
33
32
  Base.instance_methods +
34
33
  Base.private_instance_methods -
35
34
  Base.superclass.instance_methods -
36
- Base.superclass.private_instance_methods
35
+ Base.superclass.private_instance_methods +
36
+ %i[__id__ dup freeze frozen? hash class clone]
37
37
  ).map { |m| -m.to_s }.to_set.freeze
38
38
  end
39
39
  end
40
40
 
41
41
  module ClassMethods
42
- def inherited(child_class) # :nodoc:
43
- child_class.initialize_generated_modules
44
- super
45
- end
46
-
47
42
  def initialize_generated_modules # :nodoc:
48
43
  @generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new)
49
44
  private_constant :GeneratedAttributeMethods
50
45
  @attribute_methods_generated = false
46
+ @alias_attributes_mass_generated = false
51
47
  include @generated_attribute_methods
52
48
 
53
49
  super
54
50
  end
55
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"
66
+ def alias_attribute(new_name, old_name)
67
+ super
68
+
69
+ if @alias_attributes_mass_generated
70
+ ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
71
+ generate_alias_attribute_methods(code_generator, new_name, old_name)
72
+ end
73
+ end
74
+ end
75
+
76
+ def eagerly_generate_alias_attribute_methods(_new_name, _old_name) # :nodoc:
77
+ # alias attributes in Active Record are lazily generated
78
+ end
79
+
80
+ def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc:
81
+ attribute_method_patterns.each do |pattern|
82
+ alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
83
+ end
84
+ attribute_method_patterns_cache.clear
85
+ end
86
+
87
+ def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
88
+ old_name = old_name.to_s
89
+
90
+ if !abstract_class? && !has_attribute?(old_name)
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."
93
+ else
94
+ define_attribute_method_pattern(pattern, old_name, owner: code_generator, as: new_name, override: true)
95
+ end
96
+ end
97
+
98
+ def attribute_methods_generated? # :nodoc:
99
+ @attribute_methods_generated
100
+ end
101
+
56
102
  # Generates all the attribute related methods for columns in the database
57
103
  # accessors, mutators and query methods.
58
104
  def define_attribute_methods # :nodoc:
59
105
  return false if @attribute_methods_generated
60
106
  # Use a mutex; we don't want two threads simultaneously trying to define
61
107
  # attribute methods.
62
- generated_attribute_methods.synchronize do
108
+ GeneratedAttributeMethods::LOCK.synchronize do
63
109
  return false if @attribute_methods_generated
110
+
64
111
  superclass.define_attribute_methods unless base_class?
65
- 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
+
66
119
  @attribute_methods_generated = true
120
+
121
+ generate_alias_attributes
122
+ end
123
+ true
124
+ end
125
+
126
+ def generate_alias_attributes # :nodoc:
127
+ superclass.generate_alias_attributes unless superclass == Base
128
+
129
+ return if @alias_attributes_mass_generated
130
+
131
+ ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
132
+ aliases_by_attribute_name.each do |old_name, new_names|
133
+ new_names.each do |new_name|
134
+ generate_alias_attribute_methods(code_generator, new_name, old_name)
135
+ end
136
+ end
67
137
  end
138
+
139
+ @alias_attributes_mass_generated = true
68
140
  end
69
141
 
70
142
  def undefine_attribute_methods # :nodoc:
71
- generated_attribute_methods.synchronize do
72
- super if defined?(@attribute_methods_generated) && @attribute_methods_generated
143
+ GeneratedAttributeMethods::LOCK.synchronize do
144
+ super if @attribute_methods_generated
73
145
  @attribute_methods_generated = false
146
+ @alias_attributes_mass_generated = false
74
147
  end
75
148
  end
76
149
 
@@ -186,6 +259,16 @@ module ActiveRecord
186
259
  def _has_attribute?(attr_name) # :nodoc:
187
260
  attribute_types.key?(attr_name)
188
261
  end
262
+
263
+ private
264
+ def inherited(child_class)
265
+ super
266
+ child_class.initialize_generated_modules
267
+ child_class.class_eval do
268
+ @alias_attributes_mass_generated = false
269
+ @attribute_names = nil
270
+ end
271
+ end
189
272
  end
190
273
 
191
274
  # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
@@ -209,9 +292,7 @@ module ActiveRecord
209
292
 
210
293
  # If the result is true then check for the select case.
211
294
  # For queries selecting a subset of columns, return false for unselected columns.
212
- # We check defined?(@attributes) not to issue warnings if called on objects that
213
- # have been allocated but not yet initialized.
214
- if defined?(@attributes)
295
+ if @attributes
215
296
  if name = self.class.symbol_column_to_string(name.to_sym)
216
297
  return _has_attribute?(name)
217
298
  end
@@ -309,36 +390,40 @@ module ActiveRecord
309
390
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
310
391
  end
311
392
 
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.
393
+ # Returns the value of the attribute identified by +attr_name+ after it has
394
+ # been type cast. (For information about specific type casting behavior, see
395
+ # the types under ActiveModel::Type.)
317
396
  #
318
397
  # class Person < ActiveRecord::Base
319
398
  # belongs_to :organization
320
399
  # end
321
400
  #
322
- # person = Person.new(name: 'Francesco', age: '22')
323
- # person[:name] # => "Francesco"
324
- # person[:age] # => 22
401
+ # person = Person.new(name: "Francesco", date_of_birth: "2004-12-12")
402
+ # person[:name] # => "Francesco"
403
+ # person[:date_of_birth] # => Date.new(2004, 12, 12)
404
+ # person[:organization_id] # => nil
325
405
  #
326
- # person = Person.select('id').first
327
- # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
328
- # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
406
+ # Raises ActiveModel::MissingAttributeError if the attribute is missing.
407
+ # Note, however, that the +id+ attribute will never be considered missing.
408
+ #
409
+ # person = Person.select(:name).first
410
+ # person[:name] # => "Francesco"
411
+ # person[:date_of_birth] # => ActiveModel::MissingAttributeError: missing attribute 'date_of_birth' for Person
412
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute 'organization_id' for Person
413
+ # person[:id] # => nil
329
414
  def [](attr_name)
330
415
  read_attribute(attr_name) { |n| missing_attribute(n, caller) }
331
416
  end
332
417
 
333
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
418
+ # Updates the attribute identified by +attr_name+ using the specified
419
+ # +value+. The attribute value will be type cast upon being read.
334
420
  #
335
421
  # class Person < ActiveRecord::Base
336
422
  # end
337
423
  #
338
424
  # person = Person.new
339
- # person[:age] = '22'
340
- # person[:age] # => 22
341
- # person[:age].class # => Integer
425
+ # person[:date_of_birth] = "2004-12-12"
426
+ # person[:date_of_birth] # => Date.new(2004, 12, 12)
342
427
  def []=(attr_name, value)
343
428
  write_attribute(attr_name, value)
344
429
  end
@@ -376,9 +461,38 @@ module ActiveRecord
376
461
  end
377
462
 
378
463
  private
464
+ def respond_to_missing?(name, include_private = false)
465
+ if self.class.define_attribute_methods
466
+ # Some methods weren't defined yet.
467
+ return true if self.class.method_defined?(name)
468
+ return true if include_private && self.class.private_method_defined?(name)
469
+ end
470
+
471
+ super
472
+ end
473
+
474
+ def method_missing(name, ...)
475
+ unless self.class.attribute_methods_generated?
476
+ if self.class.method_defined?(name)
477
+ # The method is explicitly defined in the model, but calls a generated
478
+ # method with super. So we must resume the call chain at the right step.
479
+ last_method = method(name)
480
+ last_method = last_method.super_method while last_method.super_method
481
+ self.class.define_attribute_methods
482
+ if last_method.super_method
483
+ return last_method.super_method.call(...)
484
+ end
485
+ elsif self.class.define_attribute_methods
486
+ # Some attribute methods weren't generated yet, we retry the call
487
+ return public_send(name, ...)
488
+ end
489
+ end
490
+
491
+ super
492
+ end
493
+
379
494
  def attribute_method?(attr_name)
380
- # We check defined? because Syck calls respond_to? before actually calling initialize.
381
- defined?(@attributes) && @attributes.key?(attr_name)
495
+ @attributes&.key?(attr_name)
382
496
  end
383
497
 
384
498
  def attributes_with_values(attribute_names)
@@ -390,6 +504,7 @@ module ActiveRecord
390
504
  attribute_names &= self.class.column_names
391
505
  attribute_names.delete_if do |name|
392
506
  self.class.readonly_attribute?(name) ||
507
+ self.class.counter_cache_column?(name) ||
393
508
  column_for_attribute(name).virtual?
394
509
  end
395
510
  end