activerecord 7.0.0 → 7.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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -1268
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  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 +28 -17
  20. data/lib/active_record/associations/collection_proxy.rb +36 -13
  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 +28 -18
  24. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +18 -14
  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 +2 -4
  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 +378 -491
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  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 +153 -70
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +153 -40
  47. data/lib/active_record/attributes.rb +63 -48
  48. data/lib/active_record/autosave_association.rb +70 -38
  49. data/lib/active_record/base.rb +12 -8
  50. data/lib/active_record/callbacks.rb +16 -32
  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 -34
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
  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 +297 -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 +215 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
  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 +163 -29
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
  70. data/lib/active_record/connection_adapters/column.rb +9 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
  100. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
  104. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  107. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
  108. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
  109. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  110. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  111. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  112. data/lib/active_record/connection_adapters.rb +124 -1
  113. data/lib/active_record/connection_handling.rb +98 -106
  114. data/lib/active_record/core.rb +220 -177
  115. data/lib/active_record/counter_cache.rb +68 -34
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
  117. data/lib/active_record/database_configurations/database_config.rb +26 -5
  118. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  119. data/lib/active_record/database_configurations/url_config.rb +37 -12
  120. data/lib/active_record/database_configurations.rb +88 -35
  121. data/lib/active_record/delegated_type.rb +40 -11
  122. data/lib/active_record/deprecator.rb +7 -0
  123. data/lib/active_record/destroy_association_async_job.rb +3 -1
  124. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  125. data/lib/active_record/dynamic_matchers.rb +2 -2
  126. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  127. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  128. data/lib/active_record/encryption/config.rb +25 -1
  129. data/lib/active_record/encryption/configurable.rb +13 -14
  130. data/lib/active_record/encryption/context.rb +10 -3
  131. data/lib/active_record/encryption/contexts.rb +8 -4
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  134. data/lib/active_record/encryption/encryptable_record.rb +47 -25
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
  136. data/lib/active_record/encryption/encryptor.rb +25 -10
  137. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  138. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  139. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  140. data/lib/active_record/encryption/key_generator.rb +12 -1
  141. data/lib/active_record/encryption/message.rb +1 -1
  142. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  143. data/lib/active_record/encryption/message_serializer.rb +6 -0
  144. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  145. data/lib/active_record/encryption/properties.rb +4 -4
  146. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  147. data/lib/active_record/encryption/scheme.rb +23 -22
  148. data/lib/active_record/encryption.rb +1 -0
  149. data/lib/active_record/enum.rb +131 -27
  150. data/lib/active_record/errors.rb +151 -31
  151. data/lib/active_record/explain.rb +21 -12
  152. data/lib/active_record/explain_subscriber.rb +1 -1
  153. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  154. data/lib/active_record/fixture_set/render_context.rb +2 -0
  155. data/lib/active_record/fixture_set/table_row.rb +29 -8
  156. data/lib/active_record/fixtures.rb +169 -99
  157. data/lib/active_record/future_result.rb +47 -8
  158. data/lib/active_record/gem_version.rb +3 -3
  159. data/lib/active_record/inheritance.rb +34 -18
  160. data/lib/active_record/insert_all.rb +72 -22
  161. data/lib/active_record/integration.rb +13 -10
  162. data/lib/active_record/internal_metadata.rb +124 -20
  163. data/lib/active_record/locking/optimistic.rb +39 -24
  164. data/lib/active_record/locking/pessimistic.rb +8 -5
  165. data/lib/active_record/log_subscriber.rb +28 -27
  166. data/lib/active_record/marshalling.rb +56 -0
  167. data/lib/active_record/message_pack.rb +124 -0
  168. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  169. data/lib/active_record/middleware/database_selector.rb +18 -13
  170. data/lib/active_record/middleware/shard_selector.rb +7 -5
  171. data/lib/active_record/migration/command_recorder.rb +110 -13
  172. data/lib/active_record/migration/compatibility.rb +174 -64
  173. data/lib/active_record/migration/default_strategy.rb +22 -0
  174. data/lib/active_record/migration/execution_strategy.rb +19 -0
  175. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  176. data/lib/active_record/migration.rb +292 -125
  177. data/lib/active_record/model_schema.rb +113 -112
  178. data/lib/active_record/nested_attributes.rb +35 -9
  179. data/lib/active_record/normalization.rb +163 -0
  180. data/lib/active_record/persistence.rb +177 -345
  181. data/lib/active_record/promise.rb +84 -0
  182. data/lib/active_record/query_cache.rb +19 -25
  183. data/lib/active_record/query_logs.rb +102 -51
  184. data/lib/active_record/query_logs_formatter.rb +41 -0
  185. data/lib/active_record/querying.rb +34 -9
  186. data/lib/active_record/railtie.rb +153 -100
  187. data/lib/active_record/railties/controller_runtime.rb +24 -10
  188. data/lib/active_record/railties/databases.rake +148 -152
  189. data/lib/active_record/railties/job_runtime.rb +23 -0
  190. data/lib/active_record/readonly_attributes.rb +32 -5
  191. data/lib/active_record/reflection.rb +278 -69
  192. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  193. data/lib/active_record/relation/batches.rb +198 -63
  194. data/lib/active_record/relation/calculations.rb +293 -108
  195. data/lib/active_record/relation/delegation.rb +31 -20
  196. data/lib/active_record/relation/finder_methods.rb +93 -18
  197. data/lib/active_record/relation/merger.rb +6 -6
  198. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  199. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  200. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  201. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  202. data/lib/active_record/relation/predicate_builder.rb +28 -16
  203. data/lib/active_record/relation/query_attribute.rb +25 -1
  204. data/lib/active_record/relation/query_methods.rb +625 -107
  205. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  206. data/lib/active_record/relation/spawn_methods.rb +5 -4
  207. data/lib/active_record/relation/where_clause.rb +7 -19
  208. data/lib/active_record/relation.rb +602 -96
  209. data/lib/active_record/result.rb +55 -52
  210. data/lib/active_record/runtime_registry.rb +63 -1
  211. data/lib/active_record/sanitization.rb +76 -30
  212. data/lib/active_record/schema.rb +39 -23
  213. data/lib/active_record/schema_dumper.rb +82 -30
  214. data/lib/active_record/schema_migration.rb +75 -24
  215. data/lib/active_record/scoping/default.rb +20 -12
  216. data/lib/active_record/scoping/named.rb +3 -2
  217. data/lib/active_record/scoping.rb +2 -1
  218. data/lib/active_record/secure_password.rb +60 -0
  219. data/lib/active_record/secure_token.rb +21 -3
  220. data/lib/active_record/serialization.rb +5 -0
  221. data/lib/active_record/signed_id.rb +29 -8
  222. data/lib/active_record/statement_cache.rb +7 -7
  223. data/lib/active_record/store.rb +16 -11
  224. data/lib/active_record/suppressor.rb +3 -1
  225. data/lib/active_record/table_metadata.rb +7 -3
  226. data/lib/active_record/tasks/database_tasks.rb +191 -121
  227. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  228. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  229. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  230. data/lib/active_record/test_fixtures.rb +174 -152
  231. data/lib/active_record/testing/query_assertions.rb +121 -0
  232. data/lib/active_record/timestamp.rb +31 -17
  233. data/lib/active_record/token_for.rb +123 -0
  234. data/lib/active_record/touch_later.rb +12 -7
  235. data/lib/active_record/transaction.rb +132 -0
  236. data/lib/active_record/transactions.rb +109 -27
  237. data/lib/active_record/translation.rb +1 -3
  238. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  239. data/lib/active_record/type/internal/timezone.rb +7 -2
  240. data/lib/active_record/type/serialized.rb +9 -7
  241. data/lib/active_record/type/time.rb +4 -0
  242. data/lib/active_record/type_caster/connection.rb +4 -4
  243. data/lib/active_record/validations/absence.rb +1 -1
  244. data/lib/active_record/validations/associated.rb +12 -6
  245. data/lib/active_record/validations/numericality.rb +5 -4
  246. data/lib/active_record/validations/presence.rb +5 -28
  247. data/lib/active_record/validations/uniqueness.rb +63 -14
  248. data/lib/active_record/validations.rb +12 -5
  249. data/lib/active_record/version.rb +1 -1
  250. data/lib/active_record.rb +266 -30
  251. data/lib/arel/alias_predication.rb +1 -1
  252. data/lib/arel/collectors/bind.rb +2 -0
  253. data/lib/arel/collectors/composite.rb +7 -0
  254. data/lib/arel/collectors/sql_string.rb +1 -1
  255. data/lib/arel/collectors/substitute_binds.rb +1 -1
  256. data/lib/arel/errors.rb +10 -0
  257. data/lib/arel/factory_methods.rb +4 -0
  258. data/lib/arel/filter_predications.rb +1 -1
  259. data/lib/arel/nodes/binary.rb +6 -7
  260. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  261. data/lib/arel/nodes/cte.rb +36 -0
  262. data/lib/arel/nodes/filter.rb +1 -1
  263. data/lib/arel/nodes/fragments.rb +35 -0
  264. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  265. data/lib/arel/nodes/leading_join.rb +8 -0
  266. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  267. data/lib/arel/nodes/node.rb +115 -5
  268. data/lib/arel/nodes/sql_literal.rb +13 -0
  269. data/lib/arel/nodes/table_alias.rb +4 -0
  270. data/lib/arel/nodes.rb +6 -2
  271. data/lib/arel/predications.rb +3 -1
  272. data/lib/arel/select_manager.rb +1 -1
  273. data/lib/arel/table.rb +9 -5
  274. data/lib/arel/tree_manager.rb +8 -3
  275. data/lib/arel/update_manager.rb +2 -1
  276. data/lib/arel/visitors/dot.rb +1 -0
  277. data/lib/arel/visitors/mysql.rb +17 -5
  278. data/lib/arel/visitors/postgresql.rb +1 -12
  279. data/lib/arel/visitors/to_sql.rb +112 -34
  280. data/lib/arel/visitors/visitor.rb +2 -2
  281. data/lib/arel.rb +21 -3
  282. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  283. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  284. data/lib/rails/generators/active_record/migration.rb +3 -1
  285. data/lib/rails/generators/active_record/model/USAGE +113 -0
  286. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  287. metadata +59 -17
  288. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  289. 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,
@@ -24,38 +29,6 @@ module ActiveRecord
24
29
  # The serialization format may be YAML, JSON, or any custom format using a
25
30
  # custom coder class.
26
31
  #
27
- # === Serialization formats
28
- #
29
- # serialize attr_name [, class_name_or_coder]
30
- #
31
- # | | database storage |
32
- # class_name_or_coder | attribute read/write type | serialized | NULL |
33
- # ---------------------+---------------------------+------------+--------+
34
- # <not given> | any value that supports | YAML | |
35
- # | .to_yaml | | |
36
- # | | | |
37
- # Array | Array ** | YAML | [] |
38
- # | | | |
39
- # Hash | Hash ** | YAML | {} |
40
- # | | | |
41
- # JSON | any value that supports | JSON | |
42
- # | .to_json | | |
43
- # | | | |
44
- # <custom coder class> | any value supported by | custom | custom |
45
- # | the custom coder class | | |
46
- #
47
- # ** If +class_name_or_coder+ is +Array+ or +Hash+, values retrieved will
48
- # always be of that type, and any value assigned must be of that type or
49
- # +SerializationTypeMismatch+ will be raised.
50
- #
51
- # ==== Custom coders
52
- # A custom coder class or module may be given. This must have +self.load+
53
- # and +self.dump+ class/module methods. <tt>self.dump(object)</tt> will be called
54
- # to serialize an object and should return the serialized value to be
55
- # stored in the database (+nil+ to store as +NULL+). <tt>self.load(string)</tt>
56
- # will be called to reverse the process and load (unserialize) from the
57
- # database.
58
- #
59
32
  # Keep in mind that database adapters handle certain serialization tasks
60
33
  # for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be
61
34
  # converted between JSON object/array syntax and Ruby +Hash+ or +Array+
@@ -67,81 +40,191 @@ module ActiveRecord
67
40
  #
68
41
  # ==== Parameters
69
42
  #
70
- # * +attr_name+ - The field name that should be serialized.
71
- # * +class_name_or_coder+ - Optional, may be be +Array+ or +Hash+ or
72
- # +JSON+ or a custom coder class or module which responds to +.load+
73
- # and +.dump+. See table above.
43
+ # * +attr_name+ - The name of the attribute to serialize.
44
+ # * +coder+ The serializer implementation to use, e.g. +JSON+.
45
+ # * The attribute value will be serialized
46
+ # using the coder's <tt>dump(value)</tt> method, and will be
47
+ # deserialized using the coder's <tt>load(string)</tt> method. The
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.
74
57
  #
75
58
  # ==== Options
76
59
  #
77
- # +default+ The default value to use when no value is provided. If this option
78
- # is not passed, the previous default value (if any) will be used.
79
- # Otherwise, the default will be +nil+.
60
+ # * +:default+ - The default value to use when no value is provided. If
61
+ # this option is not passed, the previous default value (if any) will
62
+ # be used. Otherwise, the default will be +nil+.
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.
80
91
  #
81
- # ==== Example
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
82
115
  #
83
- # # Serialize a preferences attribute using YAML coder.
84
116
  # class User < ActiveRecord::Base
85
- # serialize :preferences
117
+ # serialize :address, coder: Address
86
118
  # end
87
119
  #
88
- # # Serialize preferences using JSON as coder.
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
+ #
135
+ # ==== Examples
136
+ #
137
+ # ===== Serialize the +preferences+ attribute using YAML
138
+ #
139
+ # class User < ActiveRecord::Base
140
+ # serialize :preferences, coder: YAML
141
+ # end
142
+ #
143
+ # ===== Serialize the +preferences+ attribute using JSON
144
+ #
145
+ # class User < ActiveRecord::Base
146
+ # serialize :preferences, coder: JSON
147
+ # end
148
+ #
149
+ # ===== Serialize the +preferences+ +Hash+ using YAML
150
+ #
89
151
  # class User < ActiveRecord::Base
90
- # serialize :preferences, JSON
152
+ # serialize :preferences, type: Hash, coder: YAML
91
153
  # end
92
154
  #
93
- # # Serialize preferences as Hash using YAML coder.
155
+ # ===== Serializes +preferences+ to YAML, permitting select classes
156
+ #
94
157
  # class User < ActiveRecord::Base
95
- # serialize :preferences, Hash
158
+ # serialize :preferences, coder: YAML, yaml: { permitted_classes: [Symbol, Time] }
96
159
  # end
97
160
  #
98
- # # Serialize preferences using a custom coder.
161
+ # ===== Serialize the +preferences+ attribute using a custom coder
162
+ #
99
163
  # class Rot13JSON
100
164
  # def self.rot13(string)
101
165
  # string.tr("a-zA-Z", "n-za-mN-ZA-M")
102
166
  # end
103
167
  #
104
- # # returns serialized string that will be stored in the database
105
- # def self.dump(object)
106
- # ActiveSupport::JSON.encode(object).rot13
168
+ # # Serializes an attribute value to a string that will be stored in the database.
169
+ # def self.dump(value)
170
+ # rot13(ActiveSupport::JSON.dump(value))
107
171
  # end
108
172
  #
109
- # # reverses the above, turning the serialized string from the database
110
- # # back into its original value
173
+ # # Deserializes a string from the database to an attribute value.
111
174
  # def self.load(string)
112
- # ActiveSupport::JSON.decode(string.rot13)
175
+ # ActiveSupport::JSON.load(rot13(string))
113
176
  # end
114
177
  # end
115
178
  #
116
179
  # class User < ActiveRecord::Base
117
- # serialize :preferences, Rot13JSON
180
+ # serialize :preferences, coder: Rot13JSON
118
181
  # end
119
- def serialize(attr_name, class_name_or_coder = Object, **options)
120
- # When ::JSON is used, force it to go through the Active Support JSON encoder
121
- # to ensure special objects (e.g. Active Record models) are dumped correctly
122
- # using the #as_json hook.
123
- coder = if class_name_or_coder == ::JSON
124
- Coders::JSON
125
- elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
126
- class_name_or_coder
127
- else
128
- Coders::YAMLColumn.new(attr_name, class_name_or_coder)
182
+ #
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
129
191
  end
130
192
 
131
- attribute(attr_name, **options) do |cast_type|
132
- 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)
133
199
  raise ColumnNotSerializableError.new(attr_name, cast_type)
134
200
  end
135
201
 
136
202
  cast_type = cast_type.subtype if Type::Serialized === cast_type
137
- Type::Serialized.new(cast_type, coder)
203
+ Type::Serialized.new(cast_type, column_serializer)
138
204
  end
139
205
  end
140
206
 
141
207
  private
142
- def type_incompatible_with_serialize?(type, class_name)
143
- type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON ||
144
- 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
145
228
  end
146
229
  end
147
230
  end
@@ -69,14 +69,15 @@ module ActiveRecord
69
69
  end
70
70
 
71
71
  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)
72
+ private
73
+ def hook_attribute_type(name, cast_type)
74
+ if create_time_zone_conversion_attribute?(name, cast_type)
75
+ cast_type = TimeZoneConverter.new(cast_type)
76
+ end
77
+
78
+ super
75
79
  end
76
- super
77
- end
78
80
 
79
- private
80
81
  def create_time_zone_conversion_attribute?(name, cast_type)
81
82
  enabled_for_column = time_zone_aware_attributes &&
82
83
  !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
67
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
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
 
@@ -97,7 +170,7 @@ module ActiveRecord
97
170
  super
98
171
  else
99
172
  # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
100
- # defines its own attribute method, then we don't want to overwrite that.
173
+ # defines its own attribute method, then we don't want to override that.
101
174
  defined = method_defined_within?(method_name, superclass, Base) &&
102
175
  ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
103
176
  defined || super
@@ -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,37 +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 Date.new(2004, 12, 12)). It raises
314
- # <tt>ActiveModel::MissingAttributeError</tt> 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+.
334
- # (Alias for the protected #write_attribute method).
418
+ # Updates the attribute identified by +attr_name+ using the specified
419
+ # +value+. The attribute value will be type cast upon being read.
335
420
  #
336
421
  # class Person < ActiveRecord::Base
337
422
  # end
338
423
  #
339
424
  # person = Person.new
340
- # person[:age] = '22'
341
- # person[:age] # => 22
342
- # person[:age].class # => Integer
425
+ # person[:date_of_birth] = "2004-12-12"
426
+ # person[:date_of_birth] # => Date.new(2004, 12, 12)
343
427
  def []=(attr_name, value)
344
428
  write_attribute(attr_name, value)
345
429
  end
@@ -360,10 +444,9 @@ module ActiveRecord
360
444
  # end
361
445
  #
362
446
  # private
363
- #
364
- # def print_accessed_fields
365
- # p @posts.first.accessed_fields
366
- # end
447
+ # def print_accessed_fields
448
+ # p @posts.first.accessed_fields
449
+ # end
367
450
  # end
368
451
  #
369
452
  # Which allows you to quickly change your code to:
@@ -378,9 +461,38 @@ module ActiveRecord
378
461
  end
379
462
 
380
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
+
381
494
  def attribute_method?(attr_name)
382
- # We check defined? because Syck calls respond_to? before actually calling initialize.
383
- defined?(@attributes) && @attributes.key?(attr_name)
495
+ @attributes&.key?(attr_name)
384
496
  end
385
497
 
386
498
  def attributes_with_values(attribute_names)
@@ -392,6 +504,7 @@ module ActiveRecord
392
504
  attribute_names &= self.class.column_names
393
505
  attribute_names.delete_if do |name|
394
506
  self.class.readonly_attribute?(name) ||
507
+ self.class.counter_cache_column?(name) ||
395
508
  column_for_attribute(name).virtual?
396
509
  end
397
510
  end
@@ -413,7 +526,7 @@ module ActiveRecord
413
526
  inspected_value = if value.is_a?(String) && value.length > 50
414
527
  "#{value[0, 50]}...".inspect
415
528
  elsif value.is_a?(Date) || value.is_a?(Time)
416
- %("#{value.to_formatted_s(:inspect)}")
529
+ %("#{value.to_fs(:inspect)}")
417
530
  else
418
531
  value.inspect
419
532
  end