activerecord 5.2.8 → 7.0.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (364) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1393 -587
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +10 -9
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +122 -47
  10. data/lib/active_record/associations/association_scope.rb +24 -24
  11. data/lib/active_record/associations/belongs_to_association.rb +67 -49
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -7
  13. data/lib/active_record/associations/builder/association.rb +52 -23
  14. data/lib/active_record/associations/builder/belongs_to.rb +44 -61
  15. data/lib/active_record/associations/builder/collection_association.rb +17 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +10 -3
  18. data/lib/active_record/associations/builder/has_one.rb +35 -3
  19. data/lib/active_record/associations/builder/singular_association.rb +5 -3
  20. data/lib/active_record/associations/collection_association.rb +59 -50
  21. data/lib/active_record/associations/collection_proxy.rb +32 -23
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/foreign_association.rb +20 -0
  24. data/lib/active_record/associations/has_many_association.rb +27 -14
  25. data/lib/active_record/associations/has_many_through_association.rb +26 -19
  26. data/lib/active_record/associations/has_one_association.rb +52 -37
  27. data/lib/active_record/associations/has_one_through_association.rb +6 -6
  28. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  30. data/lib/active_record/associations/join_dependency.rb +97 -62
  31. data/lib/active_record/associations/preloader/association.rb +220 -60
  32. data/lib/active_record/associations/preloader/batch.rb +48 -0
  33. data/lib/active_record/associations/preloader/branch.rb +147 -0
  34. data/lib/active_record/associations/preloader/through_association.rb +85 -40
  35. data/lib/active_record/associations/preloader.rb +44 -105
  36. data/lib/active_record/associations/singular_association.rb +9 -17
  37. data/lib/active_record/associations/through_association.rb +4 -4
  38. data/lib/active_record/associations.rb +207 -66
  39. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  40. data/lib/active_record/attribute_assignment.rb +17 -19
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +19 -8
  42. data/lib/active_record/attribute_methods/dirty.rb +141 -47
  43. data/lib/active_record/attribute_methods/primary_key.rb +22 -27
  44. data/lib/active_record/attribute_methods/query.rb +6 -10
  45. data/lib/active_record/attribute_methods/read.rb +15 -55
  46. data/lib/active_record/attribute_methods/serialization.rb +77 -18
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +16 -18
  48. data/lib/active_record/attribute_methods/write.rb +18 -37
  49. data/lib/active_record/attribute_methods.rb +90 -153
  50. data/lib/active_record/attributes.rb +38 -12
  51. data/lib/active_record/autosave_association.rb +50 -50
  52. data/lib/active_record/base.rb +23 -18
  53. data/lib/active_record/callbacks.rb +159 -44
  54. data/lib/active_record/coders/yaml_column.rb +12 -3
  55. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  58. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +92 -464
  59. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -51
  60. data/lib/active_record/connection_adapters/abstract/database_statements.rb +209 -164
  61. data/lib/active_record/connection_adapters/abstract/query_cache.rb +38 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +103 -82
  63. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  64. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +140 -110
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -94
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +16 -5
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +456 -159
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +169 -78
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +367 -162
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +311 -327
  71. data/lib/active_record/connection_adapters/column.rb +33 -11
  72. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  73. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  74. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  75. data/lib/active_record/connection_adapters/mysql/database_statements.rb +113 -45
  76. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  77. data/lib/active_record/connection_adapters/mysql/quoting.rb +71 -5
  78. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  79. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  80. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +25 -8
  81. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +143 -19
  82. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  83. data/lib/active_record/connection_adapters/mysql2_adapter.rb +63 -22
  84. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  85. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  86. data/lib/active_record/connection_adapters/postgresql/column.rb +53 -28
  87. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +56 -63
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +54 -16
  95. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  102. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +26 -12
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -52
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -2
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +39 -4
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +128 -91
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -1
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +149 -113
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +386 -182
  116. data/lib/active_record/connection_adapters/schema_cache.rb +161 -22
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
  118. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +152 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +65 -18
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  121. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +92 -26
  122. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +251 -204
  123. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  124. data/lib/active_record/connection_adapters.rb +53 -0
  125. data/lib/active_record/connection_handling.rb +292 -38
  126. data/lib/active_record/core.rb +385 -158
  127. data/lib/active_record/counter_cache.rb +8 -30
  128. data/lib/active_record/database_configurations/connection_url_resolver.rb +100 -0
  129. data/lib/active_record/database_configurations/database_config.rb +83 -0
  130. data/lib/active_record/database_configurations/hash_config.rb +154 -0
  131. data/lib/active_record/database_configurations/url_config.rb +53 -0
  132. data/lib/active_record/database_configurations.rb +256 -0
  133. data/lib/active_record/delegated_type.rb +250 -0
  134. data/lib/active_record/destroy_association_async_job.rb +36 -0
  135. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  136. data/lib/active_record/dynamic_matchers.rb +4 -5
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +44 -0
  140. data/lib/active_record/encryption/configurable.rb +61 -0
  141. data/lib/active_record/encryption/context.rb +35 -0
  142. data/lib/active_record/encryption/contexts.rb +72 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -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 +155 -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 +160 -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 +42 -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_serializer.rb +90 -0
  159. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  160. data/lib/active_record/encryption/properties.rb +76 -0
  161. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  162. data/lib/active_record/encryption/scheme.rb +99 -0
  163. data/lib/active_record/encryption.rb +55 -0
  164. data/lib/active_record/enum.rb +130 -51
  165. data/lib/active_record/errors.rb +129 -23
  166. data/lib/active_record/explain.rb +10 -6
  167. data/lib/active_record/explain_registry.rb +11 -6
  168. data/lib/active_record/explain_subscriber.rb +1 -1
  169. data/lib/active_record/fixture_set/file.rb +22 -15
  170. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  171. data/lib/active_record/fixture_set/render_context.rb +17 -0
  172. data/lib/active_record/fixture_set/table_row.rb +187 -0
  173. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  174. data/lib/active_record/fixtures.rb +206 -490
  175. data/lib/active_record/future_result.rb +139 -0
  176. data/lib/active_record/gem_version.rb +3 -3
  177. data/lib/active_record/inheritance.rb +104 -37
  178. data/lib/active_record/insert_all.rb +278 -0
  179. data/lib/active_record/integration.rb +69 -18
  180. data/lib/active_record/internal_metadata.rb +24 -9
  181. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  182. data/lib/active_record/locking/optimistic.rb +41 -26
  183. data/lib/active_record/locking/pessimistic.rb +18 -8
  184. data/lib/active_record/log_subscriber.rb +46 -35
  185. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  186. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  187. data/lib/active_record/middleware/database_selector.rb +82 -0
  188. data/lib/active_record/middleware/shard_selector.rb +60 -0
  189. data/lib/active_record/migration/command_recorder.rb +96 -44
  190. data/lib/active_record/migration/compatibility.rb +246 -64
  191. data/lib/active_record/migration/join_table.rb +1 -2
  192. data/lib/active_record/migration.rb +266 -187
  193. data/lib/active_record/model_schema.rb +165 -52
  194. data/lib/active_record/nested_attributes.rb +17 -19
  195. data/lib/active_record/no_touching.rb +11 -4
  196. data/lib/active_record/null_relation.rb +2 -7
  197. data/lib/active_record/persistence.rb +467 -92
  198. data/lib/active_record/query_cache.rb +21 -4
  199. data/lib/active_record/query_logs.rb +138 -0
  200. data/lib/active_record/querying.rb +51 -24
  201. data/lib/active_record/railtie.rb +224 -57
  202. data/lib/active_record/railties/console_sandbox.rb +2 -4
  203. data/lib/active_record/railties/controller_runtime.rb +31 -36
  204. data/lib/active_record/railties/databases.rake +369 -101
  205. data/lib/active_record/readonly_attributes.rb +15 -0
  206. data/lib/active_record/reflection.rb +170 -137
  207. data/lib/active_record/relation/batches/batch_enumerator.rb +44 -14
  208. data/lib/active_record/relation/batches.rb +46 -37
  209. data/lib/active_record/relation/calculations.rb +168 -96
  210. data/lib/active_record/relation/delegation.rb +37 -52
  211. data/lib/active_record/relation/finder_methods.rb +79 -58
  212. data/lib/active_record/relation/from_clause.rb +5 -1
  213. data/lib/active_record/relation/merger.rb +50 -51
  214. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  215. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  216. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  217. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  218. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  219. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  220. data/lib/active_record/relation/predicate_builder.rb +58 -46
  221. data/lib/active_record/relation/query_attribute.rb +9 -10
  222. data/lib/active_record/relation/query_methods.rb +685 -208
  223. data/lib/active_record/relation/record_fetch_warning.rb +9 -11
  224. data/lib/active_record/relation/spawn_methods.rb +10 -10
  225. data/lib/active_record/relation/where_clause.rb +108 -64
  226. data/lib/active_record/relation.rb +515 -151
  227. data/lib/active_record/result.rb +78 -42
  228. data/lib/active_record/runtime_registry.rb +9 -13
  229. data/lib/active_record/sanitization.rb +29 -44
  230. data/lib/active_record/schema.rb +37 -31
  231. data/lib/active_record/schema_dumper.rb +74 -23
  232. data/lib/active_record/schema_migration.rb +7 -9
  233. data/lib/active_record/scoping/default.rb +62 -17
  234. data/lib/active_record/scoping/named.rb +17 -32
  235. data/lib/active_record/scoping.rb +70 -41
  236. data/lib/active_record/secure_token.rb +16 -8
  237. data/lib/active_record/serialization.rb +6 -4
  238. data/lib/active_record/signed_id.rb +116 -0
  239. data/lib/active_record/statement_cache.rb +49 -6
  240. data/lib/active_record/store.rb +88 -9
  241. data/lib/active_record/suppressor.rb +13 -17
  242. data/lib/active_record/table_metadata.rb +42 -43
  243. data/lib/active_record/tasks/database_tasks.rb +352 -94
  244. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  245. data/lib/active_record/tasks/postgresql_database_tasks.rb +41 -39
  246. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  247. data/lib/active_record/test_databases.rb +24 -0
  248. data/lib/active_record/test_fixtures.rb +287 -0
  249. data/lib/active_record/timestamp.rb +44 -34
  250. data/lib/active_record/touch_later.rb +23 -22
  251. data/lib/active_record/transactions.rb +67 -128
  252. data/lib/active_record/translation.rb +3 -3
  253. data/lib/active_record/type/adapter_specific_registry.rb +34 -19
  254. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  255. data/lib/active_record/type/internal/timezone.rb +2 -2
  256. data/lib/active_record/type/serialized.rb +7 -4
  257. data/lib/active_record/type/time.rb +10 -0
  258. data/lib/active_record/type/type_map.rb +17 -21
  259. data/lib/active_record/type/unsigned_integer.rb +0 -1
  260. data/lib/active_record/type.rb +9 -5
  261. data/lib/active_record/type_caster/connection.rb +15 -15
  262. data/lib/active_record/type_caster/map.rb +8 -8
  263. data/lib/active_record/validations/associated.rb +2 -3
  264. data/lib/active_record/validations/numericality.rb +35 -0
  265. data/lib/active_record/validations/uniqueness.rb +39 -31
  266. data/lib/active_record/validations.rb +4 -3
  267. data/lib/active_record.rb +209 -32
  268. data/lib/arel/alias_predication.rb +9 -0
  269. data/lib/arel/attributes/attribute.rb +33 -0
  270. data/lib/arel/collectors/bind.rb +29 -0
  271. data/lib/arel/collectors/composite.rb +39 -0
  272. data/lib/arel/collectors/plain_string.rb +20 -0
  273. data/lib/arel/collectors/sql_string.rb +27 -0
  274. data/lib/arel/collectors/substitute_binds.rb +35 -0
  275. data/lib/arel/crud.rb +48 -0
  276. data/lib/arel/delete_manager.rb +32 -0
  277. data/lib/arel/errors.rb +9 -0
  278. data/lib/arel/expressions.rb +29 -0
  279. data/lib/arel/factory_methods.rb +49 -0
  280. data/lib/arel/filter_predications.rb +9 -0
  281. data/lib/arel/insert_manager.rb +48 -0
  282. data/lib/arel/math.rb +45 -0
  283. data/lib/arel/nodes/and.rb +32 -0
  284. data/lib/arel/nodes/ascending.rb +23 -0
  285. data/lib/arel/nodes/binary.rb +126 -0
  286. data/lib/arel/nodes/bind_param.rb +44 -0
  287. data/lib/arel/nodes/case.rb +55 -0
  288. data/lib/arel/nodes/casted.rb +62 -0
  289. data/lib/arel/nodes/comment.rb +29 -0
  290. data/lib/arel/nodes/count.rb +12 -0
  291. data/lib/arel/nodes/delete_statement.rb +44 -0
  292. data/lib/arel/nodes/descending.rb +23 -0
  293. data/lib/arel/nodes/equality.rb +15 -0
  294. data/lib/arel/nodes/extract.rb +24 -0
  295. data/lib/arel/nodes/false.rb +16 -0
  296. data/lib/arel/nodes/filter.rb +10 -0
  297. data/lib/arel/nodes/full_outer_join.rb +8 -0
  298. data/lib/arel/nodes/function.rb +45 -0
  299. data/lib/arel/nodes/grouping.rb +11 -0
  300. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  301. data/lib/arel/nodes/in.rb +15 -0
  302. data/lib/arel/nodes/infix_operation.rb +92 -0
  303. data/lib/arel/nodes/inner_join.rb +8 -0
  304. data/lib/arel/nodes/insert_statement.rb +37 -0
  305. data/lib/arel/nodes/join_source.rb +20 -0
  306. data/lib/arel/nodes/matches.rb +18 -0
  307. data/lib/arel/nodes/named_function.rb +23 -0
  308. data/lib/arel/nodes/node.rb +51 -0
  309. data/lib/arel/nodes/node_expression.rb +13 -0
  310. data/lib/arel/nodes/ordering.rb +27 -0
  311. data/lib/arel/nodes/outer_join.rb +8 -0
  312. data/lib/arel/nodes/over.rb +15 -0
  313. data/lib/arel/nodes/regexp.rb +16 -0
  314. data/lib/arel/nodes/right_outer_join.rb +8 -0
  315. data/lib/arel/nodes/select_core.rb +67 -0
  316. data/lib/arel/nodes/select_statement.rb +41 -0
  317. data/lib/arel/nodes/sql_literal.rb +19 -0
  318. data/lib/arel/nodes/string_join.rb +11 -0
  319. data/lib/arel/nodes/table_alias.rb +31 -0
  320. data/lib/arel/nodes/terminal.rb +16 -0
  321. data/lib/arel/nodes/true.rb +16 -0
  322. data/lib/arel/nodes/unary.rb +44 -0
  323. data/lib/arel/nodes/unary_operation.rb +20 -0
  324. data/lib/arel/nodes/unqualified_column.rb +22 -0
  325. data/lib/arel/nodes/update_statement.rb +46 -0
  326. data/lib/arel/nodes/values_list.rb +9 -0
  327. data/lib/arel/nodes/window.rb +126 -0
  328. data/lib/arel/nodes/with.rb +11 -0
  329. data/lib/arel/nodes.rb +71 -0
  330. data/lib/arel/order_predications.rb +13 -0
  331. data/lib/arel/predications.rb +258 -0
  332. data/lib/arel/select_manager.rb +276 -0
  333. data/lib/arel/table.rb +117 -0
  334. data/lib/arel/tree_manager.rb +60 -0
  335. data/lib/arel/update_manager.rb +48 -0
  336. data/lib/arel/visitors/dot.rb +298 -0
  337. data/lib/arel/visitors/mysql.rb +99 -0
  338. data/lib/arel/visitors/postgresql.rb +110 -0
  339. data/lib/arel/visitors/sqlite.rb +38 -0
  340. data/lib/arel/visitors/to_sql.rb +955 -0
  341. data/lib/arel/visitors/visitor.rb +45 -0
  342. data/lib/arel/visitors.rb +13 -0
  343. data/lib/arel/window_predications.rb +9 -0
  344. data/lib/arel.rb +55 -0
  345. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  346. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  347. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  348. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  349. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  350. data/lib/rails/generators/active_record/migration.rb +19 -2
  351. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  352. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  353. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  354. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  355. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  356. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  357. metadata +162 -32
  358. data/lib/active_record/attribute_decorators.rb +0 -90
  359. data/lib/active_record/collection_cache_key.rb +0 -53
  360. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  361. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  362. data/lib/active_record/define_callbacks.rb +0 -22
  363. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  364. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -20,8 +20,16 @@ module ActiveRecord
20
20
  # Indicates whether to use a stable #cache_key method that is accompanied
21
21
  # by a changing version in the #cache_version method.
22
22
  #
23
- # This is +false+, by default until Rails 6.0.
23
+ # This is +true+, by default on Rails 5.2 and above.
24
24
  class_attribute :cache_versioning, instance_writer: false, default: false
25
+
26
+ ##
27
+ # :singleton-method:
28
+ # Indicates whether to use a stable #cache_key method that is accompanied
29
+ # by a changing version in the #cache_version method on collections.
30
+ #
31
+ # This is +false+, by default until Rails 6.1.
32
+ class_attribute :collection_cache_versioning, instance_writer: false, default: false
25
33
  end
26
34
 
27
35
  # Returns a +String+, which Action Pack uses for constructing a URL to this
@@ -60,27 +68,18 @@ module ActiveRecord
60
68
  # the cache key will also include a version.
61
69
  #
62
70
  # Product.cache_versioning = false
63
- # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
64
- def cache_key(*timestamp_names)
71
+ # Product.find(5).cache_key # => "products/5-20071224150000" (updated_at available)
72
+ def cache_key
65
73
  if new_record?
66
74
  "#{model_name.cache_key}/new"
67
75
  else
68
- if cache_version && timestamp_names.none?
76
+ if cache_version
69
77
  "#{model_name.cache_key}/#{id}"
70
78
  else
71
- timestamp = if timestamp_names.any?
72
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
73
- Specifying a timestamp name for #cache_key has been deprecated in favor of
74
- the explicit #cache_version method that can be overwritten.
75
- MSG
76
-
77
- max_updated_column_timestamp(timestamp_names)
78
- else
79
- max_updated_column_timestamp
80
- end
79
+ timestamp = max_updated_column_timestamp
81
80
 
82
81
  if timestamp
83
- timestamp = timestamp.utc.to_s(cache_timestamp_format)
82
+ timestamp = timestamp.utc.to_fs(cache_timestamp_format)
84
83
  "#{model_name.cache_key}/#{id}-#{timestamp}"
85
84
  else
86
85
  "#{model_name.cache_key}/#{id}"
@@ -94,10 +93,20 @@ module ActiveRecord
94
93
  # cache_version, but this method can be overwritten to return something else.
95
94
  #
96
95
  # Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to
97
- # +false+ (which it is by default until Rails 6.0).
96
+ # +false+.
98
97
  def cache_version
99
- if cache_versioning && timestamp = try(:updated_at)
100
- timestamp.utc.to_s(:usec)
98
+ return unless cache_versioning
99
+
100
+ if has_attribute?("updated_at")
101
+ timestamp = updated_at_before_type_cast
102
+ if can_use_fast_cache_version?(timestamp)
103
+ raw_timestamp_to_cache_version(timestamp)
104
+
105
+ elsif timestamp = updated_at
106
+ timestamp.utc.to_fs(cache_timestamp_format)
107
+ end
108
+ elsif self.class.has_attribute?("updated_at")
109
+ raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
101
110
  end
102
111
  end
103
112
 
@@ -150,6 +159,48 @@ module ActiveRecord
150
159
  end
151
160
  end
152
161
  end
162
+
163
+ def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
164
+ collection.send(:compute_cache_key, timestamp_column)
165
+ end
153
166
  end
167
+
168
+ private
169
+ # Detects if the value before type cast
170
+ # can be used to generate a cache_version.
171
+ #
172
+ # The fast cache version only works with a
173
+ # string value directly from the database.
174
+ #
175
+ # We also must check if the timestamp format has been changed
176
+ # or if the timezone is not set to UTC then
177
+ # we cannot apply our transformations correctly.
178
+ def can_use_fast_cache_version?(timestamp)
179
+ timestamp.is_a?(String) &&
180
+ cache_timestamp_format == :usec &&
181
+ ActiveRecord.default_timezone == :utc &&
182
+ !updated_at_came_from_user?
183
+ end
184
+
185
+ # Converts a raw database string to `:usec`
186
+ # format.
187
+ #
188
+ # Example:
189
+ #
190
+ # timestamp = "2018-10-15 20:02:15.266505"
191
+ # raw_timestamp_to_cache_version(timestamp)
192
+ # # => "20181015200215266505"
193
+ #
194
+ # PostgreSQL truncates trailing zeros,
195
+ # https://github.com/postgres/postgres/commit/3e1beda2cde3495f41290e1ece5d544525810214
196
+ # to account for this we pad the output with zeros
197
+ def raw_timestamp_to_cache_version(timestamp)
198
+ key = timestamp.delete("- :.")
199
+ if key.length < 20
200
+ key.ljust(20, "0")
201
+ else
202
+ key
203
+ end
204
+ end
154
205
  end
155
206
  end
@@ -6,40 +6,55 @@ require "active_record/scoping/named"
6
6
  module ActiveRecord
7
7
  # This class is used to create a table that keeps track of values and keys such
8
8
  # as which environment migrations were run in.
9
+ #
10
+ # This is enabled by default. To disable this functionality set
11
+ # `use_metadata_table` to false in your database configuration.
9
12
  class InternalMetadata < ActiveRecord::Base # :nodoc:
13
+ self.record_timestamps = true
14
+
10
15
  class << self
16
+ def enabled?
17
+ ActiveRecord::Base.connection.use_metadata_table?
18
+ end
19
+
11
20
  def primary_key
12
21
  "key"
13
22
  end
14
23
 
15
24
  def table_name
16
- "#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}"
25
+ "#{table_name_prefix}#{internal_metadata_table_name}#{table_name_suffix}"
17
26
  end
18
27
 
19
28
  def []=(key, value)
20
- find_or_initialize_by(key: key).update_attributes!(value: value)
29
+ return unless enabled?
30
+
31
+ find_or_initialize_by(key: key).update!(value: value)
21
32
  end
22
33
 
23
34
  def [](key)
24
- where(key: key).pluck(:value).first
25
- end
35
+ return unless enabled?
26
36
 
27
- def table_exists?
28
- connection.table_exists?(table_name)
37
+ where(key: key).pick(:value)
29
38
  end
30
39
 
31
40
  # Creates an internal metadata table with columns +key+ and +value+
32
41
  def create_table
33
- unless table_exists?
34
- key_options = connection.internal_string_options_for_primary_key
42
+ return unless enabled?
35
43
 
44
+ unless connection.table_exists?(table_name)
36
45
  connection.create_table(table_name, id: false) do |t|
37
- t.string :key, key_options
46
+ t.string :key, **connection.internal_string_options_for_primary_key
38
47
  t.string :value
39
48
  t.timestamps
40
49
  end
41
50
  end
42
51
  end
52
+
53
+ def drop_table
54
+ return unless enabled?
55
+
56
+ connection.drop_table table_name, if_exists: true
57
+ end
43
58
  end
44
59
  end
45
60
  end
@@ -1,47 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
- module LegacyYamlAdapter
5
- def self.convert(klass, coder)
4
+ module LegacyYamlAdapter # :nodoc:
5
+ def self.convert(coder)
6
6
  return coder unless coder.is_a?(Psych::Coder)
7
7
 
8
8
  case coder["active_record_yaml_version"]
9
9
  when 1, 2 then coder
10
10
  else
11
- if coder["attributes"].is_a?(ActiveModel::AttributeSet)
12
- Rails420.convert(klass, coder)
13
- else
14
- Rails41.convert(klass, coder)
15
- end
16
- end
17
- end
18
-
19
- module Rails420
20
- def self.convert(klass, coder)
21
- attribute_set = coder["attributes"]
22
-
23
- klass.attribute_names.each do |attr_name|
24
- attribute = attribute_set[attr_name]
25
- if attribute.type.is_a?(Delegator)
26
- type_from_klass = klass.type_for_attribute(attr_name)
27
- attribute_set[attr_name] = attribute.with_type(type_from_klass)
28
- end
29
- end
30
-
31
- coder
32
- end
33
- end
34
-
35
- module Rails41
36
- def self.convert(klass, coder)
37
- attributes = klass.attributes_builder
38
- .build_from_database(coder["attributes"])
39
- new_record = coder["attributes"][klass.primary_key].blank?
40
-
41
- {
42
- "attributes" => attributes,
43
- "new_record" => new_record,
44
- }
11
+ raise("Active Record doesn't know how to load YAML with this format.")
45
12
  end
46
13
  end
47
14
  end
@@ -56,12 +56,21 @@ module ActiveRecord
56
56
  class_attribute :lock_optimistically, instance_writer: false, default: true
57
57
  end
58
58
 
59
- def locking_enabled? #:nodoc:
59
+ def locking_enabled? # :nodoc:
60
60
  self.class.locking_enabled?
61
61
  end
62
62
 
63
+ def increment!(*, **) # :nodoc:
64
+ super.tap do
65
+ if locking_enabled?
66
+ self[self.class.locking_column] += 1
67
+ clear_attribute_change(self.class.locking_column)
68
+ end
69
+ end
70
+ end
71
+
63
72
  private
64
- def _create_record(attribute_names = self.attribute_names, *)
73
+ def _create_record(attribute_names = self.attribute_names)
65
74
  if locking_enabled?
66
75
  # We always want to persist the locking version, even if we don't detect
67
76
  # a change from the default, since the database might have no default
@@ -71,9 +80,8 @@ module ActiveRecord
71
80
  end
72
81
 
73
82
  def _touch_row(attribute_names, time)
83
+ @_touch_attr_names << self.class.locking_column if locking_enabled?
74
84
  super
75
- ensure
76
- clear_attribute_change(self.class.locking_column) if locking_enabled?
77
85
  end
78
86
 
79
87
  def _update_row(attribute_names, attempted_action = "update")
@@ -81,15 +89,19 @@ module ActiveRecord
81
89
 
82
90
  begin
83
91
  locking_column = self.class.locking_column
84
- previous_lock_value = read_attribute_before_type_cast(locking_column)
92
+ lock_attribute_was = @attributes[locking_column]
93
+
94
+ update_constraints = _primary_key_constraints_hash
95
+ update_constraints[locking_column] = _lock_value_for_database(locking_column)
96
+
97
+ attribute_names = attribute_names.dup if attribute_names.frozen?
85
98
  attribute_names << locking_column
86
99
 
87
100
  self[locking_column] += 1
88
101
 
89
102
  affected_rows = self.class._update_record(
90
103
  attributes_with_values(attribute_names),
91
- self.class.primary_key => id_in_database,
92
- locking_column => previous_lock_value
104
+ update_constraints
93
105
  )
94
106
 
95
107
  if affected_rows != 1
@@ -100,7 +112,7 @@ module ActiveRecord
100
112
 
101
113
  # If something went wrong, revert the locking_column value.
102
114
  rescue Exception
103
- self[locking_column] = previous_lock_value.to_i
115
+ @attributes[locking_column] = lock_attribute_was
104
116
  raise
105
117
  end
106
118
  end
@@ -110,10 +122,10 @@ module ActiveRecord
110
122
 
111
123
  locking_column = self.class.locking_column
112
124
 
113
- affected_rows = self.class._delete_record(
114
- self.class.primary_key => id_in_database,
115
- locking_column => read_attribute_before_type_cast(locking_column)
116
- )
125
+ delete_constraints = _primary_key_constraints_hash
126
+ delete_constraints[locking_column] = _lock_value_for_database(locking_column)
127
+
128
+ affected_rows = self.class._delete_record(delete_constraints)
117
129
 
118
130
  if affected_rows != 1
119
131
  raise ActiveRecord::StaleObjectError.new(self, "destroy")
@@ -122,6 +134,14 @@ module ActiveRecord
122
134
  affected_rows
123
135
  end
124
136
 
137
+ def _lock_value_for_database(locking_column)
138
+ if will_save_change_to_attribute?(locking_column)
139
+ @attributes[locking_column].value_for_database
140
+ else
141
+ @attributes[locking_column].original_value_for_database
142
+ end
143
+ end
144
+
125
145
  module ClassMethods
126
146
  DEFAULT_LOCKING_COLUMN = "lock_version"
127
147
 
@@ -156,21 +176,12 @@ module ActiveRecord
156
176
  super
157
177
  end
158
178
 
159
- private
160
-
161
- # We need to apply this decorator here, rather than on module inclusion. The closure
162
- # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
163
- # sub class being decorated. As such, changes to `lock_optimistically`, or
164
- # `locking_column` would not be picked up.
165
- def inherited(subclass)
166
- subclass.class_eval do
167
- is_lock_column = ->(name, _) { lock_optimistically && name == locking_column }
168
- decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type|
169
- LockingType.new(type)
170
- end
171
- end
172
- super
179
+ def define_attribute(name, cast_type, **) # :nodoc:
180
+ if lock_optimistically && name == locking_column
181
+ cast_type = LockingType.new(cast_type)
173
182
  end
183
+ super
184
+ end
174
185
  end
175
186
  end
176
187
 
@@ -178,6 +189,10 @@ module ActiveRecord
178
189
  # `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError`
179
190
  # during update record.
180
191
  class LockingType < DelegateClass(Type::Value) # :nodoc:
192
+ def self.new(subtype)
193
+ self === subtype ? subtype : super
194
+ end
195
+
181
196
  def deserialize(value)
182
197
  super.to_i
183
198
  end
@@ -14,9 +14,9 @@ module ActiveRecord
14
14
  # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
15
15
  #
16
16
  # Account.transaction do
17
- # # select * from accounts where name = 'shugo' limit 1 for update
18
- # shugo = Account.where("name = 'shugo'").lock(true).first
19
- # yuko = Account.where("name = 'yuko'").lock(true).first
17
+ # # select * from accounts where name = 'shugo' limit 1 for update nowait
18
+ # shugo = Account.lock("FOR UPDATE NOWAIT").find_by(name: "shugo")
19
+ # yuko = Account.lock("FOR UPDATE NOWAIT").find_by(name: "yuko")
20
20
  # shugo.balance -= 100
21
21
  # shugo.save!
22
22
  # yuko.balance += 100
@@ -53,8 +53,12 @@ module ActiveRecord
53
53
  # end
54
54
  #
55
55
  # Database-specific information on row locking:
56
- # MySQL: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
57
- # PostgreSQL: https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
56
+ #
57
+ # [MySQL]
58
+ # https://dev.mysql.com/doc/refman/en/innodb-locking-reads.html
59
+ #
60
+ # [PostgreSQL]
61
+ # https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
58
62
  module Pessimistic
59
63
  # Obtain a row lock on this record. Reloads the record to obtain the requested
60
64
  # lock. Pass an SQL locking clause to append the end of the SELECT statement
@@ -77,9 +81,15 @@ module ActiveRecord
77
81
 
78
82
  # Wraps the passed block in a transaction, locking the object
79
83
  # before yielding. You can pass the SQL locking clause
80
- # as argument (see <tt>lock!</tt>).
81
- def with_lock(lock = true)
82
- transaction do
84
+ # as an optional argument (see <tt>#lock!</tt>).
85
+ #
86
+ # You can also pass options like <tt>requires_new:</tt>, <tt>isolation:</tt>,
87
+ # and <tt>joinable:</tt> to the wrapping transaction (see
88
+ # <tt>ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction</tt>).
89
+ def with_lock(*args)
90
+ transaction_opts = args.extract_options!
91
+ lock = args.present? ? args.first : true
92
+ transaction(**transaction_opts) do
83
93
  lock!(lock)
84
94
  yield
85
95
  end
@@ -4,6 +4,8 @@ module ActiveRecord
4
4
  class LogSubscriber < ActiveSupport::LogSubscriber
5
5
  IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
6
6
 
7
+ class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
8
+
7
9
  def self.runtime=(value)
8
10
  ActiveRecord::RuntimeRegistry.sql_runtime = value
9
11
  end
@@ -17,6 +19,16 @@ module ActiveRecord
17
19
  rt
18
20
  end
19
21
 
22
+ def strict_loading_violation(event)
23
+ debug do
24
+ owner = event.payload[:owner]
25
+ association = event.payload[:reflection].klass
26
+ name = event.payload[:reflection].name
27
+
28
+ color("Strict loading violation: #{owner} is marked for strict loading. The #{association} association named :#{name} cannot be lazily loaded.", RED)
29
+ end
30
+ end
31
+
20
32
  def sql(event)
21
33
  self.class.runtime += event.duration
22
34
  return unless logger.debug?
@@ -25,20 +37,31 @@ module ActiveRecord
25
37
 
26
38
  return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
27
39
 
28
- name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
40
+ name = if payload[:async]
41
+ "ASYNC #{payload[:name]} (#{payload[:lock_wait].round(1)}ms) (db time #{event.duration.round(1)}ms)"
42
+ else
43
+ "#{payload[:name]} (#{event.duration.round(1)}ms)"
44
+ end
29
45
  name = "CACHE #{name}" if payload[:cached]
30
46
  sql = payload[:sql]
31
47
  binds = nil
32
48
 
33
- unless (payload[:binds] || []).empty?
49
+ if payload[:binds]&.any?
34
50
  casted_params = type_casted_binds(payload[:type_casted_binds])
35
- binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
36
- render_bind(attr, value)
37
- }.inspect
51
+
52
+ binds = []
53
+ payload[:binds].each_with_index do |attr, i|
54
+ attribute_name = attr.respond_to?(:name) ? attr.name : attr[i].name
55
+ filtered_params = filter(attribute_name, casted_params[i])
56
+
57
+ binds << render_bind(attr, filtered_params)
58
+ end
59
+ binds = binds.inspect
60
+ binds.prepend(" ")
38
61
  end
39
62
 
40
63
  name = colorize_payload_name(name, payload[:name])
41
- sql = color(sql, sql_color(sql), true)
64
+ sql = color(sql, sql_color(sql), true) if colorize_logging
42
65
 
43
66
  debug " #{name} #{sql}#{binds}"
44
67
  end
@@ -49,13 +72,18 @@ module ActiveRecord
49
72
  end
50
73
 
51
74
  def render_bind(attr, value)
52
- if attr.is_a?(Array)
75
+ case attr
76
+ when ActiveModel::Attribute
77
+ if attr.type.binary? && attr.value
78
+ value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
79
+ end
80
+ when Array
53
81
  attr = attr.first
54
- elsif attr.type.binary? && attr.value
55
- value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
82
+ else
83
+ attr = nil
56
84
  end
57
85
 
58
- [attr && attr.name, value]
86
+ [attr&.name, value]
59
87
  end
60
88
 
61
89
  def colorize_payload_name(name, payload_name)
@@ -94,42 +122,25 @@ module ActiveRecord
94
122
  def debug(progname = nil, &block)
95
123
  return unless super
96
124
 
97
- if ActiveRecord::Base.verbose_query_logs
125
+ if ActiveRecord.verbose_query_logs
98
126
  log_query_source
99
127
  end
100
128
  end
101
129
 
102
130
  def log_query_source
103
- source_line, line_number = extract_callstack(caller_locations)
104
-
105
- if source_line
106
- if defined?(::Rails.root)
107
- app_root = "#{::Rails.root.to_s}/".freeze
108
- source_line = source_line.sub(app_root, "")
109
- end
131
+ source = extract_query_source_location(caller)
110
132
 
111
- logger.debug(" ↳ #{ source_line }:#{ line_number }")
133
+ if source
134
+ logger.debug(" ↳ #{source}")
112
135
  end
113
136
  end
114
137
 
115
- def extract_callstack(callstack)
116
- line = callstack.find do |frame|
117
- frame.absolute_path && !ignored_callstack(frame.absolute_path)
118
- end
119
-
120
- offending_line = line || callstack.first
121
-
122
- [
123
- offending_line.path,
124
- offending_line.lineno
125
- ]
138
+ def extract_query_source_location(locations)
139
+ backtrace_cleaner.clean(locations.lazy).first
126
140
  end
127
141
 
128
- RAILS_GEM_ROOT = File.expand_path("../../..", __dir__) + "/"
129
-
130
- def ignored_callstack(path)
131
- path.start_with?(RAILS_GEM_ROOT) ||
132
- path.start_with?(RbConfig::CONFIG["rubylibdir"])
142
+ def filter(name, value)
143
+ ActiveRecord::Base.inspection_filter.filter_param(name, value)
133
144
  end
134
145
  end
135
146
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Middleware
5
+ class DatabaseSelector
6
+ class Resolver
7
+ # The session class is used by the DatabaseSelector::Resolver to save
8
+ # timestamps of the last write in the session.
9
+ #
10
+ # The last_write is used to determine whether it's safe to read
11
+ # from the replica or the request needs to be sent to the primary.
12
+ class Session # :nodoc:
13
+ def self.call(request)
14
+ new(request.session)
15
+ end
16
+
17
+ # Converts time to a timestamp that represents milliseconds since
18
+ # epoch.
19
+ def self.convert_time_to_timestamp(time)
20
+ time.to_i * 1000 + time.usec / 1000
21
+ end
22
+
23
+ # Converts milliseconds since epoch timestamp into a time object.
24
+ def self.convert_timestamp_to_time(timestamp)
25
+ timestamp ? Time.at(timestamp / 1000, (timestamp % 1000) * 1000) : Time.at(0)
26
+ end
27
+
28
+ def initialize(session)
29
+ @session = session
30
+ end
31
+
32
+ attr_reader :session
33
+
34
+ def last_write_timestamp
35
+ self.class.convert_timestamp_to_time(session[:last_write])
36
+ end
37
+
38
+ def update_last_write_timestamp
39
+ session[:last_write] = self.class.convert_time_to_timestamp(Time.now)
40
+ end
41
+
42
+ def save(response)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end