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
@@ -13,17 +13,31 @@ module ActiveRecord
13
13
 
14
14
  def after_teardown # :nodoc:
15
15
  super
16
+ ensure
16
17
  teardown_fixtures
17
18
  end
18
19
 
19
20
  included do
20
- class_attribute :fixture_path, instance_writer: false
21
+ ##
22
+ # :singleton-method: fixture_paths
23
+ #
24
+ # Returns the ActiveRecord::FixtureSet collection
25
+
26
+ ##
27
+ # :singleton-method: fixture_paths=
28
+ #
29
+ # :call-seq:
30
+ # fixture_paths=(fixture_paths)
31
+ class_attribute :fixture_paths, instance_writer: false, default: []
21
32
  class_attribute :fixture_table_names, default: []
22
33
  class_attribute :fixture_class_names, default: {}
23
34
  class_attribute :use_transactional_tests, default: true
24
35
  class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances
25
36
  class_attribute :pre_loaded_fixtures, default: false
26
37
  class_attribute :lock_threads, default: true
38
+ class_attribute :fixture_sets, default: {}
39
+
40
+ ActiveSupport.run_load_hooks(:active_record_fixtures, self)
27
41
  end
28
42
 
29
43
  module ClassMethods
@@ -41,51 +55,36 @@ module ActiveRecord
41
55
 
42
56
  def fixtures(*fixture_set_names)
43
57
  if fixture_set_names.first == :all
44
- raise StandardError, "No fixture path found. Please set `#{self}.fixture_path`." if fixture_path.blank?
45
- fixture_set_names = Dir[::File.join(fixture_path, "{**,*}/*.{yml}")].uniq
46
- fixture_set_names.reject! { |f| f.start_with?(file_fixture_path.to_s) } if defined?(file_fixture_path) && file_fixture_path
47
- fixture_set_names.map! { |f| f[fixture_path.to_s.size..-5].delete_prefix("/") }
58
+ raise StandardError, "No fixture path found. Please set `#{self}.fixture_paths`." if fixture_paths.blank?
59
+ fixture_set_names = fixture_paths.flat_map do |path|
60
+ names = Dir[::File.join(path, "{**,*}/*.{yml}")].uniq
61
+ names.reject! { |f| f.start_with?(file_fixture_path.to_s) } if defined?(file_fixture_path) && file_fixture_path
62
+ names.map! { |f| f[path.to_s.size..-5].delete_prefix("/") }
63
+ end.uniq
48
64
  else
49
65
  fixture_set_names = fixture_set_names.flatten.map(&:to_s)
50
66
  end
51
67
 
52
- self.fixture_table_names |= fixture_set_names
68
+ self.fixture_table_names = (fixture_table_names | fixture_set_names).sort
53
69
  setup_fixture_accessors(fixture_set_names)
54
70
  end
55
71
 
56
72
  def setup_fixture_accessors(fixture_set_names = nil)
57
73
  fixture_set_names = Array(fixture_set_names || fixture_table_names)
58
- methods = Module.new do
74
+ unless fixture_set_names.empty?
75
+ self.fixture_sets = fixture_sets.dup
59
76
  fixture_set_names.each do |fs_name|
60
- fs_name = fs_name.to_s
61
- accessor_name = fs_name.tr("/", "_").to_sym
62
-
63
- define_method(accessor_name) do |*fixture_names|
64
- force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
65
- return_single_record = fixture_names.size == 1
66
- fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
67
-
68
- @fixture_cache[fs_name] ||= {}
69
-
70
- instances = fixture_names.map do |f_name|
71
- f_name = f_name.to_s if f_name.is_a?(Symbol)
72
- @fixture_cache[fs_name].delete(f_name) if force_reload
73
-
74
- if @loaded_fixtures[fs_name][f_name]
75
- @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
76
- else
77
- raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
78
- end
79
- end
80
-
81
- return_single_record ? instances.first : instances
82
- end
83
- private accessor_name
77
+ key = fs_name.to_s.include?("/") ? -fs_name.to_s.tr("/", "_") : fs_name
78
+ key = -key.to_s if key.is_a?(Symbol)
79
+ fs_name = -fs_name.to_s if fs_name.is_a?(Symbol)
80
+ fixture_sets[key] = fs_name
84
81
  end
85
82
  end
86
- include methods
87
83
  end
88
84
 
85
+ # Prevents automatically wrapping each specified test in a transaction,
86
+ # to allow application logic transactions to be tested in a top-level
87
+ # (non-nested) context.
89
88
  def uses_transaction(*methods)
90
89
  @uses_transaction = [] unless defined?(@uses_transaction)
91
90
  @uses_transaction.concat methods.map(&:to_s)
@@ -97,99 +96,109 @@ module ActiveRecord
97
96
  end
98
97
  end
99
98
 
100
- def run_in_transaction?
101
- use_transactional_tests &&
102
- !self.class.uses_transaction?(name)
99
+ # Generic fixture accessor for fixture names that may conflict with other methods.
100
+ #
101
+ # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
102
+ # assert_equal "Ruby on Rails", fixture(:web_sites, :rubyonrails).name
103
+ def fixture(fixture_set_name, *fixture_names)
104
+ active_record_fixture(fixture_set_name, *fixture_names)
103
105
  end
104
106
 
105
- def setup_fixtures(config = ActiveRecord::Base)
106
- if pre_loaded_fixtures && !use_transactional_tests
107
- raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
107
+ private
108
+ def run_in_transaction?
109
+ use_transactional_tests &&
110
+ !self.class.uses_transaction?(name)
108
111
  end
109
112
 
110
- @fixture_cache = {}
111
- @fixture_connections = []
112
- @@already_loaded_fixtures ||= {}
113
- @connection_subscriber = nil
114
- @legacy_saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
115
- @saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
116
-
117
- # Load fixtures once and begin transaction.
118
- if run_in_transaction?
119
- if @@already_loaded_fixtures[self.class]
120
- @loaded_fixtures = @@already_loaded_fixtures[self.class]
113
+ def setup_fixtures(config = ActiveRecord::Base)
114
+ if pre_loaded_fixtures && !use_transactional_tests
115
+ raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
116
+ end
117
+
118
+ @fixture_cache = {}
119
+ @fixture_cache_key = [self.class.fixture_table_names.dup, self.class.fixture_paths.dup, self.class.fixture_class_names.dup]
120
+ @fixture_connection_pools = []
121
+ @@already_loaded_fixtures ||= {}
122
+ @connection_subscriber = nil
123
+ @saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
124
+
125
+ if run_in_transaction?
126
+ # Load fixtures once and begin transaction.
127
+ @loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key]
128
+ unless @loaded_fixtures
129
+ @@already_loaded_fixtures.clear
130
+ @loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key] = load_fixtures(config)
131
+ end
132
+
133
+ setup_transactional_fixtures
121
134
  else
135
+ # Load fixtures for every test.
136
+ ActiveRecord::FixtureSet.reset_cache
137
+ invalidate_already_loaded_fixtures
122
138
  @loaded_fixtures = load_fixtures(config)
123
- @@already_loaded_fixtures[self.class] = @loaded_fixtures
124
139
  end
125
140
 
141
+ # Instantiate fixtures for every test if requested.
142
+ instantiate_fixtures if use_instantiated_fixtures
143
+ end
144
+
145
+ def teardown_fixtures
146
+ # Rollback changes if a transaction is active.
147
+ if run_in_transaction?
148
+ teardown_transactional_fixtures
149
+ else
150
+ ActiveRecord::FixtureSet.reset_cache
151
+ invalidate_already_loaded_fixtures
152
+ end
153
+
154
+ ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
155
+ end
156
+
157
+ def invalidate_already_loaded_fixtures
158
+ @@already_loaded_fixtures.clear
159
+ end
160
+
161
+ def setup_transactional_fixtures
162
+ setup_shared_connection_pool
163
+
126
164
  # Begin transactions for connections already established
127
- @fixture_connections = enlist_fixture_connections
128
- @fixture_connections.each do |connection|
129
- connection.begin_transaction joinable: false, _lazy: false
130
- connection.pool.lock_thread = true if lock_threads
165
+ @fixture_connection_pools = ActiveRecord::Base.connection_handler.connection_pool_list(:writing)
166
+ @fixture_connection_pools.each do |pool|
167
+ pool.pin_connection!(lock_threads)
168
+ pool.lease_connection
131
169
  end
132
170
 
133
171
  # When connections are established in the future, begin a transaction too
134
172
  @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
135
- spec_name = payload[:spec_name] if payload.key?(:spec_name)
173
+ connection_name = payload[:connection_name] if payload.key?(:connection_name)
136
174
  shard = payload[:shard] if payload.key?(:shard)
137
- setup_shared_connection_pool if ActiveRecord::Base.legacy_connection_handling
138
175
 
139
- if spec_name
140
- begin
141
- connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard)
142
- rescue ConnectionNotEstablished
143
- connection = nil
144
- end
145
-
146
- if connection
147
- setup_shared_connection_pool unless ActiveRecord::Base.legacy_connection_handling
176
+ if connection_name
177
+ pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard: shard)
178
+ if pool
179
+ setup_shared_connection_pool
148
180
 
149
- if !@fixture_connections.include?(connection)
150
- connection.begin_transaction joinable: false, _lazy: false
151
- connection.pool.lock_thread = true if lock_threads
152
- @fixture_connections << connection
181
+ unless @fixture_connection_pools.include?(pool)
182
+ pool.pin_connection!(lock_threads)
183
+ pool.lease_connection
184
+ @fixture_connection_pools << pool
153
185
  end
154
186
  end
155
187
  end
156
188
  end
157
-
158
- # Load fixtures for every test.
159
- else
160
- ActiveRecord::FixtureSet.reset_cache
161
- @@already_loaded_fixtures[self.class] = nil
162
- @loaded_fixtures = load_fixtures(config)
163
189
  end
164
190
 
165
- # Instantiate fixtures for every test if requested.
166
- instantiate_fixtures if use_instantiated_fixtures
167
- end
168
-
169
- def teardown_fixtures
170
- # Rollback changes if a transaction is active.
171
- if run_in_transaction?
191
+ def teardown_transactional_fixtures
172
192
  ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
173
- @fixture_connections.each do |connection|
174
- connection.rollback_transaction if connection.transaction_open?
175
- connection.pool.lock_thread = false
193
+ unless @fixture_connection_pools.map(&:unpin_connection!).all?
194
+ # Something caused the transaction to be committed or rolled back
195
+ # We can no longer trust the database is in a clean state.
196
+ @@already_loaded_fixtures.clear
176
197
  end
177
- @fixture_connections.clear
198
+ @fixture_connection_pools.clear
178
199
  teardown_shared_connection_pool
179
- else
180
- ActiveRecord::FixtureSet.reset_cache
181
200
  end
182
201
 
183
- ActiveRecord::Base.clear_active_connections!
184
- end
185
-
186
- def enlist_fixture_connections
187
- setup_shared_connection_pool
188
-
189
- ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
190
- end
191
-
192
- private
193
202
  # Shares the writing connection pool with connections on
194
203
  # other handlers.
195
204
  #
@@ -197,79 +206,43 @@ module ActiveRecord
197
206
  # need to share a connection pool so that the reading connection
198
207
  # can see data in the open transaction on the writing connection.
199
208
  def setup_shared_connection_pool
200
- if ActiveRecord::Base.legacy_connection_handling
201
- writing_handler = ActiveRecord::Base.connection_handlers[ActiveRecord::Base.writing_role]
202
-
203
- ActiveRecord::Base.connection_handlers.values.each do |handler|
204
- if handler != writing_handler
205
- handler.connection_pool_names.each do |name|
206
- writing_pool_manager = writing_handler.send(:owner_to_pool_manager)[name]
207
- return unless writing_pool_manager
208
-
209
- pool_manager = handler.send(:owner_to_pool_manager)[name]
210
- @legacy_saved_pool_configs[handler][name] ||= {}
211
- pool_manager.shard_names.each do |shard_name|
212
- writing_pool_config = writing_pool_manager.get_pool_config(nil, shard_name)
213
- pool_config = pool_manager.get_pool_config(nil, shard_name)
214
- next if pool_config == writing_pool_config
215
-
216
- @legacy_saved_pool_configs[handler][name][shard_name] = pool_config
217
- pool_manager.set_pool_config(nil, shard_name, writing_pool_config)
218
- end
219
- end
220
- end
221
- end
222
- else
223
- handler = ActiveRecord::Base.connection_handler
224
-
225
- handler.connection_pool_names.each do |name|
226
- pool_manager = handler.send(:owner_to_pool_manager)[name]
227
- pool_manager.shard_names.each do |shard_name|
228
- writing_pool_config = pool_manager.get_pool_config(ActiveRecord::Base.writing_role, shard_name)
229
- @saved_pool_configs[name][shard_name] ||= {}
230
- pool_manager.role_names.each do |role|
231
- next unless pool_config = pool_manager.get_pool_config(role, shard_name)
232
- next if pool_config == writing_pool_config
233
-
234
- @saved_pool_configs[name][shard_name][role] = pool_config
235
- pool_manager.set_pool_config(role, shard_name, writing_pool_config)
236
- end
209
+ handler = ActiveRecord::Base.connection_handler
210
+
211
+ handler.connection_pool_names.each do |name|
212
+ pool_manager = handler.send(:connection_name_to_pool_manager)[name]
213
+ pool_manager.shard_names.each do |shard_name|
214
+ writing_pool_config = pool_manager.get_pool_config(ActiveRecord.writing_role, shard_name)
215
+ @saved_pool_configs[name][shard_name] ||= {}
216
+ pool_manager.role_names.each do |role|
217
+ next unless pool_config = pool_manager.get_pool_config(role, shard_name)
218
+ next if pool_config == writing_pool_config
219
+
220
+ @saved_pool_configs[name][shard_name][role] = pool_config
221
+ pool_manager.set_pool_config(role, shard_name, writing_pool_config)
237
222
  end
238
223
  end
239
224
  end
240
225
  end
241
226
 
242
227
  def teardown_shared_connection_pool
243
- if ActiveRecord::Base.legacy_connection_handling
244
- @legacy_saved_pool_configs.each_pair do |handler, names|
245
- names.each_pair do |name, shards|
246
- shards.each_pair do |shard_name, pool_config|
247
- pool_manager = handler.send(:owner_to_pool_manager)[name]
248
- pool_manager.set_pool_config(nil, shard_name, pool_config)
249
- end
250
- end
251
- end
252
- else
253
- handler = ActiveRecord::Base.connection_handler
228
+ handler = ActiveRecord::Base.connection_handler
254
229
 
255
- @saved_pool_configs.each_pair do |name, shards|
256
- pool_manager = handler.send(:owner_to_pool_manager)[name]
257
- shards.each_pair do |shard_name, roles|
258
- roles.each_pair do |role, pool_config|
259
- next unless pool_manager.get_pool_config(role, shard_name)
230
+ @saved_pool_configs.each_pair do |name, shards|
231
+ pool_manager = handler.send(:connection_name_to_pool_manager)[name]
232
+ shards.each_pair do |shard_name, roles|
233
+ roles.each_pair do |role, pool_config|
234
+ next unless pool_manager.get_pool_config(role, shard_name)
260
235
 
261
- pool_manager.set_pool_config(role, shard_name, pool_config)
262
- end
236
+ pool_manager.set_pool_config(role, shard_name, pool_config)
263
237
  end
264
238
  end
265
239
  end
266
240
 
267
- @legacy_saved_pool_configs.clear
268
241
  @saved_pool_configs.clear
269
242
  end
270
243
 
271
244
  def load_fixtures(config)
272
- ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config).index_by(&:name)
245
+ ActiveRecord::FixtureSet.create_fixtures(fixture_paths, fixture_table_names, fixture_class_names, config).index_by(&:name)
273
246
  end
274
247
 
275
248
  def instantiate_fixtures
@@ -287,5 +260,50 @@ module ActiveRecord
287
260
  def load_instances?
288
261
  use_instantiated_fixtures != :no_instances
289
262
  end
263
+
264
+ def method_missing(method, ...)
265
+ if fixture_sets.key?(method.name)
266
+ active_record_fixture(method, ...)
267
+ else
268
+ super
269
+ end
270
+ end
271
+
272
+ def respond_to_missing?(method, include_private = false)
273
+ if include_private && fixture_sets.key?(method.name)
274
+ true
275
+ else
276
+ super
277
+ end
278
+ end
279
+
280
+ def active_record_fixture(fixture_set_name, *fixture_names)
281
+ if fs_name = fixture_sets[fixture_set_name.name]
282
+ access_fixture(fs_name, *fixture_names)
283
+ else
284
+ raise StandardError, "No fixture set named '#{fixture_set_name.inspect}'"
285
+ end
286
+ end
287
+
288
+ def access_fixture(fs_name, *fixture_names)
289
+ force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
290
+ return_single_record = fixture_names.size == 1
291
+
292
+ fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
293
+ @fixture_cache[fs_name] ||= {}
294
+
295
+ instances = fixture_names.map do |f_name|
296
+ f_name = f_name.to_s if f_name.is_a?(Symbol)
297
+ @fixture_cache[fs_name].delete(f_name) if force_reload
298
+
299
+ if @loaded_fixtures[fs_name][f_name]
300
+ @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
301
+ else
302
+ raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
303
+ end
304
+ end
305
+
306
+ return_single_record ? instances.first : instances
307
+ end
290
308
  end
291
309
  end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Assertions
5
+ module QueryAssertions
6
+ # Asserts that the number of SQL queries executed in the given block matches the expected count.
7
+ #
8
+ # # Check for exact number of queries
9
+ # assert_queries_count(1) { Post.first }
10
+ #
11
+ # # Check for any number of queries
12
+ # assert_queries_count { Post.first }
13
+ #
14
+ # If the +:include_schema+ option is provided, any queries (including schema related) are counted.
15
+ #
16
+ # assert_queries_count(1, include_schema: true) { Post.columns }
17
+ #
18
+ def assert_queries_count(count = nil, include_schema: false, &block)
19
+ ActiveRecord::Base.lease_connection.materialize_transactions
20
+
21
+ counter = SQLCounter.new
22
+ ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
23
+ result = _assert_nothing_raised_or_warn("assert_queries_count", &block)
24
+ queries = include_schema ? counter.log_all : counter.log
25
+ if count
26
+ assert_equal count, queries.size, "#{queries.size} instead of #{count} queries were executed. Queries: #{queries.join("\n\n")}"
27
+ else
28
+ assert_operator queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
29
+ end
30
+ result
31
+ end
32
+ end
33
+
34
+ # Asserts that no SQL queries are executed in the given block.
35
+ #
36
+ # assert_no_queries { post.comments }
37
+ #
38
+ # If the +:include_schema+ option is provided, any queries (including schema related) are counted.
39
+ #
40
+ # assert_no_queries(include_schema: true) { Post.columns }
41
+ #
42
+ def assert_no_queries(include_schema: false, &block)
43
+ assert_queries_count(0, include_schema: include_schema, &block)
44
+ end
45
+
46
+ # Asserts that the SQL queries executed in the given block match expected pattern.
47
+ #
48
+ # # Check for exact number of queries
49
+ # assert_queries_match(/LIMIT \?/, count: 1) { Post.first }
50
+ #
51
+ # # Check for any number of queries
52
+ # assert_queries_match(/LIMIT \?/) { Post.first }
53
+ #
54
+ # If the +:include_schema+ option is provided, any queries (including schema related)
55
+ # that match the matcher are considered.
56
+ #
57
+ # assert_queries_match(/FROM pg_attribute/i, include_schema: true) { Post.columns }
58
+ #
59
+ def assert_queries_match(match, count: nil, include_schema: false, &block)
60
+ ActiveRecord::Base.lease_connection.materialize_transactions
61
+
62
+ counter = SQLCounter.new
63
+ ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
64
+ result = _assert_nothing_raised_or_warn("assert_queries_match", &block)
65
+ queries = include_schema ? counter.log_all : counter.log
66
+ matched_queries = queries.select { |query| match === query }
67
+
68
+ if count
69
+ assert_equal count, matched_queries.size, "#{matched_queries.size} instead of #{count} queries were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
70
+ else
71
+ assert_operator matched_queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
72
+ end
73
+
74
+ result
75
+ end
76
+ end
77
+
78
+ # Asserts that no SQL queries matching the pattern are executed in the given block.
79
+ #
80
+ # assert_no_queries_match(/SELECT/i) { post.comments }
81
+ #
82
+ # If the +:include_schema+ option is provided, any queries (including schema related)
83
+ # that match the matcher are counted.
84
+ #
85
+ # assert_no_queries_match(/FROM pg_attribute/i, include_schema: true) { Post.columns }
86
+ #
87
+ def assert_no_queries_match(match, include_schema: false, &block)
88
+ assert_queries_match(match, count: 0, include_schema: include_schema, &block)
89
+ end
90
+
91
+ class SQLCounter # :nodoc:
92
+ attr_reader :log_full, :log_all
93
+
94
+ def initialize
95
+ @log_full = []
96
+ @log_all = []
97
+ end
98
+
99
+ def log
100
+ @log_full.map(&:first)
101
+ end
102
+
103
+ def call(*, payload)
104
+ return if payload[:cached]
105
+
106
+ sql = payload[:sql]
107
+ @log_all << sql
108
+
109
+ unless payload[:name] == "SCHEMA"
110
+ bound_values = (payload[:binds] || []).map do |value|
111
+ value = value.value_for_database if value.respond_to?(:value_for_database)
112
+ value
113
+ end
114
+
115
+ @log_full << [sql, bound_values]
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -30,7 +30,7 @@ module ActiveRecord
30
30
  #
31
31
  # ActiveRecord::Base.time_zone_aware_types = [:datetime]
32
32
  #
33
- # You can also add database specific timezone aware types. For example, for PostgreSQL:
33
+ # You can also add database-specific timezone aware types. For example, for PostgreSQL:
34
34
  #
35
35
  # ActiveRecord::Base.time_zone_aware_types += [:tsrange, :tstzrange]
36
36
  #
@@ -54,8 +54,10 @@ module ActiveRecord
54
54
 
55
55
  module ClassMethods # :nodoc:
56
56
  def touch_attributes_with_time(*names, time: nil)
57
+ names = names.map(&:to_s)
58
+ names = names.map { |name| attribute_aliases[name] || name }
57
59
  attribute_names = timestamp_attributes_for_update_in_model
58
- attribute_names |= names.map(&:to_s)
60
+ attribute_names |= names
59
61
  attribute_names.index_with(time || current_time_from_proper_timezone)
60
62
  end
61
63
 
@@ -75,9 +77,17 @@ module ActiveRecord
75
77
  end
76
78
 
77
79
  def current_time_from_proper_timezone
78
- default_timezone == :utc ? Time.now.utc : Time.now
80
+ with_connection { |c| c.default_timezone == :utc ? Time.now.utc : Time.now }
79
81
  end
80
82
 
83
+ protected
84
+ def reload_schema_from_cache(recursive = true)
85
+ @timestamp_attributes_for_create_in_model = nil
86
+ @timestamp_attributes_for_update_in_model = nil
87
+ @all_timestamp_attributes_in_model = nil
88
+ super
89
+ end
90
+
81
91
  private
82
92
  def timestamp_attributes_for_create
83
93
  ["created_at", "created_on"].map! { |name| attribute_aliases[name] || name }
@@ -86,16 +96,14 @@ module ActiveRecord
86
96
  def timestamp_attributes_for_update
87
97
  ["updated_at", "updated_on"].map! { |name| attribute_aliases[name] || name }
88
98
  end
89
-
90
- def reload_schema_from_cache
91
- @timestamp_attributes_for_create_in_model = nil
92
- @timestamp_attributes_for_update_in_model = nil
93
- @all_timestamp_attributes_in_model = nil
94
- super
95
- end
96
99
  end
97
100
 
98
101
  private
102
+ def init_internals
103
+ super
104
+ @_touch_record = nil
105
+ end
106
+
99
107
  def _create_record
100
108
  if record_timestamps
101
109
  current_time = current_time_from_proper_timezone
@@ -109,6 +117,17 @@ module ActiveRecord
109
117
  end
110
118
 
111
119
  def _update_record
120
+ record_update_timestamps
121
+
122
+ super
123
+ end
124
+
125
+ def create_or_update(touch: true, **)
126
+ @_touch_record = touch
127
+ super
128
+ end
129
+
130
+ def record_update_timestamps
112
131
  if @_touch_record && should_record_timestamps?
113
132
  current_time = current_time_from_proper_timezone
114
133
 
@@ -118,16 +137,11 @@ module ActiveRecord
118
137
  end
119
138
  end
120
139
 
121
- super
122
- end
123
-
124
- def create_or_update(touch: true, **)
125
- @_touch_record = touch
126
- super
140
+ yield if block_given?
127
141
  end
128
142
 
129
143
  def should_record_timestamps?
130
- record_timestamps && (!partial_writes? || has_changes_to_save?)
144
+ record_timestamps && (!partial_updates? || has_changes_to_save?)
131
145
  end
132
146
 
133
147
  def timestamp_attributes_for_create_in_model
@@ -148,8 +162,7 @@ module ActiveRecord
148
162
 
149
163
  def max_updated_column_timestamp
150
164
  timestamp_attributes_for_update_in_model
151
- .map { |attr| self[attr]&.to_time }
152
- .compact
165
+ .filter_map { |attr| (v = self[attr]) && (v.is_a?(::Time) ? v : v.to_time) }
153
166
  .max
154
167
  end
155
168