activerecord 6.1.7 → 7.2.0

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 (332) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -1385
  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 +17 -14
  7. data/lib/active_record/association_relation.rb +2 -12
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +60 -21
  10. data/lib/active_record/associations/association_scope.rb +17 -12
  11. data/lib/active_record/associations/belongs_to_association.rb +37 -11
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
  13. data/lib/active_record/associations/builder/association.rb +11 -5
  14. data/lib/active_record/associations/builder/belongs_to.rb +41 -14
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +4 -4
  19. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  20. data/lib/active_record/associations/collection_association.rb +46 -36
  21. data/lib/active_record/associations/collection_proxy.rb +44 -16
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +10 -3
  25. data/lib/active_record/associations/has_many_association.rb +29 -19
  26. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  27. data/lib/active_record/associations/has_one_association.rb +20 -10
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  30. data/lib/active_record/associations/join_dependency.rb +23 -15
  31. data/lib/active_record/associations/nested_error.rb +47 -0
  32. data/lib/active_record/associations/preloader/association.rb +212 -53
  33. data/lib/active_record/associations/preloader/batch.rb +48 -0
  34. data/lib/active_record/associations/preloader/branch.rb +153 -0
  35. data/lib/active_record/associations/preloader/through_association.rb +50 -16
  36. data/lib/active_record/associations/preloader.rb +50 -121
  37. data/lib/active_record/associations/singular_association.rb +15 -3
  38. data/lib/active_record/associations/through_association.rb +25 -14
  39. data/lib/active_record/associations.rb +404 -509
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +2 -14
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  43. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  44. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  45. data/lib/active_record/attribute_methods/primary_key.rb +47 -27
  46. data/lib/active_record/attribute_methods/query.rb +31 -19
  47. data/lib/active_record/attribute_methods/read.rb +14 -11
  48. data/lib/active_record/attribute_methods/serialization.rb +174 -37
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -9
  50. data/lib/active_record/attribute_methods/write.rb +12 -15
  51. data/lib/active_record/attribute_methods.rb +164 -52
  52. data/lib/active_record/attributes.rb +51 -49
  53. data/lib/active_record/autosave_association.rb +74 -57
  54. data/lib/active_record/base.rb +27 -5
  55. data/lib/active_record/callbacks.rb +18 -34
  56. data/lib/active_record/coders/column_serializer.rb +61 -0
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +70 -46
  59. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +327 -612
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -64
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +377 -142
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +345 -166
  75. data/lib/active_record/connection_adapters/column.rb +13 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
  78. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
  79. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  81. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  82. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  83. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
  85. data/lib/active_record/connection_adapters/pool_config.rb +26 -16
  86. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  87. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  88. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
  89. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  94. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  100. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
  103. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  104. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  105. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
  106. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  107. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +401 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +518 -251
  110. data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
  111. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  112. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
  113. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
  114. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
  119. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  120. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  121. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  122. data/lib/active_record/connection_adapters.rb +130 -6
  123. data/lib/active_record/connection_handling.rb +132 -146
  124. data/lib/active_record/core.rb +276 -251
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -3
  127. data/lib/active_record/database_configurations/database_config.rb +34 -10
  128. data/lib/active_record/database_configurations/hash_config.rb +107 -31
  129. data/lib/active_record/database_configurations/url_config.rb +38 -13
  130. data/lib/active_record/database_configurations.rb +96 -60
  131. data/lib/active_record/delegated_type.rb +90 -20
  132. data/lib/active_record/deprecator.rb +7 -0
  133. data/lib/active_record/destroy_association_async_job.rb +4 -2
  134. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  135. data/lib/active_record/dynamic_matchers.rb +3 -3
  136. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +68 -0
  140. data/lib/active_record/encryption/configurable.rb +60 -0
  141. data/lib/active_record/encryption/context.rb +42 -0
  142. data/lib/active_record/encryption/contexts.rb +76 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +170 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +53 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  159. data/lib/active_record/encryption/message_serializer.rb +96 -0
  160. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  161. data/lib/active_record/encryption/properties.rb +76 -0
  162. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  163. data/lib/active_record/encryption/scheme.rb +100 -0
  164. data/lib/active_record/encryption.rb +56 -0
  165. data/lib/active_record/enum.rb +163 -63
  166. data/lib/active_record/errors.rb +210 -27
  167. data/lib/active_record/explain.rb +21 -12
  168. data/lib/active_record/explain_registry.rb +11 -6
  169. data/lib/active_record/explain_subscriber.rb +1 -1
  170. data/lib/active_record/fixture_set/file.rb +15 -1
  171. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  172. data/lib/active_record/fixture_set/render_context.rb +2 -0
  173. data/lib/active_record/fixture_set/table_row.rb +70 -14
  174. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  175. data/lib/active_record/fixtures.rb +179 -112
  176. data/lib/active_record/future_result.rb +178 -0
  177. data/lib/active_record/gem_version.rb +4 -4
  178. data/lib/active_record/inheritance.rb +85 -31
  179. data/lib/active_record/insert_all.rb +148 -32
  180. data/lib/active_record/integration.rb +14 -10
  181. data/lib/active_record/internal_metadata.rb +123 -23
  182. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  183. data/lib/active_record/locking/optimistic.rb +43 -27
  184. data/lib/active_record/locking/pessimistic.rb +15 -6
  185. data/lib/active_record/log_subscriber.rb +41 -29
  186. data/lib/active_record/marshalling.rb +56 -0
  187. data/lib/active_record/message_pack.rb +124 -0
  188. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  189. data/lib/active_record/middleware/database_selector.rb +23 -13
  190. data/lib/active_record/middleware/shard_selector.rb +62 -0
  191. data/lib/active_record/migration/command_recorder.rb +113 -16
  192. data/lib/active_record/migration/compatibility.rb +235 -46
  193. data/lib/active_record/migration/default_strategy.rb +22 -0
  194. data/lib/active_record/migration/execution_strategy.rb +19 -0
  195. data/lib/active_record/migration/join_table.rb +1 -1
  196. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  197. data/lib/active_record/migration.rb +374 -177
  198. data/lib/active_record/model_schema.rb +143 -159
  199. data/lib/active_record/nested_attributes.rb +48 -21
  200. data/lib/active_record/no_touching.rb +3 -3
  201. data/lib/active_record/normalization.rb +163 -0
  202. data/lib/active_record/persistence.rb +282 -283
  203. data/lib/active_record/promise.rb +84 -0
  204. data/lib/active_record/query_cache.rb +19 -25
  205. data/lib/active_record/query_logs.rb +189 -0
  206. data/lib/active_record/query_logs_formatter.rb +41 -0
  207. data/lib/active_record/querying.rb +44 -9
  208. data/lib/active_record/railtie.rb +234 -71
  209. data/lib/active_record/railties/controller_runtime.rb +25 -11
  210. data/lib/active_record/railties/databases.rake +189 -256
  211. data/lib/active_record/railties/job_runtime.rb +23 -0
  212. data/lib/active_record/readonly_attributes.rb +41 -3
  213. data/lib/active_record/reflection.rb +325 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +198 -63
  216. data/lib/active_record/relation/calculations.rb +300 -111
  217. data/lib/active_record/relation/delegation.rb +33 -22
  218. data/lib/active_record/relation/finder_methods.rb +123 -52
  219. data/lib/active_record/relation/merger.rb +26 -19
  220. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  221. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  222. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  223. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  224. data/lib/active_record/relation/predicate_builder.rb +29 -22
  225. data/lib/active_record/relation/query_attribute.rb +30 -12
  226. data/lib/active_record/relation/query_methods.rb +842 -150
  227. data/lib/active_record/relation/record_fetch_warning.rb +10 -9
  228. data/lib/active_record/relation/spawn_methods.rb +7 -6
  229. data/lib/active_record/relation/where_clause.rb +15 -36
  230. data/lib/active_record/relation.rb +736 -145
  231. data/lib/active_record/result.rb +67 -54
  232. data/lib/active_record/runtime_registry.rb +71 -13
  233. data/lib/active_record/sanitization.rb +84 -34
  234. data/lib/active_record/schema.rb +39 -23
  235. data/lib/active_record/schema_dumper.rb +90 -31
  236. data/lib/active_record/schema_migration.rb +74 -23
  237. data/lib/active_record/scoping/default.rb +72 -15
  238. data/lib/active_record/scoping/named.rb +5 -13
  239. data/lib/active_record/scoping.rb +65 -34
  240. data/lib/active_record/secure_password.rb +60 -0
  241. data/lib/active_record/secure_token.rb +21 -3
  242. data/lib/active_record/serialization.rb +6 -1
  243. data/lib/active_record/signed_id.rb +30 -9
  244. data/lib/active_record/statement_cache.rb +7 -7
  245. data/lib/active_record/store.rb +10 -10
  246. data/lib/active_record/suppressor.rb +13 -15
  247. data/lib/active_record/table_metadata.rb +7 -3
  248. data/lib/active_record/tasks/database_tasks.rb +277 -149
  249. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  250. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  251. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  252. data/lib/active_record/test_databases.rb +1 -1
  253. data/lib/active_record/test_fixtures.rb +173 -155
  254. data/lib/active_record/testing/query_assertions.rb +121 -0
  255. data/lib/active_record/timestamp.rb +32 -19
  256. data/lib/active_record/token_for.rb +123 -0
  257. data/lib/active_record/touch_later.rb +12 -7
  258. data/lib/active_record/transaction.rb +132 -0
  259. data/lib/active_record/transactions.rb +118 -41
  260. data/lib/active_record/translation.rb +3 -5
  261. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  262. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  263. data/lib/active_record/type/internal/timezone.rb +7 -2
  264. data/lib/active_record/type/serialized.rb +9 -7
  265. data/lib/active_record/type/time.rb +4 -0
  266. data/lib/active_record/type/type_map.rb +17 -20
  267. data/lib/active_record/type.rb +1 -2
  268. data/lib/active_record/type_caster/connection.rb +4 -4
  269. data/lib/active_record/validations/absence.rb +1 -1
  270. data/lib/active_record/validations/associated.rb +13 -7
  271. data/lib/active_record/validations/numericality.rb +5 -4
  272. data/lib/active_record/validations/presence.rb +5 -28
  273. data/lib/active_record/validations/uniqueness.rb +64 -15
  274. data/lib/active_record/validations.rb +12 -5
  275. data/lib/active_record/version.rb +1 -1
  276. data/lib/active_record.rb +444 -32
  277. data/lib/arel/alias_predication.rb +1 -1
  278. data/lib/arel/attributes/attribute.rb +0 -8
  279. data/lib/arel/collectors/bind.rb +2 -0
  280. data/lib/arel/collectors/composite.rb +7 -0
  281. data/lib/arel/collectors/sql_string.rb +1 -1
  282. data/lib/arel/collectors/substitute_binds.rb +1 -1
  283. data/lib/arel/crud.rb +28 -22
  284. data/lib/arel/delete_manager.rb +18 -4
  285. data/lib/arel/errors.rb +10 -0
  286. data/lib/arel/factory_methods.rb +4 -0
  287. data/lib/arel/filter_predications.rb +9 -0
  288. data/lib/arel/insert_manager.rb +2 -3
  289. data/lib/arel/nodes/binary.rb +6 -7
  290. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  291. data/lib/arel/nodes/casted.rb +1 -1
  292. data/lib/arel/nodes/cte.rb +36 -0
  293. data/lib/arel/nodes/delete_statement.rb +12 -13
  294. data/lib/arel/nodes/filter.rb +10 -0
  295. data/lib/arel/nodes/fragments.rb +35 -0
  296. data/lib/arel/nodes/function.rb +1 -0
  297. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  298. data/lib/arel/nodes/insert_statement.rb +2 -2
  299. data/lib/arel/nodes/leading_join.rb +8 -0
  300. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  301. data/lib/arel/nodes/node.rb +115 -5
  302. data/lib/arel/nodes/select_core.rb +2 -2
  303. data/lib/arel/nodes/select_statement.rb +2 -2
  304. data/lib/arel/nodes/sql_literal.rb +13 -0
  305. data/lib/arel/nodes/table_alias.rb +4 -0
  306. data/lib/arel/nodes/update_statement.rb +8 -3
  307. data/lib/arel/nodes.rb +7 -2
  308. data/lib/arel/predications.rb +14 -4
  309. data/lib/arel/select_manager.rb +11 -5
  310. data/lib/arel/table.rb +9 -6
  311. data/lib/arel/tree_manager.rb +8 -15
  312. data/lib/arel/update_manager.rb +20 -5
  313. data/lib/arel/visitors/dot.rb +81 -90
  314. data/lib/arel/visitors/mysql.rb +23 -5
  315. data/lib/arel/visitors/postgresql.rb +1 -22
  316. data/lib/arel/visitors/to_sql.rb +170 -36
  317. data/lib/arel/visitors/visitor.rb +2 -2
  318. data/lib/arel.rb +23 -4
  319. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  320. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  321. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  322. data/lib/rails/generators/active_record/migration.rb +3 -1
  323. data/lib/rails/generators/active_record/model/USAGE +113 -0
  324. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  325. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  326. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  328. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  329. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  330. metadata +100 -14
  331. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  332. data/lib/active_record/null_relation.rb +0 -67
@@ -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,16 +16,18 @@ 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
- # If you have an attribute that needs to be saved to the database as an
20
- # object, and retrieved as the same object, then specify the name of that
21
- # attribute using this method and it will be handled automatically. The
22
- # serialization is done through YAML. If +class_name+ is specified, the
23
- # serialized object must be of that class on assignment and retrieval.
24
- # Otherwise SerializationTypeMismatch will be raised.
24
+ # If you have an attribute that needs to be saved to the database as a
25
+ # serialized object, and retrieved by deserializing into the same object,
26
+ # then specify the name of that attribute using this method and serialization
27
+ # will be handled automatically.
25
28
  #
26
- # Empty objects as <tt>{}</tt>, in the case of +Hash+, or <tt>[]</tt>, in the case of
27
- # +Array+, will always be persisted as null.
29
+ # The serialization format may be YAML, JSON, or any custom format using a
30
+ # custom coder class.
28
31
  #
29
32
  # Keep in mind that database adapters handle certain serialization tasks
30
33
  # for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be
@@ -37,57 +40,191 @@ module ActiveRecord
37
40
  #
38
41
  # ==== Parameters
39
42
  #
40
- # * +attr_name+ - The field name that should be serialized.
41
- # * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+
42
- # or a class name that the object type should be equal to.
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.
43
57
  #
44
58
  # ==== Options
45
59
  #
46
- # +default+ The default value to use when no value is provided. If this option
47
- # is not passed, the previous default value (if any) will be used.
48
- # 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.
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
49
97
  #
50
- # ==== Example
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
51
115
  #
52
- # # Serialize a preferences attribute.
53
116
  # class User < ActiveRecord::Base
54
- # serialize :preferences
117
+ # serialize :address, coder: Address
55
118
  # end
56
119
  #
57
- # # 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
+ #
58
139
  # class User < ActiveRecord::Base
59
- # serialize :preferences, JSON
140
+ # serialize :preferences, coder: YAML
60
141
  # end
61
142
  #
62
- # # Serialize preferences as Hash using YAML coder.
143
+ # ===== Serialize the +preferences+ attribute using JSON
144
+ #
63
145
  # class User < ActiveRecord::Base
64
- # serialize :preferences, Hash
146
+ # serialize :preferences, coder: JSON
65
147
  # end
66
- def serialize(attr_name, class_name_or_coder = Object, **options)
67
- # When ::JSON is used, force it to go through the Active Support JSON encoder
68
- # to ensure special objects (e.g. Active Record models) are dumped correctly
69
- # using the #as_json hook.
70
- coder = if class_name_or_coder == ::JSON
71
- Coders::JSON
72
- elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
73
- class_name_or_coder
74
- else
75
- Coders::YAMLColumn.new(attr_name, class_name_or_coder)
148
+ #
149
+ # ===== Serialize the +preferences+ +Hash+ using YAML
150
+ #
151
+ # class User < ActiveRecord::Base
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] }
159
+ # end
160
+ #
161
+ # ===== Serialize the +preferences+ attribute using a custom coder
162
+ #
163
+ # class Rot13JSON
164
+ # def self.rot13(string)
165
+ # string.tr("a-zA-Z", "n-za-mN-ZA-M")
166
+ # end
167
+ #
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))
171
+ # end
172
+ #
173
+ # # Deserializes a string from the database to an attribute value.
174
+ # def self.load(string)
175
+ # ActiveSupport::JSON.load(rot13(string))
176
+ # end
177
+ # end
178
+ #
179
+ # class User < ActiveRecord::Base
180
+ # serialize :preferences, coder: Rot13JSON
181
+ # end
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
76
191
  end
77
192
 
78
- decorate_attribute_type(attr_name.to_s, **options) do |cast_type|
79
- 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)
80
199
  raise ColumnNotSerializableError.new(attr_name, cast_type)
81
200
  end
82
201
 
83
- Type::Serialized.new(cast_type, coder)
202
+ cast_type = cast_type.subtype if Type::Serialized === cast_type
203
+ Type::Serialized.new(cast_type, column_serializer)
84
204
  end
85
205
  end
86
206
 
87
207
  private
88
- def type_incompatible_with_serialize?(type, class_name)
89
- type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON ||
90
- 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
91
228
  end
92
229
  end
93
230
  end
@@ -25,6 +25,8 @@ module ActiveRecord
25
25
  rescue ArgumentError
26
26
  nil
27
27
  end
28
+ elsif value.respond_to?(:infinite?) && value.infinite?
29
+ value
28
30
  else
29
31
  map_avoiding_infinite_recursion(super) { |v| cast(v) }
30
32
  end
@@ -36,7 +38,7 @@ module ActiveRecord
36
38
 
37
39
  if value.acts_like?(:time)
38
40
  value.in_time_zone
39
- elsif value.is_a?(::Float)
41
+ elsif value.respond_to?(:infinite?) && value.infinite?
40
42
  value
41
43
  else
42
44
  map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
@@ -61,21 +63,21 @@ module ActiveRecord
61
63
  extend ActiveSupport::Concern
62
64
 
63
65
  included do
64
- mattr_accessor :time_zone_aware_attributes, instance_writer: false, default: false
65
-
66
+ class_attribute :time_zone_aware_attributes, instance_writer: false, default: false
66
67
  class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: []
67
68
  class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ]
68
69
  end
69
70
 
70
71
  module ClassMethods # :nodoc:
71
- def define_attribute(name, cast_type, **)
72
- if create_time_zone_conversion_attribute?(name, cast_type)
73
- 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
74
79
  end
75
- super
76
- end
77
80
 
78
- private
79
81
  def create_time_zone_conversion_attribute?(name, cast_type)
80
82
  enabled_for_column = time_zone_aware_attributes &&
81
83
  !skip_time_zone_conversion_for_attributes.include?(name.to_sym)
@@ -2,30 +2,32 @@
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
 
8
9
  included do
9
- attribute_method_suffix "="
10
+ attribute_method_suffix "=", parameters: "value"
10
11
  end
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 <<
19
- "def #{temp_method_name}(value)" <<
20
- " _write_attribute(#{attr_name_expr}, value)" <<
21
- "end"
19
+ owner.define_cached_method(temp_method_name, as: "#{as}=", namespace: :active_record) do |batch|
20
+ batch <<
21
+ "def #{temp_method_name}(value)" <<
22
+ " _write_attribute(#{attr_name_expr}, value)" <<
23
+ "end"
24
+ end
22
25
  end
23
26
  end
24
27
  end
25
28
 
26
- # Updates the attribute identified by <tt>attr_name</tt> with the
27
- # specified +value+. Empty strings for Integer and Float columns are
28
- # 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.
29
31
  def write_attribute(attr_name, value)
30
32
  name = attr_name.to_s
31
33
  name = self.class.attribute_aliases[name] || name
@@ -42,11 +44,6 @@ module ActiveRecord
42
44
 
43
45
  alias :attribute= :_write_attribute
44
46
  private :attribute=
45
-
46
- private
47
- def write_attribute_without_type_cast(attr_name, value)
48
- @attributes.write_cast_value(attr_name, value)
49
- end
50
47
  end
51
48
  end
52
49
  end