activerecord 7.0.8.7 → 7.2.3

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 (283) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +781 -1777
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +30 -30
  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 +31 -23
  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 +40 -9
  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 +35 -21
  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 +4 -3
  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 +153 -33
  47. data/lib/active_record/attributes.rb +96 -71
  48. data/lib/active_record/autosave_association.rb +81 -39
  49. data/lib/active_record/base.rb +11 -7
  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 +343 -91
  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 +229 -64
  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 +142 -12
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +539 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +289 -128
  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 +60 -55
  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 +108 -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 +153 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -1
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +371 -64
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +374 -203
  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 +57 -45
  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 +51 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +298 -113
  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 +101 -105
  110. data/lib/active_record/core.rb +273 -178
  111. data/lib/active_record/counter_cache.rb +69 -35
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -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 +56 -27
  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 +46 -22
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +48 -13
  130. data/lib/active_record/encryption/encryptor.rb +35 -19
  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 +130 -28
  143. data/lib/active_record/errors.rb +154 -34
  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 +48 -10
  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 +236 -118
  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 +96 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +35 -10
  178. data/lib/active_record/railtie.rb +131 -87
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +147 -155
  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 +270 -108
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +97 -21
  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 +20 -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 +3 -2
  196. data/lib/active_record/relation/query_methods.rb +585 -109
  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 +15 -21
  200. data/lib/active_record/relation.rb +592 -92
  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 +90 -23
  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 +33 -11
  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 +23 -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 +108 -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 +3 -1
  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/crud.rb +2 -0
  248. data/lib/arel/delete_manager.rb +5 -0
  249. data/lib/arel/errors.rb +10 -0
  250. data/lib/arel/factory_methods.rb +4 -0
  251. data/lib/arel/nodes/binary.rb +6 -7
  252. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  253. data/lib/arel/nodes/cte.rb +36 -0
  254. data/lib/arel/nodes/delete_statement.rb +4 -2
  255. data/lib/arel/nodes/fragments.rb +35 -0
  256. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  257. data/lib/arel/nodes/leading_join.rb +8 -0
  258. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  259. data/lib/arel/nodes/node.rb +115 -5
  260. data/lib/arel/nodes/sql_literal.rb +13 -0
  261. data/lib/arel/nodes/table_alias.rb +4 -0
  262. data/lib/arel/nodes/update_statement.rb +4 -2
  263. data/lib/arel/nodes.rb +6 -2
  264. data/lib/arel/predications.rb +3 -1
  265. data/lib/arel/select_manager.rb +7 -3
  266. data/lib/arel/table.rb +9 -5
  267. data/lib/arel/tree_manager.rb +8 -3
  268. data/lib/arel/update_manager.rb +7 -1
  269. data/lib/arel/visitors/dot.rb +3 -0
  270. data/lib/arel/visitors/mysql.rb +17 -5
  271. data/lib/arel/visitors/postgresql.rb +1 -12
  272. data/lib/arel/visitors/sqlite.rb +25 -0
  273. data/lib/arel/visitors/to_sql.rb +114 -34
  274. data/lib/arel/visitors/visitor.rb +2 -2
  275. data/lib/arel.rb +21 -3
  276. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  277. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  278. data/lib/rails/generators/active_record/migration.rb +3 -1
  279. data/lib/rails/generators/active_record/model/USAGE +113 -0
  280. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  281. metadata +56 -17
  282. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  283. 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,119 @@ 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) # :nodoc:
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") && !_has_attribute?("id_value")
117
+ end
118
+
119
+ generate_alias_attributes
120
+
66
121
  @attribute_methods_generated = true
67
122
  end
123
+
124
+ true
125
+ end
126
+
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
68
141
  end
69
142
 
70
143
  def undefine_attribute_methods # :nodoc:
71
- generated_attribute_methods.synchronize do
72
- super if defined?(@attribute_methods_generated) && @attribute_methods_generated
144
+ GeneratedAttributeMethods::LOCK.synchronize do
145
+ super if @attribute_methods_generated
73
146
  @attribute_methods_generated = false
147
+ @alias_attributes_mass_generated = false
74
148
  end
75
149
  end
76
150
 
@@ -186,6 +260,16 @@ module ActiveRecord
186
260
  def _has_attribute?(attr_name) # :nodoc:
187
261
  attribute_types.key?(attr_name)
188
262
  end
263
+
264
+ private
265
+ def inherited(child_class)
266
+ super
267
+ child_class.initialize_generated_modules
268
+ child_class.class_eval do
269
+ @alias_attributes_mass_generated = false
270
+ @attribute_names = nil
271
+ end
272
+ end
189
273
  end
190
274
 
191
275
  # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
@@ -209,9 +293,7 @@ module ActiveRecord
209
293
 
210
294
  # If the result is true then check for the select case.
211
295
  # 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)
296
+ if @attributes
215
297
  if name = self.class.symbol_column_to_string(name.to_sym)
216
298
  return _has_attribute?(name)
217
299
  end
@@ -309,36 +391,40 @@ module ActiveRecord
309
391
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
310
392
  end
311
393
 
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.
394
+ # Returns the value of the attribute identified by +attr_name+ after it has
395
+ # been type cast. (For information about specific type casting behavior, see
396
+ # the types under ActiveModel::Type.)
317
397
  #
318
398
  # class Person < ActiveRecord::Base
319
399
  # belongs_to :organization
320
400
  # end
321
401
  #
322
- # person = Person.new(name: 'Francesco', age: '22')
323
- # person[:name] # => "Francesco"
324
- # person[:age] # => 22
402
+ # person = Person.new(name: "Francesco", date_of_birth: "2004-12-12")
403
+ # person[:name] # => "Francesco"
404
+ # person[:date_of_birth] # => Date.new(2004, 12, 12)
405
+ # person[:organization_id] # => nil
406
+ #
407
+ # Raises ActiveModel::MissingAttributeError if the attribute is missing.
408
+ # Note, however, that the +id+ attribute will never be considered missing.
325
409
  #
326
- # person = Person.select('id').first
327
- # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
328
- # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
410
+ # person = Person.select(:name).first
411
+ # person[:name] # => "Francesco"
412
+ # person[:date_of_birth] # => ActiveModel::MissingAttributeError: missing attribute 'date_of_birth' for Person
413
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute 'organization_id' for Person
414
+ # person[:id] # => nil
329
415
  def [](attr_name)
330
416
  read_attribute(attr_name) { |n| missing_attribute(n, caller) }
331
417
  end
332
418
 
333
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
419
+ # Updates the attribute identified by +attr_name+ using the specified
420
+ # +value+. The attribute value will be type cast upon being read.
334
421
  #
335
422
  # class Person < ActiveRecord::Base
336
423
  # end
337
424
  #
338
425
  # person = Person.new
339
- # person[:age] = '22'
340
- # person[:age] # => 22
341
- # person[:age].class # => Integer
426
+ # person[:date_of_birth] = "2004-12-12"
427
+ # person[:date_of_birth] # => Date.new(2004, 12, 12)
342
428
  def []=(attr_name, value)
343
429
  write_attribute(attr_name, value)
344
430
  end
@@ -376,9 +462,42 @@ module ActiveRecord
376
462
  end
377
463
 
378
464
  private
465
+ def respond_to_missing?(name, include_private = false)
466
+ if self.class.define_attribute_methods
467
+ # Some methods weren't defined yet.
468
+ return true if self.class.method_defined?(name)
469
+ return true if include_private && self.class.private_method_defined?(name)
470
+ end
471
+
472
+ super
473
+ end
474
+
475
+ def method_missing(name, ...)
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
487
+ end
488
+
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
497
+ end
498
+
379
499
  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)
500
+ @attributes&.key?(attr_name)
382
501
  end
383
502
 
384
503
  def attributes_with_values(attribute_names)
@@ -390,6 +509,7 @@ module ActiveRecord
390
509
  attribute_names &= self.class.column_names
391
510
  attribute_names.delete_if do |name|
392
511
  self.class.readonly_attribute?(name) ||
512
+ self.class.counter_cache_column?(name) ||
393
513
  column_for_attribute(name).virtual?
394
514
  end
395
515
  end