activerecord 6.1.7 → 7.1.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 (307) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1516 -1019
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +17 -18
  5. data/lib/active_record/aggregations.rb +17 -14
  6. data/lib/active_record/association_relation.rb +1 -11
  7. data/lib/active_record/associations/association.rb +50 -19
  8. data/lib/active_record/associations/association_scope.rb +17 -12
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +11 -5
  12. data/lib/active_record/associations/builder/belongs_to.rb +40 -14
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  15. data/lib/active_record/associations/builder/has_many.rb +3 -2
  16. data/lib/active_record/associations/builder/has_one.rb +2 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  18. data/lib/active_record/associations/collection_association.rb +35 -31
  19. data/lib/active_record/associations/collection_proxy.rb +30 -15
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  21. data/lib/active_record/associations/foreign_association.rb +10 -3
  22. data/lib/active_record/associations/has_many_association.rb +28 -18
  23. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  24. data/lib/active_record/associations/has_one_association.rb +20 -10
  25. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency.rb +26 -16
  27. data/lib/active_record/associations/preloader/association.rb +207 -52
  28. data/lib/active_record/associations/preloader/batch.rb +48 -0
  29. data/lib/active_record/associations/preloader/branch.rb +147 -0
  30. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  31. data/lib/active_record/associations/preloader.rb +50 -121
  32. data/lib/active_record/associations/singular_association.rb +9 -3
  33. data/lib/active_record/associations/through_association.rb +25 -14
  34. data/lib/active_record/associations.rb +423 -289
  35. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  36. data/lib/active_record/attribute_assignment.rb +1 -3
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +61 -14
  39. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  40. data/lib/active_record/attribute_methods/query.rb +31 -19
  41. data/lib/active_record/attribute_methods/read.rb +25 -10
  42. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  44. data/lib/active_record/attribute_methods/write.rb +10 -13
  45. data/lib/active_record/attribute_methods.rb +121 -40
  46. data/lib/active_record/attributes.rb +27 -38
  47. data/lib/active_record/autosave_association.rb +61 -30
  48. data/lib/active_record/base.rb +25 -2
  49. data/lib/active_record/callbacks.rb +18 -34
  50. data/lib/active_record/coders/column_serializer.rb +61 -0
  51. data/lib/active_record/coders/json.rb +1 -1
  52. data/lib/active_record/coders/yaml_column.rb +70 -46
  53. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  54. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +96 -590
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -51
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +77 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +360 -136
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +622 -149
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +285 -156
  69. data/lib/active_record/connection_adapters/column.rb +13 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +104 -53
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +18 -1
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -52
  83. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  88. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  91. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +381 -69
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +492 -230
  100. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +65 -53
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  104. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -102
  107. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  108. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  109. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  110. data/lib/active_record/connection_adapters.rb +9 -6
  111. data/lib/active_record/connection_handling.rb +107 -136
  112. data/lib/active_record/core.rb +194 -224
  113. data/lib/active_record/counter_cache.rb +46 -25
  114. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  115. data/lib/active_record/database_configurations/database_config.rb +21 -12
  116. data/lib/active_record/database_configurations/hash_config.rb +84 -16
  117. data/lib/active_record/database_configurations/url_config.rb +18 -12
  118. data/lib/active_record/database_configurations.rb +95 -59
  119. data/lib/active_record/delegated_type.rb +61 -15
  120. data/lib/active_record/deprecator.rb +7 -0
  121. data/lib/active_record/destroy_association_async_job.rb +3 -1
  122. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  123. data/lib/active_record/dynamic_matchers.rb +1 -1
  124. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  125. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  126. data/lib/active_record/encryption/cipher.rb +53 -0
  127. data/lib/active_record/encryption/config.rb +68 -0
  128. data/lib/active_record/encryption/configurable.rb +60 -0
  129. data/lib/active_record/encryption/context.rb +42 -0
  130. data/lib/active_record/encryption/contexts.rb +76 -0
  131. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  132. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  133. data/lib/active_record/encryption/encryptable_record.rb +224 -0
  134. data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -0
  135. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  136. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  137. data/lib/active_record/encryption/encryptor.rb +155 -0
  138. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  139. data/lib/active_record/encryption/errors.rb +15 -0
  140. data/lib/active_record/encryption/extended_deterministic_queries.rb +172 -0
  141. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  142. data/lib/active_record/encryption/key.rb +28 -0
  143. data/lib/active_record/encryption/key_generator.rb +53 -0
  144. data/lib/active_record/encryption/key_provider.rb +46 -0
  145. data/lib/active_record/encryption/message.rb +33 -0
  146. data/lib/active_record/encryption/message_serializer.rb +92 -0
  147. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  148. data/lib/active_record/encryption/properties.rb +76 -0
  149. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  150. data/lib/active_record/encryption/scheme.rb +96 -0
  151. data/lib/active_record/encryption.rb +56 -0
  152. data/lib/active_record/enum.rb +156 -62
  153. data/lib/active_record/errors.rb +171 -15
  154. data/lib/active_record/explain.rb +23 -3
  155. data/lib/active_record/explain_registry.rb +11 -6
  156. data/lib/active_record/explain_subscriber.rb +1 -1
  157. data/lib/active_record/fixture_set/file.rb +15 -1
  158. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  159. data/lib/active_record/fixture_set/render_context.rb +2 -0
  160. data/lib/active_record/fixture_set/table_row.rb +70 -14
  161. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  162. data/lib/active_record/fixtures.rb +131 -86
  163. data/lib/active_record/future_result.rb +164 -0
  164. data/lib/active_record/gem_version.rb +3 -3
  165. data/lib/active_record/inheritance.rb +81 -29
  166. data/lib/active_record/insert_all.rb +133 -20
  167. data/lib/active_record/integration.rb +11 -10
  168. data/lib/active_record/internal_metadata.rb +117 -33
  169. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  170. data/lib/active_record/locking/optimistic.rb +36 -21
  171. data/lib/active_record/locking/pessimistic.rb +15 -6
  172. data/lib/active_record/log_subscriber.rb +52 -19
  173. data/lib/active_record/marshalling.rb +56 -0
  174. data/lib/active_record/message_pack.rb +124 -0
  175. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  176. data/lib/active_record/middleware/database_selector.rb +23 -13
  177. data/lib/active_record/middleware/shard_selector.rb +62 -0
  178. data/lib/active_record/migration/command_recorder.rb +108 -13
  179. data/lib/active_record/migration/compatibility.rb +221 -48
  180. data/lib/active_record/migration/default_strategy.rb +23 -0
  181. data/lib/active_record/migration/execution_strategy.rb +19 -0
  182. data/lib/active_record/migration/join_table.rb +1 -1
  183. data/lib/active_record/migration.rb +355 -171
  184. data/lib/active_record/model_schema.rb +116 -97
  185. data/lib/active_record/nested_attributes.rb +36 -15
  186. data/lib/active_record/no_touching.rb +3 -3
  187. data/lib/active_record/normalization.rb +159 -0
  188. data/lib/active_record/persistence.rb +405 -85
  189. data/lib/active_record/promise.rb +84 -0
  190. data/lib/active_record/query_cache.rb +3 -21
  191. data/lib/active_record/query_logs.rb +174 -0
  192. data/lib/active_record/query_logs_formatter.rb +41 -0
  193. data/lib/active_record/querying.rb +29 -6
  194. data/lib/active_record/railtie.rb +219 -43
  195. data/lib/active_record/railties/controller_runtime.rb +13 -9
  196. data/lib/active_record/railties/databases.rake +185 -249
  197. data/lib/active_record/railties/job_runtime.rb +23 -0
  198. data/lib/active_record/readonly_attributes.rb +41 -3
  199. data/lib/active_record/reflection.rb +229 -80
  200. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  201. data/lib/active_record/relation/batches.rb +192 -63
  202. data/lib/active_record/relation/calculations.rb +211 -90
  203. data/lib/active_record/relation/delegation.rb +27 -13
  204. data/lib/active_record/relation/finder_methods.rb +108 -51
  205. data/lib/active_record/relation/merger.rb +22 -13
  206. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  207. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  208. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  209. data/lib/active_record/relation/predicate_builder.rb +27 -20
  210. data/lib/active_record/relation/query_attribute.rb +30 -12
  211. data/lib/active_record/relation/query_methods.rb +654 -127
  212. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  213. data/lib/active_record/relation/spawn_methods.rb +20 -3
  214. data/lib/active_record/relation/where_clause.rb +10 -19
  215. data/lib/active_record/relation.rb +262 -120
  216. data/lib/active_record/result.rb +37 -11
  217. data/lib/active_record/runtime_registry.rb +18 -13
  218. data/lib/active_record/sanitization.rb +65 -20
  219. data/lib/active_record/schema.rb +36 -22
  220. data/lib/active_record/schema_dumper.rb +73 -24
  221. data/lib/active_record/schema_migration.rb +68 -33
  222. data/lib/active_record/scoping/default.rb +72 -15
  223. data/lib/active_record/scoping/named.rb +5 -13
  224. data/lib/active_record/scoping.rb +65 -34
  225. data/lib/active_record/secure_password.rb +60 -0
  226. data/lib/active_record/secure_token.rb +21 -3
  227. data/lib/active_record/serialization.rb +6 -1
  228. data/lib/active_record/signed_id.rb +10 -8
  229. data/lib/active_record/store.rb +10 -10
  230. data/lib/active_record/suppressor.rb +13 -15
  231. data/lib/active_record/table_metadata.rb +16 -3
  232. data/lib/active_record/tasks/database_tasks.rb +225 -136
  233. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  234. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  235. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  236. data/lib/active_record/test_databases.rb +1 -1
  237. data/lib/active_record/test_fixtures.rb +116 -96
  238. data/lib/active_record/timestamp.rb +28 -17
  239. data/lib/active_record/token_for.rb +113 -0
  240. data/lib/active_record/touch_later.rb +11 -6
  241. data/lib/active_record/transactions.rb +48 -27
  242. data/lib/active_record/translation.rb +3 -3
  243. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  244. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  245. data/lib/active_record/type/internal/timezone.rb +7 -2
  246. data/lib/active_record/type/serialized.rb +9 -5
  247. data/lib/active_record/type/time.rb +4 -0
  248. data/lib/active_record/type/type_map.rb +17 -20
  249. data/lib/active_record/type.rb +1 -2
  250. data/lib/active_record/validations/absence.rb +1 -1
  251. data/lib/active_record/validations/associated.rb +4 -4
  252. data/lib/active_record/validations/numericality.rb +5 -4
  253. data/lib/active_record/validations/presence.rb +5 -28
  254. data/lib/active_record/validations/uniqueness.rb +51 -6
  255. data/lib/active_record/validations.rb +8 -4
  256. data/lib/active_record/version.rb +1 -1
  257. data/lib/active_record.rb +335 -32
  258. data/lib/arel/attributes/attribute.rb +0 -8
  259. data/lib/arel/crud.rb +28 -22
  260. data/lib/arel/delete_manager.rb +18 -4
  261. data/lib/arel/errors.rb +10 -0
  262. data/lib/arel/factory_methods.rb +4 -0
  263. data/lib/arel/filter_predications.rb +9 -0
  264. data/lib/arel/insert_manager.rb +2 -3
  265. data/lib/arel/nodes/and.rb +4 -0
  266. data/lib/arel/nodes/binary.rb +6 -1
  267. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  268. data/lib/arel/nodes/casted.rb +1 -1
  269. data/lib/arel/nodes/cte.rb +36 -0
  270. data/lib/arel/nodes/delete_statement.rb +12 -13
  271. data/lib/arel/nodes/filter.rb +10 -0
  272. data/lib/arel/nodes/fragments.rb +35 -0
  273. data/lib/arel/nodes/function.rb +1 -0
  274. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  275. data/lib/arel/nodes/insert_statement.rb +2 -2
  276. data/lib/arel/nodes/leading_join.rb +8 -0
  277. data/lib/arel/nodes/node.rb +111 -2
  278. data/lib/arel/nodes/select_core.rb +2 -2
  279. data/lib/arel/nodes/select_statement.rb +2 -2
  280. data/lib/arel/nodes/sql_literal.rb +6 -0
  281. data/lib/arel/nodes/table_alias.rb +4 -0
  282. data/lib/arel/nodes/update_statement.rb +8 -3
  283. data/lib/arel/nodes.rb +5 -0
  284. data/lib/arel/predications.rb +13 -3
  285. data/lib/arel/select_manager.rb +10 -4
  286. data/lib/arel/table.rb +9 -6
  287. data/lib/arel/tree_manager.rb +0 -12
  288. data/lib/arel/update_manager.rb +18 -4
  289. data/lib/arel/visitors/dot.rb +80 -90
  290. data/lib/arel/visitors/mysql.rb +16 -3
  291. data/lib/arel/visitors/postgresql.rb +0 -10
  292. data/lib/arel/visitors/to_sql.rb +139 -19
  293. data/lib/arel/visitors/visitor.rb +2 -2
  294. data/lib/arel.rb +18 -3
  295. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  296. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  297. data/lib/rails/generators/active_record/migration.rb +3 -1
  298. data/lib/rails/generators/active_record/model/USAGE +113 -0
  299. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  300. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  302. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  303. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  304. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  305. metadata +92 -13
  306. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  307. 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,211 @@ 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.
49
79
  #
50
- # ==== Example
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
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
+ #
139
+ # class User < ActiveRecord::Base
140
+ # serialize :preferences, coder: YAML
141
+ # end
142
+ #
143
+ # ===== Serialize the +preferences+ attribute using JSON
144
+ #
58
145
  # class User < ActiveRecord::Base
59
- # serialize :preferences, JSON
146
+ # serialize :preferences, coder: JSON
60
147
  # end
61
148
  #
62
- # # Serialize preferences as Hash using YAML coder.
149
+ # ===== Serialize the +preferences+ +Hash+ using YAML
150
+ #
63
151
  # class User < ActiveRecord::Base
64
- # serialize :preferences, Hash
152
+ # serialize :preferences, type: Hash, coder: YAML
65
153
  # 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)
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, class_name_or_coder = nil, coder: nil, type: Object, yaml: {}, **options)
184
+ unless class_name_or_coder.nil?
185
+ if class_name_or_coder == ::JSON || [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
186
+ ActiveRecord.deprecator.warn(<<~MSG)
187
+ Passing the coder as positional argument is deprecated and will be removed in Rails 7.2.
188
+
189
+ Please pass the coder as a keyword argument:
190
+
191
+ serialize #{attr_name.inspect}, coder: #{class_name_or_coder}
192
+ MSG
193
+ coder = class_name_or_coder
194
+ else
195
+ ActiveRecord.deprecator.warn(<<~MSG)
196
+ Passing the class as positional argument is deprecated and will be removed in Rails 7.2.
197
+
198
+ Please pass the class as a keyword argument:
199
+
200
+ serialize #{attr_name.inspect}, type: #{class_name_or_coder.name}
201
+ MSG
202
+ type = class_name_or_coder
203
+ end
76
204
  end
77
205
 
78
- decorate_attribute_type(attr_name.to_s, **options) do |cast_type|
79
- if type_incompatible_with_serialize?(cast_type, class_name_or_coder)
206
+ coder ||= default_column_serializer
207
+ unless coder
208
+ raise ArgumentError, <<~MSG.squish
209
+ missing keyword: :coder
210
+
211
+ If no default coder is configured, a coder must be provided to `serialize`.
212
+ MSG
213
+ end
214
+
215
+ column_serializer = build_column_serializer(attr_name, coder, type, yaml)
216
+
217
+ attribute(attr_name, **options) do |cast_type|
218
+ if type_incompatible_with_serialize?(cast_type, coder, type)
80
219
  raise ColumnNotSerializableError.new(attr_name, cast_type)
81
220
  end
82
221
 
83
- Type::Serialized.new(cast_type, coder)
222
+ cast_type = cast_type.subtype if Type::Serialized === cast_type
223
+ Type::Serialized.new(cast_type, column_serializer)
84
224
  end
85
225
  end
86
226
 
87
227
  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
228
+ def build_column_serializer(attr_name, coder, type, yaml = nil)
229
+ # When ::JSON is used, force it to go through the Active Support JSON encoder
230
+ # to ensure special objects (e.g. Active Record models) are dumped correctly
231
+ # using the #as_json hook.
232
+ coder = Coders::JSON if coder == ::JSON
233
+
234
+ if coder == ::YAML || coder == Coders::YAMLColumn
235
+ Coders::YAMLColumn.new(attr_name, type, **(yaml || {}))
236
+ elsif coder.respond_to?(:new) && !coder.respond_to?(:load)
237
+ coder.new(attr_name, type)
238
+ elsif type && type != Object
239
+ Coders::ColumnSerializer.new(attr_name, coder, type)
240
+ else
241
+ coder
242
+ end
243
+ end
244
+
245
+ def type_incompatible_with_serialize?(cast_type, coder, type)
246
+ cast_type.is_a?(ActiveRecord::Type::Json) && coder == ::JSON ||
247
+ cast_type.respond_to?(:type_cast_array, true) && type == ::Array
91
248
  end
92
249
  end
93
250
  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,8 +63,7 @@ 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
@@ -2,11 +2,12 @@
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:
@@ -15,17 +16,18 @@ module ActiveRecord
15
16
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
16
17
  owner, 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("#{name}=", as: temp_method_name, 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
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
 
24
24
  RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
25
25
 
26
- class GeneratedAttributeMethods < Module #:nodoc:
26
+ class GeneratedAttributeMethods < Module # :nodoc:
27
27
  include Mutex_m
28
28
  end
29
29
 
@@ -33,26 +33,94 @@ module ActiveRecord
33
33
  Base.instance_methods +
34
34
  Base.private_instance_methods -
35
35
  Base.superclass.instance_methods -
36
- Base.superclass.private_instance_methods
36
+ Base.superclass.private_instance_methods +
37
+ %i[__id__ dup freeze frozen? hash object_id class clone]
37
38
  ).map { |m| -m.to_s }.to_set.freeze
38
39
  end
39
40
  end
40
41
 
41
42
  module ClassMethods
42
- def inherited(child_class) #:nodoc:
43
- child_class.initialize_generated_modules
44
- super
45
- end
46
-
47
43
  def initialize_generated_modules # :nodoc:
48
44
  @generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new)
49
45
  private_constant :GeneratedAttributeMethods
50
46
  @attribute_methods_generated = false
47
+ @alias_attributes_mass_generated = false
51
48
  include @generated_attribute_methods
52
49
 
53
50
  super
54
51
  end
55
52
 
53
+ def alias_attribute(new_name, old_name)
54
+ super
55
+
56
+ if @alias_attributes_mass_generated
57
+ ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
58
+ generate_alias_attribute_methods(code_generator, new_name, old_name)
59
+ end
60
+ end
61
+ end
62
+
63
+ def eagerly_generate_alias_attribute_methods(_new_name, _old_name) # :nodoc:
64
+ # alias attributes in Active Record are lazily generated
65
+ end
66
+
67
+ def generate_alias_attributes # :nodoc:
68
+ superclass.generate_alias_attributes unless superclass == Base
69
+ return if @alias_attributes_mass_generated
70
+
71
+ generated_attribute_methods.synchronize do
72
+ return if @alias_attributes_mass_generated
73
+ ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
74
+ aliases_by_attribute_name.each do |old_name, new_names|
75
+ new_names.each do |new_name|
76
+ generate_alias_attribute_methods(code_generator, new_name, old_name)
77
+ end
78
+ end
79
+ end
80
+
81
+ @alias_attributes_mass_generated = true
82
+ end
83
+ end
84
+
85
+ def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
86
+ method_name = pattern.method_name(new_name).to_s
87
+ target_name = pattern.method_name(old_name).to_s
88
+ parameters = pattern.parameters
89
+ old_name = old_name.to_s
90
+
91
+ method_defined = method_defined?(target_name) || private_method_defined?(target_name)
92
+ manually_defined = method_defined &&
93
+ !self.instance_method(target_name).owner.is_a?(GeneratedAttributeMethods)
94
+ reserved_method_name = ::ActiveRecord::AttributeMethods.dangerous_attribute_methods.include?(target_name)
95
+
96
+ if !abstract_class? && !has_attribute?(old_name)
97
+ # We only need to issue this deprecation warning once, so we issue it when defining the original reader method.
98
+ should_warn = target_name == old_name
99
+ if should_warn
100
+ ActiveRecord.deprecator.warn(
101
+ "#{self} model aliases `#{old_name}`, but `#{old_name}` is not an attribute. " \
102
+ "Starting in Rails 7.2, alias_attribute with non-attribute targets will raise. " \
103
+ "Use `alias_method :#{new_name}, :#{old_name}` or define the method manually."
104
+ )
105
+ end
106
+ super
107
+ elsif manually_defined && !reserved_method_name
108
+ aliased_method_redefined_as_well = method_defined_within?(method_name, self)
109
+ return if aliased_method_redefined_as_well
110
+
111
+ ActiveRecord.deprecator.warn(
112
+ "#{self} model aliases `#{old_name}` and has a method called `#{target_name}` defined. " \
113
+ "Starting in Rails 7.2 `#{method_name}` will not be calling `#{target_name}` anymore. " \
114
+ "You may want to additionally define `#{method_name}` to preserve the current behavior."
115
+ )
116
+ super
117
+ else
118
+ define_proxy_call(code_generator, method_name, pattern.proxy_target, parameters, old_name,
119
+ namespace: :proxy_alias_attribute
120
+ )
121
+ end
122
+ end
123
+
56
124
  # Generates all the attribute related methods for columns in the database
57
125
  # accessors, mutators and query methods.
58
126
  def define_attribute_methods # :nodoc:
@@ -71,6 +139,7 @@ module ActiveRecord
71
139
  generated_attribute_methods.synchronize do
72
140
  super if defined?(@attribute_methods_generated) && @attribute_methods_generated
73
141
  @attribute_methods_generated = false
142
+ @alias_attributes_mass_generated = false
74
143
  end
75
144
  end
76
145
 
@@ -97,7 +166,7 @@ module ActiveRecord
97
166
  super
98
167
  else
99
168
  # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
100
- # defines its own attribute method, then we don't want to overwrite that.
169
+ # defines its own attribute method, then we don't want to override that.
101
170
  defined = method_defined_within?(method_name, superclass, Base) &&
102
171
  ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
103
172
  defined || super
@@ -186,6 +255,16 @@ module ActiveRecord
186
255
  def _has_attribute?(attr_name) # :nodoc:
187
256
  attribute_types.key?(attr_name)
188
257
  end
258
+
259
+ private
260
+ def inherited(child_class)
261
+ super
262
+ child_class.initialize_generated_modules
263
+ child_class.class_eval do
264
+ @alias_attributes_mass_generated = false
265
+ @attribute_names = nil
266
+ end
267
+ end
189
268
  end
190
269
 
191
270
  # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
@@ -267,9 +346,8 @@ module ActiveRecord
267
346
 
268
347
  # Returns an <tt>#inspect</tt>-like string for the value of the
269
348
  # attribute +attr_name+. String attributes are truncated up to 50
270
- # characters, Date and Time attributes are returned in the
271
- # <tt>:db</tt> format. Other attributes return the value of
272
- # <tt>#inspect</tt> without modification.
349
+ # characters. Other attributes return the value of <tt>#inspect</tt>
350
+ # without modification.
273
351
  #
274
352
  # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
275
353
  #
@@ -277,7 +355,7 @@ module ActiveRecord
277
355
  # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
278
356
  #
279
357
  # person.attribute_for_inspect(:created_at)
280
- # # => "\"2012-10-22 00:15:07\""
358
+ # # => "\"2012-10-22 00:15:07.000000000 +0000\""
281
359
  #
282
360
  # person.attribute_for_inspect(:tag_ids)
283
361
  # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
@@ -310,37 +388,40 @@ module ActiveRecord
310
388
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
311
389
  end
312
390
 
313
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
314
- # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
315
- # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
316
- #
317
- # Note: +:id+ is always present.
391
+ # Returns the value of the attribute identified by +attr_name+ after it has
392
+ # been type cast. (For information about specific type casting behavior, see
393
+ # the types under ActiveModel::Type.)
318
394
  #
319
395
  # class Person < ActiveRecord::Base
320
396
  # belongs_to :organization
321
397
  # end
322
398
  #
323
- # person = Person.new(name: 'Francesco', age: '22')
324
- # person[:name] # => "Francesco"
325
- # person[:age] # => 22
399
+ # person = Person.new(name: "Francesco", date_of_birth: "2004-12-12")
400
+ # person[:name] # => "Francesco"
401
+ # person[:date_of_birth] # => Date.new(2004, 12, 12)
402
+ # person[:organization_id] # => nil
326
403
  #
327
- # person = Person.select('id').first
328
- # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
329
- # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
404
+ # Raises ActiveModel::MissingAttributeError if the attribute is missing.
405
+ # Note, however, that the +id+ attribute will never be considered missing.
406
+ #
407
+ # person = Person.select(:name).first
408
+ # person[:name] # => "Francesco"
409
+ # person[:date_of_birth] # => ActiveModel::MissingAttributeError: missing attribute 'date_of_birth' for Person
410
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute 'organization_id' for Person
411
+ # person[:id] # => nil
330
412
  def [](attr_name)
331
413
  read_attribute(attr_name) { |n| missing_attribute(n, caller) }
332
414
  end
333
415
 
334
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
335
- # (Alias for the protected #write_attribute method).
416
+ # Updates the attribute identified by +attr_name+ using the specified
417
+ # +value+. The attribute value will be type cast upon being read.
336
418
  #
337
419
  # class Person < ActiveRecord::Base
338
420
  # end
339
421
  #
340
422
  # person = Person.new
341
- # person[:age] = '22'
342
- # person[:age] # => 22
343
- # person[:age].class # => Integer
423
+ # person[:date_of_birth] = "2004-12-12"
424
+ # person[:date_of_birth] # => Date.new(2004, 12, 12)
344
425
  def []=(attr_name, value)
345
426
  write_attribute(attr_name, value)
346
427
  end
@@ -361,10 +442,9 @@ module ActiveRecord
361
442
  # end
362
443
  #
363
444
  # private
364
- #
365
- # def print_accessed_fields
366
- # p @posts.first.accessed_fields
367
- # end
445
+ # def print_accessed_fields
446
+ # p @posts.first.accessed_fields
447
+ # end
368
448
  # end
369
449
  #
370
450
  # Which allows you to quickly change your code to:
@@ -385,25 +465,26 @@ module ActiveRecord
385
465
  end
386
466
 
387
467
  def attributes_with_values(attribute_names)
388
- attribute_names.index_with do |name|
389
- _read_attribute(name)
390
- end
468
+ attribute_names.index_with { |name| @attributes[name] }
391
469
  end
392
470
 
393
- # Filters the primary keys and readonly attributes from the attribute names.
471
+ # Filters the primary keys, readonly attributes and virtual columns from the attribute names.
394
472
  def attributes_for_update(attribute_names)
395
473
  attribute_names &= self.class.column_names
396
474
  attribute_names.delete_if do |name|
397
- self.class.readonly_attribute?(name)
475
+ self.class.readonly_attribute?(name) ||
476
+ self.class.counter_cache_column?(name) ||
477
+ column_for_attribute(name).virtual?
398
478
  end
399
479
  end
400
480
 
401
- # Filters out the primary keys, from the attribute names, when the primary
481
+ # Filters out the virtual columns and also primary keys, from the attribute names, when the primary
402
482
  # key is to be generated (e.g. the id attribute has no value).
403
483
  def attributes_for_create(attribute_names)
404
484
  attribute_names &= self.class.column_names
405
485
  attribute_names.delete_if do |name|
406
- pk_attribute?(name) && id.nil?
486
+ (pk_attribute?(name) && id.nil?) ||
487
+ column_for_attribute(name).virtual?
407
488
  end
408
489
  end
409
490
 
@@ -414,7 +495,7 @@ module ActiveRecord
414
495
  inspected_value = if value.is_a?(String) && value.length > 50
415
496
  "#{value[0, 50]}...".inspect
416
497
  elsif value.is_a?(Date) || value.is_a?(Time)
417
- %("#{value.to_s(:inspect)}")
498
+ %("#{value.to_fs(:inspect)}")
418
499
  else
419
500
  value.inspect
420
501
  end