activerecord 6.0.0 → 7.2.3

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 (376) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +996 -594
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +34 -34
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +22 -20
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +41 -30
  9. data/lib/active_record/associations/association.rb +106 -41
  10. data/lib/active_record/associations/association_scope.rb +30 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +69 -14
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
  13. data/lib/active_record/associations/builder/association.rb +39 -6
  14. data/lib/active_record/associations/builder/belongs_to.rb +47 -17
  15. data/lib/active_record/associations/builder/collection_association.rb +14 -6
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
  17. data/lib/active_record/associations/builder/has_many.rb +7 -3
  18. data/lib/active_record/associations/builder/has_one.rb +13 -16
  19. data/lib/active_record/associations/builder/singular_association.rb +7 -3
  20. data/lib/active_record/associations/collection_association.rb +90 -53
  21. data/lib/active_record/associations/collection_proxy.rb +54 -19
  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 +21 -1
  25. data/lib/active_record/associations/has_many_association.rb +41 -10
  26. data/lib/active_record/associations/has_many_through_association.rb +29 -12
  27. data/lib/active_record/associations/has_one_association.rb +33 -9
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
  30. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  31. data/lib/active_record/associations/join_dependency.rb +97 -54
  32. data/lib/active_record/associations/nested_error.rb +47 -0
  33. data/lib/active_record/associations/preloader/association.rb +237 -54
  34. data/lib/active_record/associations/preloader/batch.rb +48 -0
  35. data/lib/active_record/associations/preloader/branch.rb +153 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +51 -17
  37. data/lib/active_record/associations/preloader.rb +55 -121
  38. data/lib/active_record/associations/singular_association.rb +16 -4
  39. data/lib/active_record/associations/through_association.rb +26 -15
  40. data/lib/active_record/associations.rb +454 -440
  41. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  42. data/lib/active_record/attribute_assignment.rb +11 -14
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
  44. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  45. data/lib/active_record/attribute_methods/dirty.rb +75 -34
  46. data/lib/active_record/attribute_methods/primary_key.rb +53 -31
  47. data/lib/active_record/attribute_methods/query.rb +31 -22
  48. data/lib/active_record/attribute_methods/read.rb +16 -17
  49. data/lib/active_record/attribute_methods/serialization.rb +177 -35
  50. data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
  51. data/lib/active_record/attribute_methods/write.rb +16 -28
  52. data/lib/active_record/attribute_methods.rb +227 -100
  53. data/lib/active_record/attributes.rb +94 -56
  54. data/lib/active_record/autosave_association.rb +119 -73
  55. data/lib/active_record/base.rb +31 -21
  56. data/lib/active_record/callbacks.rb +168 -55
  57. data/lib/active_record/coders/column_serializer.rb +61 -0
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +70 -25
  60. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
  76. data/lib/active_record/connection_adapters/column.rb +28 -1
  77. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  78. data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
  79. data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
  80. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  81. data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
  82. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
  83. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
  84. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
  85. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  87. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  88. data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
  89. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  90. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  91. data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
  92. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
  95. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  101. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
  106. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  110. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
  111. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
  121. data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  123. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  124. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
  131. data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
  132. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  133. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  134. data/lib/active_record/connection_adapters.rb +176 -0
  135. data/lib/active_record/connection_handling.rb +243 -115
  136. data/lib/active_record/core.rb +481 -199
  137. data/lib/active_record/counter_cache.rb +69 -32
  138. data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
  139. data/lib/active_record/database_configurations/database_config.rb +77 -10
  140. data/lib/active_record/database_configurations/hash_config.rb +148 -26
  141. data/lib/active_record/database_configurations/url_config.rb +44 -45
  142. data/lib/active_record/database_configurations.rb +190 -114
  143. data/lib/active_record/delegated_type.rb +279 -0
  144. data/lib/active_record/deprecator.rb +7 -0
  145. data/lib/active_record/destroy_association_async_job.rb +38 -0
  146. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  147. data/lib/active_record/dynamic_matchers.rb +5 -6
  148. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  149. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  150. data/lib/active_record/encryption/cipher.rb +53 -0
  151. data/lib/active_record/encryption/config.rb +68 -0
  152. data/lib/active_record/encryption/configurable.rb +60 -0
  153. data/lib/active_record/encryption/context.rb +42 -0
  154. data/lib/active_record/encryption/contexts.rb +76 -0
  155. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  156. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  157. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  158. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  159. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  160. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  161. data/lib/active_record/encryption/encryptor.rb +171 -0
  162. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  163. data/lib/active_record/encryption/errors.rb +15 -0
  164. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  165. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  166. data/lib/active_record/encryption/key.rb +28 -0
  167. data/lib/active_record/encryption/key_generator.rb +53 -0
  168. data/lib/active_record/encryption/key_provider.rb +46 -0
  169. data/lib/active_record/encryption/message.rb +33 -0
  170. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  171. data/lib/active_record/encryption/message_serializer.rb +96 -0
  172. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  173. data/lib/active_record/encryption/properties.rb +76 -0
  174. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  175. data/lib/active_record/encryption/scheme.rb +100 -0
  176. data/lib/active_record/encryption.rb +58 -0
  177. data/lib/active_record/enum.rb +224 -73
  178. data/lib/active_record/errors.rb +254 -36
  179. data/lib/active_record/explain.rb +30 -17
  180. data/lib/active_record/explain_registry.rb +11 -6
  181. data/lib/active_record/explain_subscriber.rb +2 -2
  182. data/lib/active_record/fixture_set/file.rb +22 -15
  183. data/lib/active_record/fixture_set/model_metadata.rb +15 -6
  184. data/lib/active_record/fixture_set/render_context.rb +3 -1
  185. data/lib/active_record/fixture_set/table_row.rb +88 -16
  186. data/lib/active_record/fixture_set/table_rows.rb +4 -5
  187. data/lib/active_record/fixtures.rb +229 -116
  188. data/lib/active_record/future_result.rb +178 -0
  189. data/lib/active_record/gem_version.rb +4 -4
  190. data/lib/active_record/inheritance.rb +121 -48
  191. data/lib/active_record/insert_all.rb +178 -29
  192. data/lib/active_record/integration.rb +16 -14
  193. data/lib/active_record/internal_metadata.rb +132 -21
  194. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  195. data/lib/active_record/locking/optimistic.rb +64 -33
  196. data/lib/active_record/locking/pessimistic.rb +21 -8
  197. data/lib/active_record/log_subscriber.rb +61 -30
  198. data/lib/active_record/marshalling.rb +59 -0
  199. data/lib/active_record/message_pack.rb +124 -0
  200. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  201. data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
  202. data/lib/active_record/middleware/database_selector.rb +25 -13
  203. data/lib/active_record/middleware/shard_selector.rb +62 -0
  204. data/lib/active_record/migration/command_recorder.rb +160 -55
  205. data/lib/active_record/migration/compatibility.rb +286 -43
  206. data/lib/active_record/migration/default_strategy.rb +22 -0
  207. data/lib/active_record/migration/execution_strategy.rb +19 -0
  208. data/lib/active_record/migration/join_table.rb +1 -2
  209. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  210. data/lib/active_record/migration.rb +421 -193
  211. data/lib/active_record/model_schema.rb +217 -125
  212. data/lib/active_record/nested_attributes.rb +62 -27
  213. data/lib/active_record/no_touching.rb +4 -4
  214. data/lib/active_record/normalization.rb +163 -0
  215. data/lib/active_record/persistence.rb +322 -319
  216. data/lib/active_record/promise.rb +84 -0
  217. data/lib/active_record/query_cache.rb +18 -15
  218. data/lib/active_record/query_logs.rb +193 -0
  219. data/lib/active_record/query_logs_formatter.rb +41 -0
  220. data/lib/active_record/querying.rb +54 -14
  221. data/lib/active_record/railtie.rb +250 -72
  222. data/lib/active_record/railties/console_sandbox.rb +2 -4
  223. data/lib/active_record/railties/controller_runtime.rb +25 -11
  224. data/lib/active_record/railties/databases.rake +312 -197
  225. data/lib/active_record/railties/job_runtime.rb +23 -0
  226. data/lib/active_record/readonly_attributes.rb +45 -3
  227. data/lib/active_record/reflection.rb +389 -146
  228. data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
  229. data/lib/active_record/relation/batches.rb +214 -73
  230. data/lib/active_record/relation/calculations.rb +379 -124
  231. data/lib/active_record/relation/delegation.rb +36 -23
  232. data/lib/active_record/relation/finder_methods.rb +159 -49
  233. data/lib/active_record/relation/from_clause.rb +5 -1
  234. data/lib/active_record/relation/merger.rb +41 -33
  235. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
  236. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
  237. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
  238. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  239. data/lib/active_record/relation/predicate_builder.rb +79 -53
  240. data/lib/active_record/relation/query_attribute.rb +30 -12
  241. data/lib/active_record/relation/query_methods.rb +1156 -279
  242. data/lib/active_record/relation/record_fetch_warning.rb +12 -11
  243. data/lib/active_record/relation/spawn_methods.rb +10 -9
  244. data/lib/active_record/relation/where_clause.rb +100 -66
  245. data/lib/active_record/relation.rb +829 -194
  246. data/lib/active_record/result.rb +76 -56
  247. data/lib/active_record/runtime_registry.rb +71 -13
  248. data/lib/active_record/sanitization.rb +86 -47
  249. data/lib/active_record/schema.rb +39 -23
  250. data/lib/active_record/schema_dumper.rb +140 -33
  251. data/lib/active_record/schema_migration.rb +74 -29
  252. data/lib/active_record/scoping/default.rb +73 -19
  253. data/lib/active_record/scoping/named.rb +10 -28
  254. data/lib/active_record/scoping.rb +65 -35
  255. data/lib/active_record/secure_password.rb +60 -0
  256. data/lib/active_record/secure_token.rb +34 -8
  257. data/lib/active_record/serialization.rb +11 -4
  258. data/lib/active_record/signed_id.rb +138 -0
  259. data/lib/active_record/statement_cache.rb +26 -10
  260. data/lib/active_record/store.rb +19 -14
  261. data/lib/active_record/suppressor.rb +15 -17
  262. data/lib/active_record/table_metadata.rb +46 -36
  263. data/lib/active_record/tasks/database_tasks.rb +371 -205
  264. data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
  265. data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
  266. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
  267. data/lib/active_record/test_databases.rb +5 -4
  268. data/lib/active_record/test_fixtures.rb +189 -104
  269. data/lib/active_record/testing/query_assertions.rb +121 -0
  270. data/lib/active_record/timestamp.rb +35 -25
  271. data/lib/active_record/token_for.rb +123 -0
  272. data/lib/active_record/touch_later.rb +31 -27
  273. data/lib/active_record/transaction.rb +132 -0
  274. data/lib/active_record/transactions.rb +131 -99
  275. data/lib/active_record/translation.rb +3 -5
  276. data/lib/active_record/type/adapter_specific_registry.rb +33 -18
  277. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  278. data/lib/active_record/type/internal/timezone.rb +7 -2
  279. data/lib/active_record/type/serialized.rb +11 -6
  280. data/lib/active_record/type/time.rb +14 -0
  281. data/lib/active_record/type/type_map.rb +17 -21
  282. data/lib/active_record/type/unsigned_integer.rb +0 -1
  283. data/lib/active_record/type.rb +7 -2
  284. data/lib/active_record/type_caster/connection.rb +4 -5
  285. data/lib/active_record/type_caster/map.rb +8 -5
  286. data/lib/active_record/validations/absence.rb +1 -1
  287. data/lib/active_record/validations/associated.rb +13 -8
  288. data/lib/active_record/validations/numericality.rb +36 -0
  289. data/lib/active_record/validations/presence.rb +5 -28
  290. data/lib/active_record/validations/uniqueness.rb +88 -18
  291. data/lib/active_record/validations.rb +15 -8
  292. data/lib/active_record/version.rb +1 -1
  293. data/lib/active_record.rb +446 -40
  294. data/lib/arel/alias_predication.rb +1 -1
  295. data/lib/arel/attributes/attribute.rb +4 -8
  296. data/lib/arel/collectors/bind.rb +8 -1
  297. data/lib/arel/collectors/composite.rb +15 -0
  298. data/lib/arel/collectors/sql_string.rb +7 -0
  299. data/lib/arel/collectors/substitute_binds.rb +7 -0
  300. data/lib/arel/crud.rb +30 -22
  301. data/lib/arel/delete_manager.rb +23 -4
  302. data/lib/arel/errors.rb +10 -0
  303. data/lib/arel/factory_methods.rb +4 -0
  304. data/lib/arel/filter_predications.rb +9 -0
  305. data/lib/arel/insert_manager.rb +2 -3
  306. data/lib/arel/nodes/binary.rb +82 -9
  307. data/lib/arel/nodes/bind_param.rb +8 -0
  308. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  309. data/lib/arel/nodes/casted.rb +22 -10
  310. data/lib/arel/nodes/cte.rb +36 -0
  311. data/lib/arel/nodes/delete_statement.rb +14 -13
  312. data/lib/arel/nodes/equality.rb +6 -9
  313. data/lib/arel/nodes/filter.rb +10 -0
  314. data/lib/arel/nodes/fragments.rb +35 -0
  315. data/lib/arel/nodes/function.rb +1 -0
  316. data/lib/arel/nodes/grouping.rb +3 -0
  317. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  318. data/lib/arel/nodes/in.rb +8 -1
  319. data/lib/arel/nodes/infix_operation.rb +13 -1
  320. data/lib/arel/nodes/insert_statement.rb +2 -2
  321. data/lib/arel/nodes/join_source.rb +1 -1
  322. data/lib/arel/nodes/leading_join.rb +8 -0
  323. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  324. data/lib/arel/nodes/node.rb +122 -11
  325. data/lib/arel/nodes/ordering.rb +27 -0
  326. data/lib/arel/nodes/select_core.rb +2 -2
  327. data/lib/arel/nodes/select_statement.rb +2 -2
  328. data/lib/arel/nodes/sql_literal.rb +16 -0
  329. data/lib/arel/nodes/table_alias.rb +11 -3
  330. data/lib/arel/nodes/unary.rb +0 -1
  331. data/lib/arel/nodes/update_statement.rb +11 -4
  332. data/lib/arel/nodes.rb +10 -3
  333. data/lib/arel/predications.rb +31 -28
  334. data/lib/arel/select_manager.rb +18 -9
  335. data/lib/arel/table.rb +21 -10
  336. data/lib/arel/tree_manager.rb +8 -15
  337. data/lib/arel/update_manager.rb +25 -5
  338. data/lib/arel/visitors/dot.rb +94 -90
  339. data/lib/arel/visitors/mysql.rb +34 -6
  340. data/lib/arel/visitors/postgresql.rb +5 -16
  341. data/lib/arel/visitors/sqlite.rb +25 -1
  342. data/lib/arel/visitors/to_sql.rb +227 -81
  343. data/lib/arel/visitors/visitor.rb +2 -3
  344. data/lib/arel/visitors.rb +0 -7
  345. data/lib/arel.rb +37 -15
  346. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  347. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  348. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  349. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  350. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
  351. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  352. data/lib/rails/generators/active_record/migration.rb +9 -3
  353. data/lib/rails/generators/active_record/model/USAGE +113 -0
  354. data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
  355. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  356. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  357. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  358. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  359. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  360. metadata +117 -30
  361. data/lib/active_record/attribute_decorators.rb +0 -90
  362. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  363. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  364. data/lib/active_record/define_callbacks.rb +0 -22
  365. data/lib/active_record/null_relation.rb +0 -68
  366. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  367. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  368. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  369. data/lib/arel/attributes.rb +0 -22
  370. data/lib/arel/visitors/depth_first.rb +0 -204
  371. data/lib/arel/visitors/ibm_db.rb +0 -34
  372. data/lib/arel/visitors/informix.rb +0 -62
  373. data/lib/arel/visitors/mssql.rb +0 -157
  374. data/lib/arel/visitors/oracle.rb +0 -159
  375. data/lib/arel/visitors/oracle12.rb +0 -66
  376. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -1,29 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/hash/indifferent_access"
4
- require "active_support/core_ext/string/filters"
3
+ require "active_support/core_ext/enumerable"
4
+ require "active_support/core_ext/module/delegation"
5
5
  require "active_support/parameter_filter"
6
6
  require "concurrent/map"
7
7
 
8
8
  module ActiveRecord
9
+ # = Active Record \Core
9
10
  module Core
10
11
  extend ActiveSupport::Concern
12
+ include ActiveModel::Access
11
13
 
12
14
  included do
13
15
  ##
14
16
  # :singleton-method:
15
17
  #
16
- # Accepts a logger conforming to the interface of Log4r which is then
17
- # passed on to any new database connections made and which can be
18
- # retrieved on both a class and instance level by calling +logger+.
19
- mattr_accessor :logger, instance_writer: false
18
+ # Accepts a logger conforming to the interface of Log4r or the default
19
+ # Ruby +Logger+ class, which is then passed on to any new database
20
+ # connections made. You can retrieve this logger by calling +logger+ on
21
+ # either an Active Record model class or an Active Record model instance.
22
+ class_attribute :logger, instance_writer: false
23
+
24
+ class_attribute :_destroy_association_async_job, instance_accessor: false, default: "ActiveRecord::DestroyAssociationAsyncJob"
25
+
26
+ # The job class used to destroy associations in the background.
27
+ def self.destroy_association_async_job
28
+ if _destroy_association_async_job.is_a?(String)
29
+ self._destroy_association_async_job = _destroy_association_async_job.constantize
30
+ end
31
+ _destroy_association_async_job
32
+ rescue NameError => error
33
+ raise NameError, "Unable to load destroy_association_async_job: #{error.message}"
34
+ end
35
+
36
+ singleton_class.alias_method :destroy_association_async_job=, :_destroy_association_async_job=
37
+ delegate :destroy_association_async_job, to: :class
20
38
 
21
39
  ##
22
40
  # :singleton-method:
23
41
  #
24
- # Specifies if the methods calling database queries should be logged below
25
- # their relevant queries. Defaults to false.
26
- mattr_accessor :verbose_query_logs, instance_writer: false, default: false
42
+ # Specifies the maximum number of records that will be destroyed in a
43
+ # single background job by the <tt>dependent: :destroy_async</tt>
44
+ # association option. When +nil+ (default), all dependent records will be
45
+ # destroyed in a single background job. If specified, the records to be
46
+ # destroyed will be split into multiple background jobs.
47
+ class_attribute :destroy_association_async_batch_size, instance_writer: false, instance_predicate: false, default: nil
27
48
 
28
49
  ##
29
50
  # Contains the database configuration - as is typically stored in config/database.yml -
@@ -33,114 +54,200 @@ module ActiveRecord
33
54
  #
34
55
  # development:
35
56
  # adapter: sqlite3
36
- # database: db/development.sqlite3
57
+ # database: storage/development.sqlite3
37
58
  #
38
59
  # production:
39
60
  # adapter: sqlite3
40
- # database: db/production.sqlite3
61
+ # database: storage/production.sqlite3
41
62
  #
42
63
  # ...would result in ActiveRecord::Base.configurations to look like this:
43
64
  #
44
65
  # #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
45
66
  # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
46
- # @spec_name="primary", @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>,
67
+ # @name="primary", @config={adapter: "sqlite3", database: "storage/development.sqlite3"}>,
47
68
  # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
48
- # @spec_name="primary", @config={"adapter"=>"mysql2", "database"=>"db/production.sqlite3"}>
69
+ # @name="primary", @config={adapter: "sqlite3", database: "storage/production.sqlite3"}>
49
70
  # ]>
50
71
  def self.configurations=(config)
51
72
  @@configurations = ActiveRecord::DatabaseConfigurations.new(config)
52
73
  end
53
74
  self.configurations = {}
54
75
 
55
- # Returns fully resolved ActiveRecord::DatabaseConfigurations object
76
+ # Returns a fully resolved ActiveRecord::DatabaseConfigurations object.
56
77
  def self.configurations
57
78
  @@configurations
58
79
  end
59
80
 
60
81
  ##
61
82
  # :singleton-method:
62
- # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
63
- # dates and times from the database. This is set to :utc by default.
64
- mattr_accessor :default_timezone, instance_writer: false, default: :utc
83
+ # Force enumeration of all columns in SELECT statements.
84
+ # e.g. <tt>SELECT first_name, last_name FROM ...</tt> instead of <tt>SELECT * FROM ...</tt>
85
+ # This avoids +PreparedStatementCacheExpired+ errors when a column is added
86
+ # to the database while the app is running.
87
+ class_attribute :enumerate_columns_in_select_statements, instance_accessor: false, default: false
65
88
 
66
- ##
67
- # :singleton-method:
68
- # Specifies the format to use when dumping the database schema with Rails'
69
- # Rakefile. If :sql, the schema is dumped as (potentially database-
70
- # specific) SQL statements. If :ruby, the schema is dumped as an
71
- # ActiveRecord::Schema file which can be loaded into any database that
72
- # supports migrations. Use :ruby if you want to have different database
73
- # adapters for, e.g., your development and test environments.
74
- mattr_accessor :schema_format, instance_writer: false, default: :ruby
89
+ class_attribute :belongs_to_required_by_default, instance_accessor: false
75
90
 
76
- ##
77
- # :singleton-method:
78
- # Specifies if an error should be raised if the query has an order being
79
- # ignored when doing batch queries. Useful in applications where the
80
- # scope being ignored is error-worthy, rather than a warning.
81
- mattr_accessor :error_on_ignored_order, instance_writer: false, default: false
91
+ class_attribute :strict_loading_by_default, instance_accessor: false, default: false
82
92
 
83
- # :singleton-method:
84
- # Specify the behavior for unsafe raw query methods. Values are as follows
85
- # deprecated - Warnings are logged when unsafe raw SQL is passed to
86
- # query methods.
87
- # disabled - Unsafe raw SQL passed to query methods results in
88
- # UnknownAttributeReference exception.
89
- mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :deprecated
93
+ class_attribute :has_many_inversing, instance_accessor: false, default: false
90
94
 
91
- ##
92
- # :singleton-method:
93
- # Specify whether or not to use timestamps for migration versions
94
- mattr_accessor :timestamped_migrations, instance_writer: false, default: true
95
+ class_attribute :run_commit_callbacks_on_first_saved_instances_in_transaction, instance_accessor: false, default: true
95
96
 
96
- ##
97
- # :singleton-method:
98
- # Specify whether schema dump should happen at the end of the
99
- # db:migrate rails command. This is true by default, which is useful for the
100
- # development environment. This should ideally be false in the production
101
- # environment where dumping schema is rarely needed.
102
- mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
97
+ class_attribute :default_connection_handler, instance_writer: false
103
98
 
104
- ##
105
- # :singleton-method:
106
- # Specifies which database schemas to dump when calling db:structure:dump.
107
- # If the value is :schema_search_path (the default), any schemas listed in
108
- # schema_search_path are dumped. Use :all to dump all schemas regardless
109
- # of schema_search_path, or a string of comma separated schemas for a
110
- # custom list.
111
- mattr_accessor :dump_schemas, instance_writer: false, default: :schema_search_path
99
+ class_attribute :default_role, instance_writer: false
100
+
101
+ class_attribute :default_shard, instance_writer: false
102
+
103
+ class_attribute :shard_selector, instance_accessor: false, default: nil
112
104
 
113
105
  ##
114
106
  # :singleton-method:
115
- # Specify a threshold for the size of query result sets. If the number of
116
- # records in the set exceeds the threshold, a warning is logged. This can
117
- # be used to identify queries which load thousands of records and
118
- # potentially cause memory bloat.
119
- mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false
107
+ #
108
+ # Specifies the attributes that will be included in the output of the
109
+ # #inspect method:
110
+ #
111
+ # Post.attributes_for_inspect = [:id, :title]
112
+ # Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">"
113
+ #
114
+ # When set to `:all` inspect will list all the record's attributes:
115
+ #
116
+ # Post.attributes_for_inspect = :all
117
+ # Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
118
+ class_attribute :attributes_for_inspect, instance_accessor: false, default: :all
120
119
 
121
- mattr_accessor :maintain_test_schema, instance_accessor: false
120
+ def self.application_record_class? # :nodoc:
121
+ if ActiveRecord.application_record_class
122
+ self == ActiveRecord.application_record_class
123
+ else
124
+ if defined?(ApplicationRecord) && self == ApplicationRecord
125
+ true
126
+ end
127
+ end
128
+ end
122
129
 
123
- mattr_accessor :belongs_to_required_by_default, instance_accessor: false
130
+ self.filter_attributes = []
124
131
 
125
- mattr_accessor :connection_handlers, instance_accessor: false, default: {}
132
+ def self.connection_handler
133
+ ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] || default_connection_handler
134
+ end
126
135
 
127
- mattr_accessor :writing_role, instance_accessor: false, default: :writing
136
+ def self.connection_handler=(handler)
137
+ ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] = handler
138
+ end
128
139
 
129
- mattr_accessor :reading_role, instance_accessor: false, default: :reading
140
+ def self.asynchronous_queries_session # :nodoc:
141
+ asynchronous_queries_tracker.current_session
142
+ end
130
143
 
131
- class_attribute :default_connection_handler, instance_writer: false
144
+ def self.asynchronous_queries_tracker # :nodoc:
145
+ ActiveSupport::IsolatedExecutionState[:active_record_asynchronous_queries_tracker] ||= \
146
+ AsynchronousQueriesTracker.new
147
+ end
132
148
 
133
- self.filter_attributes = []
149
+ # Returns the symbol representing the current connected role.
150
+ #
151
+ # ActiveRecord::Base.connected_to(role: :writing) do
152
+ # ActiveRecord::Base.current_role #=> :writing
153
+ # end
154
+ #
155
+ # ActiveRecord::Base.connected_to(role: :reading) do
156
+ # ActiveRecord::Base.current_role #=> :reading
157
+ # end
158
+ def self.current_role
159
+ connected_to_stack.reverse_each do |hash|
160
+ return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
161
+ return hash[:role] if hash[:role] && hash[:klasses].include?(connection_class_for_self)
162
+ end
134
163
 
135
- def self.connection_handler
136
- Thread.current.thread_variable_get("ar_connection_handler") || default_connection_handler
164
+ default_role
137
165
  end
138
166
 
139
- def self.connection_handler=(handler)
140
- Thread.current.thread_variable_set("ar_connection_handler", handler)
167
+ # Returns the symbol representing the current connected shard.
168
+ #
169
+ # ActiveRecord::Base.connected_to(role: :reading) do
170
+ # ActiveRecord::Base.current_shard #=> :default
171
+ # end
172
+ #
173
+ # ActiveRecord::Base.connected_to(role: :writing, shard: :one) do
174
+ # ActiveRecord::Base.current_shard #=> :one
175
+ # end
176
+ def self.current_shard
177
+ connected_to_stack.reverse_each do |hash|
178
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base)
179
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_class_for_self)
180
+ end
181
+
182
+ default_shard
183
+ end
184
+
185
+ # Returns the symbol representing the current setting for
186
+ # preventing writes.
187
+ #
188
+ # ActiveRecord::Base.connected_to(role: :reading) do
189
+ # ActiveRecord::Base.current_preventing_writes #=> true
190
+ # end
191
+ #
192
+ # ActiveRecord::Base.connected_to(role: :writing) do
193
+ # ActiveRecord::Base.current_preventing_writes #=> false
194
+ # end
195
+ def self.current_preventing_writes
196
+ connected_to_stack.reverse_each do |hash|
197
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
198
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_class_for_self)
199
+ end
200
+
201
+ false
202
+ end
203
+
204
+ def self.connected_to_stack # :nodoc:
205
+ if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack]
206
+ connected_to_stack
207
+ else
208
+ connected_to_stack = Concurrent::Array.new
209
+ ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack] = connected_to_stack
210
+ connected_to_stack
211
+ end
212
+ end
213
+
214
+ def self.connection_class=(b) # :nodoc:
215
+ @connection_class = b
216
+ end
217
+
218
+ def self.connection_class # :nodoc:
219
+ @connection_class ||= false
220
+ end
221
+
222
+ def self.connection_class? # :nodoc:
223
+ self.connection_class
224
+ end
225
+
226
+ def self.connection_class_for_self # :nodoc:
227
+ klass = self
228
+
229
+ until klass == Base
230
+ break if klass.connection_class?
231
+ klass = klass.superclass
232
+ end
233
+
234
+ klass
141
235
  end
142
236
 
143
237
  self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
238
+ self.default_role = ActiveRecord.writing_role
239
+ self.default_shard = :default
240
+
241
+ def self.strict_loading_violation!(owner:, reflection:) # :nodoc:
242
+ case ActiveRecord.action_on_strict_loading_violation
243
+ when :raise
244
+ message = reflection.strict_loading_violation_message(owner)
245
+ raise ActiveRecord::StrictLoadingViolationError.new(message)
246
+ when :log
247
+ name = "strict_loading_violation.active_record"
248
+ ActiveSupport::Notifications.instrument(name, owner: owner, reflection: reflection)
249
+ end
250
+ end
144
251
  end
145
252
 
146
253
  module ClassMethods
@@ -148,67 +255,68 @@ module ActiveRecord
148
255
  @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
149
256
  end
150
257
 
151
- def inherited(child_class) # :nodoc:
152
- # initialize cache at class definition for thread safety
153
- child_class.initialize_find_by_cache
154
- super
155
- end
156
-
157
258
  def find(*ids) # :nodoc:
158
259
  # We don't have cache keys for this stuff yet
159
260
  return super unless ids.length == 1
160
- return super if block_given? ||
161
- primary_key.nil? ||
162
- scope_attributes? ||
163
- columns_hash.key?(inheritance_column) && !base_class?
261
+ return super if block_given? || primary_key.nil? || scope_attributes?
164
262
 
165
263
  id = ids.first
166
264
 
167
265
  return super if StatementCache.unsupported_value?(id)
168
266
 
169
- key = primary_key
170
-
171
- statement = cached_find_by_statement(key) { |params|
172
- where(key => params.bind).limit(1)
173
- }
174
-
175
- record = statement.execute([id], connection)&.first
176
- unless record
177
- raise RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
178
- end
179
- record
267
+ cached_find_by([primary_key], [id]) ||
268
+ raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id.inspect}", name, primary_key, id))
180
269
  end
181
270
 
182
271
  def find_by(*args) # :nodoc:
183
- return super if scope_attributes? || reflect_on_all_aggregations.any? ||
184
- columns_hash.key?(inheritance_column) && !base_class?
272
+ return super if scope_attributes?
185
273
 
186
274
  hash = args.first
275
+ return super unless Hash === hash
276
+
277
+ hash = hash.each_with_object({}) do |(key, value), h|
278
+ key = key.to_s
279
+ key = attribute_aliases[key] || key
280
+
281
+ return super if reflect_on_aggregation(key)
282
+
283
+ reflection = _reflect_on_association(key)
284
+
285
+ if !reflection
286
+ value = value.id if value.respond_to?(:id)
287
+ elsif reflection.belongs_to? && !reflection.polymorphic?
288
+ key = reflection.join_foreign_key
289
+ pkey = reflection.join_primary_key
290
+
291
+ if pkey.is_a?(Array)
292
+ if pkey.all? { |attribute| value.respond_to?(attribute) }
293
+ value = pkey.map do |attribute|
294
+ if attribute == "id"
295
+ value.id_value
296
+ else
297
+ value.public_send(attribute)
298
+ end
299
+ end
300
+ composite_primary_key = true
301
+ end
302
+ else
303
+ value = value.public_send(pkey) if value.respond_to?(pkey)
304
+ end
305
+ end
306
+
307
+ if !composite_primary_key &&
308
+ (!columns_hash.key?(key) || StatementCache.unsupported_value?(value))
309
+ return super
310
+ end
187
311
 
188
- return super if !(Hash === hash) || hash.values.any? { |v|
189
- StatementCache.unsupported_value?(v)
190
- }
191
-
192
- # We can't cache Post.find_by(author: david) ...yet
193
- return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) }
194
-
195
- keys = hash.keys
196
-
197
- statement = cached_find_by_statement(keys) { |params|
198
- wheres = keys.each_with_object({}) { |param, o|
199
- o[param] = params.bind
200
- }
201
- where(wheres).limit(1)
202
- }
203
- begin
204
- statement.execute(hash.values, connection)&.first
205
- rescue TypeError
206
- raise ActiveRecord::StatementInvalid
312
+ h[key] = value
207
313
  end
314
+
315
+ cached_find_by(hash.keys, hash.values)
208
316
  end
209
317
 
210
318
  def find_by!(*args) # :nodoc:
211
- find_by(*args) || raise(RecordNotFound.new("Couldn't find #{name}", name))
319
+ find_by(*args) || where(*args).raise_record_not_found_exception!
212
320
  end
213
321
 
214
322
  def initialize_generated_modules # :nodoc:
@@ -227,24 +335,38 @@ module ActiveRecord
227
335
 
228
336
  # Returns columns which shouldn't be exposed while calling +#inspect+.
229
337
  def filter_attributes
230
- if defined?(@filter_attributes)
231
- @filter_attributes
232
- else
338
+ if @filter_attributes.nil?
233
339
  superclass.filter_attributes
340
+ else
341
+ @filter_attributes
234
342
  end
235
343
  end
236
344
 
237
345
  # Specifies columns which shouldn't be exposed while calling +#inspect+.
238
- attr_writer :filter_attributes
346
+ def filter_attributes=(filter_attributes)
347
+ @inspection_filter = nil
348
+ @filter_attributes = filter_attributes
349
+ end
350
+
351
+ def inspection_filter # :nodoc:
352
+ if @filter_attributes.nil?
353
+ superclass.inspection_filter
354
+ else
355
+ @inspection_filter ||= begin
356
+ mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
357
+ ActiveSupport::ParameterFilter.new(@filter_attributes, mask: mask)
358
+ end
359
+ end
360
+ end
239
361
 
240
362
  # Returns a string like 'Post(id:integer, title:string, body:text)'
241
363
  def inspect # :nodoc:
242
- if self == Base
364
+ if self == Base || singleton_class?
243
365
  super
244
366
  elsif abstract_class?
245
367
  "#{super}(abstract)"
246
- elsif !connected?
247
- "#{super} (call '#{super}.connection' to establish a connection)"
368
+ elsif !schema_loaded? && !connected?
369
+ "#{super} (call '#{super}.load_schema' to load schema informations)"
248
370
  elsif table_exists?
249
371
  attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
250
372
  "#{super}(#{attr_list})"
@@ -253,24 +375,9 @@ module ActiveRecord
253
375
  end
254
376
  end
255
377
 
256
- # Overwrite the default class equality method to provide support for decorated models.
257
- def ===(object) # :nodoc:
258
- object.is_a?(self)
259
- end
260
-
261
- # Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
262
- #
263
- # class Post < ActiveRecord::Base
264
- # scope :published_and_commented, -> { published.and(arel_table[:comments_count].gt(0)) }
265
- # end
378
+ # Returns an instance of +Arel::Table+ loaded with the current table name.
266
379
  def arel_table # :nodoc:
267
- @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster)
268
- end
269
-
270
- def arel_attribute(name, table = arel_table) # :nodoc:
271
- name = name.to_s
272
- name = attribute_aliases[name] || name
273
- table[name]
380
+ @arel_table ||= Arel::Table.new(table_name, klass: self)
274
381
  end
275
382
 
276
383
  def predicate_builder # :nodoc:
@@ -281,15 +388,32 @@ module ActiveRecord
281
388
  TypeCaster::Map.new(self)
282
389
  end
283
390
 
284
- def _internal? # :nodoc:
285
- false
391
+ def cached_find_by_statement(connection, key, &block) # :nodoc:
392
+ cache = @find_by_statement_cache[connection.prepared_statements]
393
+ cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
286
394
  end
287
395
 
288
396
  private
397
+ def inherited(subclass)
398
+ super
289
399
 
290
- def cached_find_by_statement(key, &block)
291
- cache = @find_by_statement_cache[connection.prepared_statements]
292
- cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
400
+ # initialize cache at class definition for thread safety
401
+ subclass.initialize_find_by_cache
402
+ unless subclass.base_class?
403
+ klass = self
404
+ until klass.base_class?
405
+ klass.initialize_find_by_cache
406
+ klass = klass.superclass
407
+ end
408
+ end
409
+
410
+ subclass.class_eval do
411
+ @arel_table = nil
412
+ @predicate_builder = nil
413
+ @inspection_filter = nil
414
+ @filter_attributes ||= nil
415
+ @generated_association_methods ||= nil
416
+ end
293
417
  end
294
418
 
295
419
  def relation
@@ -297,7 +421,6 @@ module ActiveRecord
297
421
 
298
422
  if finder_needs_type_condition? && !ignore_default_scope?
299
423
  relation.where!(type_condition)
300
- relation.create_with!(inheritance_column.to_s => sti_name)
301
424
  else
302
425
  relation
303
426
  end
@@ -306,6 +429,27 @@ module ActiveRecord
306
429
  def table_metadata
307
430
  TableMetadata.new(self, arel_table)
308
431
  end
432
+
433
+ def cached_find_by(keys, values)
434
+ with_connection do |connection|
435
+ statement = cached_find_by_statement(connection, keys) { |params|
436
+ wheres = keys.index_with do |key|
437
+ if key.is_a?(Array)
438
+ [key.map { params.bind }]
439
+ else
440
+ params.bind
441
+ end
442
+ end
443
+ where(wheres).limit(1)
444
+ }
445
+
446
+ begin
447
+ statement.execute(values.flatten, connection, allow_retry: true).first
448
+ rescue TypeError
449
+ raise ActiveRecord::StatementInvalid
450
+ end
451
+ end
452
+ end
309
453
  end
310
454
 
311
455
  # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
@@ -313,7 +457,7 @@ module ActiveRecord
313
457
  # In both instances, valid attribute keys are determined by the column names of the associated table --
314
458
  # hence you can't have attributes that aren't part of the table columns.
315
459
  #
316
- # ==== Example:
460
+ # ==== Example
317
461
  # # Instantiates a single new object
318
462
  # User.new(first_name: 'Jamie')
319
463
  def initialize(attributes = nil)
@@ -323,7 +467,7 @@ module ActiveRecord
323
467
  init_internals
324
468
  initialize_internals_callback
325
469
 
326
- assign_attributes(attributes) if attributes
470
+ super
327
471
 
328
472
  yield self if block_given?
329
473
  _run_initialize_callbacks
@@ -344,7 +488,7 @@ module ActiveRecord
344
488
  # post.init_with(coder)
345
489
  # post.title # => 'hello world'
346
490
  def init_with(coder, &block)
347
- coder = LegacyYamlAdapter.convert(self.class, coder)
491
+ coder = LegacyYamlAdapter.convert(coder)
348
492
  attributes = self.class.yaml_encoder.decode(coder)
349
493
  init_with_attributes(attributes, coder["new_record"], &block)
350
494
  end
@@ -391,19 +535,24 @@ module ActiveRecord
391
535
  # only, not its associations. The extent of a "deep" copy is application
392
536
  # specific and is therefore left to the application to implement according
393
537
  # to its need.
394
- # The dup method does not preserve the timestamps (created|updated)_(at|on).
538
+ # The dup method does not preserve the timestamps (created|updated)_(at|on)
539
+ # and locking column.
395
540
 
396
541
  ##
397
542
  def initialize_dup(other) # :nodoc:
398
543
  @attributes = @attributes.deep_dup
399
- @attributes.reset(@primary_key)
544
+ if self.class.composite_primary_key?
545
+ @primary_key.each { |key| @attributes.reset(key) }
546
+ else
547
+ @attributes.reset(@primary_key)
548
+ end
400
549
 
401
550
  _run_initialize_callbacks
402
551
 
403
552
  @new_record = true
553
+ @previously_new_record = false
404
554
  @destroyed = false
405
555
  @_start_transaction_state = nil
406
- @transaction_state = nil
407
556
 
408
557
  super
409
558
  end
@@ -426,6 +575,35 @@ module ActiveRecord
426
575
  coder["active_record_yaml_version"] = 2
427
576
  end
428
577
 
578
+ ##
579
+ # :method: slice
580
+ #
581
+ # :call-seq: slice(*methods)
582
+ #
583
+ # Returns a hash of the given methods with their names as keys and returned
584
+ # values as values.
585
+ #
586
+ # topic = Topic.new(title: "Budget", author_name: "Jason")
587
+ # topic.slice(:title, :author_name)
588
+ # # => { "title" => "Budget", "author_name" => "Jason" }
589
+ #
590
+ #--
591
+ # Implemented by ActiveModel::Access#slice.
592
+
593
+ ##
594
+ # :method: values_at
595
+ #
596
+ # :call-seq: values_at(*methods)
597
+ #
598
+ # Returns an array of the values returned by the given methods.
599
+ #
600
+ # topic = Topic.new(title: "Budget", author_name: "Jason")
601
+ # topic.values_at(:title, :author_name)
602
+ # # => ["Budget", "Jason"]
603
+ #
604
+ #--
605
+ # Implemented by ActiveModel::Access#values_at.
606
+
429
607
  # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
430
608
  # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
431
609
  #
@@ -438,7 +616,7 @@ module ActiveRecord
438
616
  def ==(comparison_object)
439
617
  super ||
440
618
  comparison_object.instance_of?(self.class) &&
441
- !id.nil? &&
619
+ primary_key_values_present? &&
442
620
  comparison_object.id == id
443
621
  end
444
622
  alias :eql? :==
@@ -446,7 +624,9 @@ module ActiveRecord
446
624
  # Delegates to id in order to allow two records of the same type and id to work with something like:
447
625
  # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
448
626
  def hash
449
- if id
627
+ id = self.id
628
+
629
+ if primary_key_values_present?
450
630
  self.class.hash ^ id.hash
451
631
  else
452
632
  super
@@ -463,7 +643,6 @@ module ActiveRecord
463
643
 
464
644
  # Returns +true+ if the attributes hash has been frozen.
465
645
  def frozen?
466
- sync_with_transaction_state if @transaction_state&.finalized?
467
646
  @attributes.frozen?
468
647
  end
469
648
 
@@ -484,13 +663,91 @@ module ActiveRecord
484
663
  false
485
664
  end
486
665
 
487
- # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
488
- # attributes will be marked as read only since they cannot be saved.
666
+ # Returns +true+ if the record is read only.
489
667
  def readonly?
490
668
  @readonly
491
669
  end
492
670
 
493
- # Marks this record as read only.
671
+ # Returns +true+ if the record is in strict_loading mode.
672
+ def strict_loading?
673
+ @strict_loading
674
+ end
675
+
676
+ # Sets the record to strict_loading mode. This will raise an error
677
+ # if the record tries to lazily load an association.
678
+ #
679
+ # NOTE: Strict loading is disabled during validation in order to let the record validate its association.
680
+ #
681
+ # user = User.first
682
+ # user.strict_loading! # => true
683
+ # user.address.city
684
+ # # => ActiveRecord::StrictLoadingViolationError
685
+ # user.comments.to_a
686
+ # # => ActiveRecord::StrictLoadingViolationError
687
+ #
688
+ # ==== Parameters
689
+ #
690
+ # * +value+ - Boolean specifying whether to enable or disable strict loading.
691
+ # * <tt>:mode</tt> - Symbol specifying strict loading mode. Defaults to :all. Using
692
+ # :n_plus_one_only mode will only raise an error if an association that
693
+ # will lead to an n plus one query is lazily loaded.
694
+ #
695
+ # ==== Examples
696
+ #
697
+ # user = User.first
698
+ # user.strict_loading!(false) # => false
699
+ # user.address.city # => "Tatooine"
700
+ # user.comments.to_a # => [#<Comment:0x00...]
701
+ #
702
+ # user.strict_loading!(mode: :n_plus_one_only)
703
+ # user.address.city # => "Tatooine"
704
+ # user.comments.to_a # => [#<Comment:0x00...]
705
+ # user.comments.first.ratings.to_a
706
+ # # => ActiveRecord::StrictLoadingViolationError
707
+ def strict_loading!(value = true, mode: :all)
708
+ unless [:all, :n_plus_one_only].include?(mode)
709
+ raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided."
710
+ end
711
+
712
+ @strict_loading_mode = mode
713
+ @strict_loading = value
714
+ end
715
+
716
+ attr_reader :strict_loading_mode
717
+
718
+ # Returns +true+ if the record uses strict_loading with +:n_plus_one_only+ mode enabled.
719
+ def strict_loading_n_plus_one_only?
720
+ @strict_loading_mode == :n_plus_one_only
721
+ end
722
+
723
+ # Returns +true+ if the record uses strict_loading with +:all+ mode enabled.
724
+ def strict_loading_all?
725
+ @strict_loading_mode == :all
726
+ end
727
+
728
+ # Prevents records from being written to the database:
729
+ #
730
+ # customer = Customer.new
731
+ # customer.readonly!
732
+ # customer.save # raises ActiveRecord::ReadOnlyRecord
733
+ #
734
+ # customer = Customer.first
735
+ # customer.readonly!
736
+ # customer.update(name: 'New Name') # raises ActiveRecord::ReadOnlyRecord
737
+ #
738
+ # Read-only records cannot be deleted from the database either:
739
+ #
740
+ # customer = Customer.first
741
+ # customer.readonly!
742
+ # customer.destroy # raises ActiveRecord::ReadOnlyRecord
743
+ #
744
+ # Please, note that the objects themselves are still mutable in memory:
745
+ #
746
+ # customer = Customer.new
747
+ # customer.readonly!
748
+ # customer.name = 'New Name' # OK
749
+ #
750
+ # but you won't be able to persist the changes.
494
751
  def readonly!
495
752
  @readonly = true
496
753
  end
@@ -499,28 +756,28 @@ module ActiveRecord
499
756
  self.class.connection_handler
500
757
  end
501
758
 
502
- # Returns the contents of the record as a nicely formatted string.
759
+ # Returns the attributes of the record as a nicely formatted string.
760
+ #
761
+ # Post.first.inspect
762
+ # #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
763
+ #
764
+ # The attributes can be limited by setting <tt>.attributes_for_inspect</tt>.
765
+ #
766
+ # Post.attributes_for_inspect = [:id, :title]
767
+ # Post.first.inspect
768
+ # #=> "#<Post id: 1, title: "Hello, World!">"
503
769
  def inspect
504
- # We check defined?(@attributes) not to issue warnings if the object is
505
- # allocated but not initialized.
506
- inspection = if defined?(@attributes) && @attributes
507
- self.class.attribute_names.collect do |name|
508
- if has_attribute?(name)
509
- attr = _read_attribute(name)
510
- value = if attr.nil?
511
- attr.inspect
512
- else
513
- attr = format_for_inspect(attr)
514
- inspection_filter.filter_param(name, attr)
515
- end
516
- "#{name}: #{value}"
517
- end
518
- end.compact.join(", ")
519
- else
520
- "not initialized"
521
- end
770
+ inspect_with_attributes(attributes_for_inspect)
771
+ end
522
772
 
523
- "#<#{self.class} #{inspection}>"
773
+ # Returns all attributes of the record as a nicely formatted string,
774
+ # ignoring <tt>.attributes_for_inspect</tt>.
775
+ #
776
+ # Post.first.full_inspect
777
+ # #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
778
+ #
779
+ def full_inspect
780
+ inspect_with_attributes(all_attributes_for_inspect)
524
781
  end
525
782
 
526
783
  # Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
@@ -528,17 +785,17 @@ module ActiveRecord
528
785
  def pretty_print(pp)
529
786
  return super if custom_inspect_method_defined?
530
787
  pp.object_address_group(self) do
531
- if defined?(@attributes) && @attributes
532
- attr_names = self.class.attribute_names.select { |name| has_attribute?(name) }
788
+ if @attributes
789
+ attr_names = attributes_for_inspect.select { |name| _has_attribute?(name.to_s) }
533
790
  pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
791
+ attr_name = attr_name.to_s
534
792
  pp.breakable " "
535
793
  pp.group(1) do
536
794
  pp.text attr_name
537
795
  pp.text ":"
538
796
  pp.breakable
539
- value = _read_attribute(attr_name)
540
- value = inspection_filter.filter_param(attr_name, value) unless value.nil?
541
- pp.pp value
797
+ value = attribute_for_inspect(attr_name)
798
+ pp.text value
542
799
  end
543
800
  end
544
801
  else
@@ -548,13 +805,7 @@ module ActiveRecord
548
805
  end
549
806
  end
550
807
 
551
- # Returns a hash of the given methods with their names as keys and returned values as values.
552
- def slice(*methods)
553
- Hash[methods.flatten.map! { |method| [method, public_send(method)] }].with_indifferent_access
554
- end
555
-
556
808
  private
557
-
558
809
  # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
559
810
  # the array, and then rescues from the possible +NoMethodError+. If those elements are
560
811
  # +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have,
@@ -568,15 +819,20 @@ module ActiveRecord
568
819
  end
569
820
 
570
821
  def init_internals
571
- @primary_key = self.class.primary_key
572
822
  @readonly = false
823
+ @previously_new_record = false
573
824
  @destroyed = false
574
825
  @marked_for_destruction = false
575
826
  @destroyed_by_association = nil
576
827
  @_start_transaction_state = nil
577
- @transaction_state = nil
578
828
 
579
- self.class.define_attribute_methods
829
+ klass = self.class
830
+
831
+ @primary_key = klass.primary_key
832
+ @strict_loading = klass.strict_loading_by_default
833
+ @strict_loading_mode = :all
834
+
835
+ klass.define_attribute_methods
580
836
  end
581
837
 
582
838
  def initialize_internals_callback
@@ -586,14 +842,40 @@ module ActiveRecord
586
842
  self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
587
843
  end
588
844
 
845
+ class InspectionMask < DelegateClass(::String)
846
+ def pretty_print(pp)
847
+ pp.text __getobj__
848
+ end
849
+ end
850
+ private_constant :InspectionMask
851
+
589
852
  def inspection_filter
590
- @inspection_filter ||= begin
591
- mask = DelegateClass(::String).new(ActiveSupport::ParameterFilter::FILTERED)
592
- def mask.pretty_print(pp)
593
- pp.text __getobj__
594
- end
595
- ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
853
+ self.class.inspection_filter
854
+ end
855
+
856
+ def inspect_with_attributes(attributes_to_list)
857
+ inspection = if @attributes
858
+ attributes_to_list.filter_map do |name|
859
+ name = name.to_s
860
+ if _has_attribute?(name)
861
+ "#{name}: #{attribute_for_inspect(name)}"
862
+ end
863
+ end.join(", ")
864
+ else
865
+ "not initialized"
596
866
  end
867
+
868
+ "#<#{self.class} #{inspection}>"
869
+ end
870
+
871
+ def attributes_for_inspect
872
+ self.class.attributes_for_inspect == :all ? all_attributes_for_inspect : self.class.attributes_for_inspect
873
+ end
874
+
875
+ def all_attributes_for_inspect
876
+ return [] unless @attributes
877
+
878
+ attribute_names
597
879
  end
598
880
  end
599
881
  end