activerecord 6.1.7 → 7.2.2

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 (333) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +616 -1290
  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 +19 -8
  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 +30 -27
  30. data/lib/active_record/associations/join_dependency.rb +28 -20
  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 +429 -522
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +1 -5
  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 +15 -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 +57 -54
  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 +19 -35
  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 +325 -604
  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 +230 -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 +378 -143
  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 +348 -165
  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 +403 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +520 -253
  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 +310 -253
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -4
  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 +58 -0
  165. data/lib/active_record/enum.rb +170 -62
  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 +59 -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 +145 -158
  199. data/lib/active_record/nested_attributes.rb +61 -23
  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 +18 -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 +229 -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 +332 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +200 -65
  216. data/lib/active_record/relation/calculations.rb +301 -112
  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 +870 -163
  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 +6 -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 +288 -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 +65 -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/sqlite.rb +25 -0
  317. data/lib/arel/visitors/to_sql.rb +170 -36
  318. data/lib/arel/visitors/visitor.rb +2 -2
  319. data/lib/arel.rb +23 -4
  320. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  321. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  322. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  323. data/lib/rails/generators/active_record/migration.rb +3 -1
  324. data/lib/rails/generators/active_record/model/USAGE +113 -0
  325. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  326. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  328. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  329. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  330. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  331. metadata +103 -17
  332. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  333. data/lib/active_record/null_relation.rb +0 -67
@@ -1,42 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/enumerable"
4
- require "active_support/core_ext/hash/indifferent_access"
5
- require "active_support/core_ext/string/filters"
4
+ require "active_support/core_ext/module/delegation"
6
5
  require "active_support/parameter_filter"
7
6
  require "concurrent/map"
8
7
 
9
8
  module ActiveRecord
9
+ # = Active Record \Core
10
10
  module Core
11
11
  extend ActiveSupport::Concern
12
+ include ActiveModel::Access
12
13
 
13
14
  included do
14
15
  ##
15
16
  # :singleton-method:
16
17
  #
17
- # Accepts a logger conforming to the interface of Log4r which is then
18
- # passed on to any new database connections made and which can be
19
- # retrieved on both a class and instance level by calling +logger+.
20
- 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
21
35
 
22
- ##
23
- # :singleton-method:
24
- #
25
- # Specifies if the methods calling database queries should be logged below
26
- # their relevant queries. Defaults to false.
27
- mattr_accessor :verbose_query_logs, instance_writer: false, default: false
36
+ singleton_class.alias_method :destroy_association_async_job=, :_destroy_association_async_job=
37
+ delegate :destroy_association_async_job, to: :class
28
38
 
29
39
  ##
30
40
  # :singleton-method:
31
41
  #
32
- # Specifies the names of the queues used by background jobs.
33
- mattr_accessor :queues, instance_accessor: false, default: {}
34
-
35
- ##
36
- # :singleton-method:
37
- #
38
- # Specifies the job used to destroy associations in the background
39
- class_attribute :destroy_association_async_job, instance_writer: false, instance_predicate: 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
40
48
 
41
49
  ##
42
50
  # Contains the database configuration - as is typically stored in config/database.yml -
@@ -46,106 +54,45 @@ module ActiveRecord
46
54
  #
47
55
  # development:
48
56
  # adapter: sqlite3
49
- # database: db/development.sqlite3
57
+ # database: storage/development.sqlite3
50
58
  #
51
59
  # production:
52
60
  # adapter: sqlite3
53
- # database: db/production.sqlite3
61
+ # database: storage/production.sqlite3
54
62
  #
55
63
  # ...would result in ActiveRecord::Base.configurations to look like this:
56
64
  #
57
65
  # #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
58
66
  # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
59
- # @name="primary", @config={adapter: "sqlite3", database: "db/development.sqlite3"}>,
67
+ # @name="primary", @config={adapter: "sqlite3", database: "storage/development.sqlite3"}>,
60
68
  # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
61
- # @name="primary", @config={adapter: "sqlite3", database: "db/production.sqlite3"}>
69
+ # @name="primary", @config={adapter: "sqlite3", database: "storage/production.sqlite3"}>
62
70
  # ]>
63
71
  def self.configurations=(config)
64
72
  @@configurations = ActiveRecord::DatabaseConfigurations.new(config)
65
73
  end
66
74
  self.configurations = {}
67
75
 
68
- # Returns fully resolved ActiveRecord::DatabaseConfigurations object
76
+ # Returns a fully resolved ActiveRecord::DatabaseConfigurations object.
69
77
  def self.configurations
70
78
  @@configurations
71
79
  end
72
80
 
73
81
  ##
74
82
  # :singleton-method:
75
- # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
76
- # dates and times from the database. This is set to :utc by default.
77
- mattr_accessor :default_timezone, instance_writer: false, default: :utc
78
-
79
- ##
80
- # :singleton-method:
81
- # Specifies the format to use when dumping the database schema with Rails'
82
- # Rakefile. If :sql, the schema is dumped as (potentially database-
83
- # specific) SQL statements. If :ruby, the schema is dumped as an
84
- # ActiveRecord::Schema file which can be loaded into any database that
85
- # supports migrations. Use :ruby if you want to have different database
86
- # adapters for, e.g., your development and test environments.
87
- mattr_accessor :schema_format, instance_writer: false, default: :ruby
88
-
89
- ##
90
- # :singleton-method:
91
- # Specifies if an error should be raised if the query has an order being
92
- # ignored when doing batch queries. Useful in applications where the
93
- # scope being ignored is error-worthy, rather than a warning.
94
- mattr_accessor :error_on_ignored_order, instance_writer: false, default: false
95
-
96
- ##
97
- # :singleton-method:
98
- # Specify whether or not to use timestamps for migration versions
99
- mattr_accessor :timestamped_migrations, instance_writer: false, default: true
100
-
101
- ##
102
- # :singleton-method:
103
- # Specify whether schema dump should happen at the end of the
104
- # db:migrate rails command. This is true by default, which is useful for the
105
- # development environment. This should ideally be false in the production
106
- # environment where dumping schema is rarely needed.
107
- mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
108
-
109
- ##
110
- # :singleton-method:
111
- # Specifies which database schemas to dump when calling db:schema:dump.
112
- # If the value is :schema_search_path (the default), any schemas listed in
113
- # schema_search_path are dumped. Use :all to dump all schemas regardless
114
- # of schema_search_path, or a string of comma separated schemas for a
115
- # custom list.
116
- mattr_accessor :dump_schemas, instance_writer: false, default: :schema_search_path
117
-
118
- ##
119
- # :singleton-method:
120
- # Specify a threshold for the size of query result sets. If the number of
121
- # records in the set exceeds the threshold, a warning is logged. This can
122
- # be used to identify queries which load thousands of records and
123
- # potentially cause memory bloat.
124
- mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false
125
-
126
- ##
127
- # :singleton-method:
128
- # Show a warning when Rails couldn't parse your database.yml
129
- # for multiple databases.
130
- mattr_accessor :suppress_multiple_database_warning, instance_writer: false, default: false
131
-
132
- mattr_accessor :maintain_test_schema, instance_accessor: false
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
133
88
 
134
89
  class_attribute :belongs_to_required_by_default, instance_accessor: false
135
90
 
136
- ##
137
- # :singleton-method:
138
- # Set the application to log or raise when an association violates strict loading.
139
- # Defaults to :raise.
140
- mattr_accessor :action_on_strict_loading_violation, instance_accessor: false, default: :raise
141
-
142
91
  class_attribute :strict_loading_by_default, instance_accessor: false, default: false
143
92
 
144
- mattr_accessor :writing_role, instance_accessor: false, default: :writing
93
+ class_attribute :has_many_inversing, instance_accessor: false, default: false
145
94
 
146
- mattr_accessor :reading_role, instance_accessor: false, default: :reading
147
-
148
- mattr_accessor :has_many_inversing, instance_accessor: false, default: false
95
+ class_attribute :run_commit_callbacks_on_first_saved_instances_in_transaction, instance_accessor: false, default: true
149
96
 
150
97
  class_attribute :default_connection_handler, instance_writer: false
151
98
 
@@ -153,40 +100,50 @@ module ActiveRecord
153
100
 
154
101
  class_attribute :default_shard, instance_writer: false
155
102
 
156
- mattr_accessor :legacy_connection_handling, instance_writer: false, default: true
103
+ class_attribute :shard_selector, instance_accessor: false, default: nil
157
104
 
158
- # Application configurable boolean that instructs the YAML Coder to use
159
- # an unsafe load if set to true.
160
- mattr_accessor :use_yaml_unsafe_load, instance_writer: false, default: false
105
+ ##
106
+ # :singleton-method:
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
161
119
 
162
- # Application configurable array that provides additional permitted classes
163
- # to Psych safe_load in the YAML Coder
164
- mattr_accessor :yaml_column_permitted_classes, instance_writer: false, default: [Symbol]
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
165
129
 
166
130
  self.filter_attributes = []
167
131
 
168
132
  def self.connection_handler
169
- Thread.current.thread_variable_get(:ar_connection_handler) || default_connection_handler
133
+ ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] || default_connection_handler
170
134
  end
171
135
 
172
136
  def self.connection_handler=(handler)
173
- Thread.current.thread_variable_set(:ar_connection_handler, handler)
137
+ ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] = handler
174
138
  end
175
139
 
176
- def self.connection_handlers
177
- unless legacy_connection_handling
178
- raise NotImplementedError, "The new connection handling does not support accessing multiple connection handlers."
179
- end
180
-
181
- @@connection_handlers ||= {}
140
+ def self.asynchronous_queries_session # :nodoc:
141
+ asynchronous_queries_tracker.current_session
182
142
  end
183
143
 
184
- def self.connection_handlers=(handlers)
185
- unless legacy_connection_handling
186
- raise NotImplementedError, "The new connection handling does not setting support multiple connection handlers."
187
- end
188
-
189
- @@connection_handlers = handlers
144
+ def self.asynchronous_queries_tracker # :nodoc:
145
+ ActiveSupport::IsolatedExecutionState[:active_record_asynchronous_queries_tracker] ||= \
146
+ AsynchronousQueriesTracker.new
190
147
  end
191
148
 
192
149
  # Returns the symbol representing the current connected role.
@@ -199,16 +156,12 @@ module ActiveRecord
199
156
  # ActiveRecord::Base.current_role #=> :reading
200
157
  # end
201
158
  def self.current_role
202
- if ActiveRecord::Base.legacy_connection_handling
203
- connection_handlers.key(connection_handler) || default_role
204
- else
205
- connected_to_stack.reverse_each do |hash|
206
- return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
207
- return hash[:role] if hash[:role] && hash[:klasses].include?(connection_classes)
208
- end
209
-
210
- default_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)
211
162
  end
163
+
164
+ default_role
212
165
  end
213
166
 
214
167
  # Returns the symbol representing the current connected shard.
@@ -223,7 +176,7 @@ module ActiveRecord
223
176
  def self.current_shard
224
177
  connected_to_stack.reverse_each do |hash|
225
178
  return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base)
226
- return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_classes)
179
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_class_for_self)
227
180
  end
228
181
 
229
182
  default_shard
@@ -240,24 +193,20 @@ module ActiveRecord
240
193
  # ActiveRecord::Base.current_preventing_writes #=> false
241
194
  # end
242
195
  def self.current_preventing_writes
243
- if legacy_connection_handling
244
- connection_handler.prevent_writes
245
- else
246
- connected_to_stack.reverse_each do |hash|
247
- return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
248
- return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_classes)
249
- end
250
-
251
- false
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)
252
199
  end
200
+
201
+ false
253
202
  end
254
203
 
255
204
  def self.connected_to_stack # :nodoc:
256
- if connected_to_stack = Thread.current.thread_variable_get(:ar_connected_to_stack)
205
+ if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack]
257
206
  connected_to_stack
258
207
  else
259
208
  connected_to_stack = Concurrent::Array.new
260
- Thread.current.thread_variable_set(:ar_connected_to_stack, connected_to_stack)
209
+ ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack] = connected_to_stack
261
210
  connected_to_stack
262
211
  end
263
212
  end
@@ -266,7 +215,7 @@ module ActiveRecord
266
215
  @connection_class = b
267
216
  end
268
217
 
269
- def self.connection_class # :nodoc
218
+ def self.connection_class # :nodoc:
270
219
  @connection_class ||= false
271
220
  end
272
221
 
@@ -274,7 +223,7 @@ module ActiveRecord
274
223
  self.connection_class
275
224
  end
276
225
 
277
- def self.connection_classes # :nodoc:
226
+ def self.connection_class_for_self # :nodoc:
278
227
  klass = self
279
228
 
280
229
  until klass == Base
@@ -285,22 +234,14 @@ module ActiveRecord
285
234
  klass
286
235
  end
287
236
 
288
- def self.allow_unsafe_raw_sql # :nodoc:
289
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql is deprecated and will be removed in Rails 7.0")
290
- end
291
-
292
- def self.allow_unsafe_raw_sql=(value) # :nodoc:
293
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql= is deprecated and will be removed in Rails 7.0")
294
- end
295
-
296
237
  self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
297
- self.default_role = writing_role
238
+ self.default_role = ActiveRecord.writing_role
298
239
  self.default_shard = :default
299
240
 
300
241
  def self.strict_loading_violation!(owner:, reflection:) # :nodoc:
301
- case action_on_strict_loading_violation
242
+ case ActiveRecord.action_on_strict_loading_violation
302
243
  when :raise
303
- message = "`#{owner}` is marked for strict_loading. The `#{reflection.klass}` association named `:#{reflection.name}` cannot be lazily loaded."
244
+ message = reflection.strict_loading_violation_message(owner)
304
245
  raise ActiveRecord::StrictLoadingViolationError.new(message)
305
246
  when :log
306
247
  name = "strict_loading_violation.active_record"
@@ -314,19 +255,6 @@ module ActiveRecord
314
255
  @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
315
256
  end
316
257
 
317
- def inherited(child_class) # :nodoc:
318
- # initialize cache at class definition for thread safety
319
- child_class.initialize_find_by_cache
320
- unless child_class.base_class?
321
- klass = self
322
- until klass.base_class?
323
- klass.initialize_find_by_cache
324
- klass = klass.superclass
325
- end
326
- end
327
- super
328
- end
329
-
330
258
  def find(*ids) # :nodoc:
331
259
  # We don't have cache keys for this stuff yet
332
260
  return super unless ids.length == 1
@@ -336,14 +264,8 @@ module ActiveRecord
336
264
 
337
265
  return super if StatementCache.unsupported_value?(id)
338
266
 
339
- key = primary_key
340
-
341
- statement = cached_find_by_statement(key) { |params|
342
- where(key => params.bind).limit(1)
343
- }
344
-
345
- statement.execute([id], connection).first ||
346
- raise(RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id))
267
+ cached_find_by([primary_key], [id]) ||
268
+ raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", name, primary_key, id))
347
269
  end
348
270
 
349
271
  def find_by(*args) # :nodoc:
@@ -365,31 +287,36 @@ module ActiveRecord
365
287
  elsif reflection.belongs_to? && !reflection.polymorphic?
366
288
  key = reflection.join_foreign_key
367
289
  pkey = reflection.join_primary_key
368
- value = value.public_send(pkey) if value.respond_to?(pkey)
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
369
305
  end
370
306
 
371
- if !columns_hash.key?(key) || StatementCache.unsupported_value?(value)
307
+ if !composite_primary_key &&
308
+ (!columns_hash.key?(key) || StatementCache.unsupported_value?(value))
372
309
  return super
373
310
  end
374
311
 
375
312
  h[key] = value
376
313
  end
377
314
 
378
- keys = hash.keys
379
- statement = cached_find_by_statement(keys) { |params|
380
- wheres = keys.index_with { params.bind }
381
- where(wheres).limit(1)
382
- }
383
-
384
- begin
385
- statement.execute(hash.values, connection).first
386
- rescue TypeError
387
- raise ActiveRecord::StatementInvalid
388
- end
315
+ cached_find_by(hash.keys, hash.values)
389
316
  end
390
317
 
391
318
  def find_by!(*args) # :nodoc:
392
- find_by(*args) || raise(RecordNotFound.new("Couldn't find #{name}", name))
319
+ find_by(*args) || where(*args).raise_record_not_found_exception!
393
320
  end
394
321
 
395
322
  def initialize_generated_modules # :nodoc:
@@ -408,10 +335,10 @@ module ActiveRecord
408
335
 
409
336
  # Returns columns which shouldn't be exposed while calling +#inspect+.
410
337
  def filter_attributes
411
- if defined?(@filter_attributes)
412
- @filter_attributes
413
- else
338
+ if @filter_attributes.nil?
414
339
  superclass.filter_attributes
340
+ else
341
+ @filter_attributes
415
342
  end
416
343
  end
417
344
 
@@ -422,24 +349,24 @@ module ActiveRecord
422
349
  end
423
350
 
424
351
  def inspection_filter # :nodoc:
425
- if defined?(@filter_attributes)
352
+ if @filter_attributes.nil?
353
+ superclass.inspection_filter
354
+ else
426
355
  @inspection_filter ||= begin
427
356
  mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
428
357
  ActiveSupport::ParameterFilter.new(@filter_attributes, mask: mask)
429
358
  end
430
- else
431
- superclass.inspection_filter
432
359
  end
433
360
  end
434
361
 
435
362
  # Returns a string like 'Post(id:integer, title:string, body:text)'
436
363
  def inspect # :nodoc:
437
- if self == Base
364
+ if self == Base || singleton_class?
438
365
  super
439
366
  elsif abstract_class?
440
367
  "#{super}(abstract)"
441
- elsif !connected?
442
- "#{super} (call '#{super}.connection' to establish a connection)"
368
+ elsif !schema_loaded? && !connected?
369
+ "#{super} (call '#{super}.load_schema' to load schema informations)"
443
370
  elsif table_exists?
444
371
  attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
445
372
  "#{super}(#{attr_list})"
@@ -448,25 +375,11 @@ module ActiveRecord
448
375
  end
449
376
  end
450
377
 
451
- # Overwrite the default class equality method to provide support for decorated models.
452
- def ===(object) # :nodoc:
453
- object.is_a?(self)
454
- end
455
-
456
- # Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
457
- #
458
- # class Post < ActiveRecord::Base
459
- # scope :published_and_commented, -> { published.and(arel_table[:comments_count].gt(0)) }
460
- # end
378
+ # Returns an instance of +Arel::Table+ loaded with the current table name.
461
379
  def arel_table # :nodoc:
462
380
  @arel_table ||= Arel::Table.new(table_name, klass: self)
463
381
  end
464
382
 
465
- def arel_attribute(name, table = arel_table) # :nodoc:
466
- table[name]
467
- end
468
- deprecate :arel_attribute
469
-
470
383
  def predicate_builder # :nodoc:
471
384
  @predicate_builder ||= PredicateBuilder.new(table_metadata)
472
385
  end
@@ -475,16 +388,34 @@ module ActiveRecord
475
388
  TypeCaster::Map.new(self)
476
389
  end
477
390
 
478
- def _internal? # :nodoc:
479
- false
480
- end
481
-
482
- def cached_find_by_statement(key, &block) # :nodoc:
391
+ def cached_find_by_statement(connection, key, &block) # :nodoc:
483
392
  cache = @find_by_statement_cache[connection.prepared_statements]
484
393
  cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
485
394
  end
486
395
 
487
396
  private
397
+ def inherited(subclass)
398
+ super
399
+
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
417
+ end
418
+
488
419
  def relation
489
420
  relation = Relation.create(self)
490
421
 
@@ -498,6 +429,27 @@ module ActiveRecord
498
429
  def table_metadata
499
430
  TableMetadata.new(self, arel_table)
500
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
501
453
  end
502
454
 
503
455
  # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
@@ -505,7 +457,7 @@ module ActiveRecord
505
457
  # In both instances, valid attribute keys are determined by the column names of the associated table --
506
458
  # hence you can't have attributes that aren't part of the table columns.
507
459
  #
508
- # ==== Example:
460
+ # ==== Example
509
461
  # # Instantiates a single new object
510
462
  # User.new(first_name: 'Jamie')
511
463
  def initialize(attributes = nil)
@@ -515,7 +467,7 @@ module ActiveRecord
515
467
  init_internals
516
468
  initialize_internals_callback
517
469
 
518
- assign_attributes(attributes) if attributes
470
+ super
519
471
 
520
472
  yield self if block_given?
521
473
  _run_initialize_callbacks
@@ -536,7 +488,7 @@ module ActiveRecord
536
488
  # post.init_with(coder)
537
489
  # post.title # => 'hello world'
538
490
  def init_with(coder, &block)
539
- coder = LegacyYamlAdapter.convert(self.class, coder)
491
+ coder = LegacyYamlAdapter.convert(coder)
540
492
  attributes = self.class.yaml_encoder.decode(coder)
541
493
  init_with_attributes(attributes, coder["new_record"], &block)
542
494
  end
@@ -583,12 +535,17 @@ module ActiveRecord
583
535
  # only, not its associations. The extent of a "deep" copy is application
584
536
  # specific and is therefore left to the application to implement according
585
537
  # to its need.
586
- # 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.
587
540
 
588
541
  ##
589
542
  def initialize_dup(other) # :nodoc:
590
543
  @attributes = @attributes.deep_dup
591
- @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
592
549
 
593
550
  _run_initialize_callbacks
594
551
 
@@ -618,6 +575,35 @@ module ActiveRecord
618
575
  coder["active_record_yaml_version"] = 2
619
576
  end
620
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
+
621
607
  # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
622
608
  # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
623
609
  #
@@ -630,7 +616,7 @@ module ActiveRecord
630
616
  def ==(comparison_object)
631
617
  super ||
632
618
  comparison_object.instance_of?(self.class) &&
633
- !id.nil? &&
619
+ primary_key_values_present? &&
634
620
  comparison_object.id == id
635
621
  end
636
622
  alias :eql? :==
@@ -638,7 +624,9 @@ module ActiveRecord
638
624
  # Delegates to id in order to allow two records of the same type and id to work with something like:
639
625
  # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
640
626
  def hash
641
- if id
627
+ id = self.id
628
+
629
+ if primary_key_values_present?
642
630
  self.class.hash ^ id.hash
643
631
  else
644
632
  super
@@ -689,14 +677,57 @@ module ActiveRecord
689
677
  # if the record tries to lazily load an association.
690
678
  #
691
679
  # user = User.first
692
- # user.strict_loading!
680
+ # user.strict_loading! # => true
681
+ # user.address.city
682
+ # => ActiveRecord::StrictLoadingViolationError
693
683
  # user.comments.to_a
694
684
  # => ActiveRecord::StrictLoadingViolationError
695
- def strict_loading!
696
- @strict_loading = true
685
+ #
686
+ # ==== Parameters
687
+ #
688
+ # * +value+ - Boolean specifying whether to enable or disable strict loading.
689
+ # * <tt>:mode</tt> - Symbol specifying strict loading mode. Defaults to :all. Using
690
+ # :n_plus_one_only mode will only raise an error if an association that
691
+ # will lead to an n plus one query is lazily loaded.
692
+ #
693
+ # ==== Examples
694
+ #
695
+ # user = User.first
696
+ # user.strict_loading!(false) # => false
697
+ # user.address.city # => "Tatooine"
698
+ # user.comments.to_a # => [#<Comment:0x00...]
699
+ #
700
+ # user.strict_loading!(mode: :n_plus_one_only)
701
+ # user.address.city # => "Tatooine"
702
+ # user.comments.to_a # => [#<Comment:0x00...]
703
+ # user.comments.first.ratings.to_a
704
+ # => ActiveRecord::StrictLoadingViolationError
705
+ def strict_loading!(value = true, mode: :all)
706
+ unless [:all, :n_plus_one_only].include?(mode)
707
+ raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided."
708
+ end
709
+
710
+ @strict_loading_mode = mode
711
+ @strict_loading = value
712
+ end
713
+
714
+ attr_reader :strict_loading_mode
715
+
716
+ # Returns +true+ if the record uses strict_loading with +:n_plus_one_only+ mode enabled.
717
+ def strict_loading_n_plus_one_only?
718
+ @strict_loading_mode == :n_plus_one_only
719
+ end
720
+
721
+ # Returns +true+ if the record uses strict_loading with +:all+ mode enabled.
722
+ def strict_loading_all?
723
+ @strict_loading_mode == :all
697
724
  end
698
725
 
699
726
  # Marks this record as read only.
727
+ #
728
+ # customer = Customer.first
729
+ # customer.readonly!
730
+ # customer.save # Raises an ActiveRecord::ReadOnlyRecord
700
731
  def readonly!
701
732
  @readonly = true
702
733
  end
@@ -705,21 +736,28 @@ module ActiveRecord
705
736
  self.class.connection_handler
706
737
  end
707
738
 
708
- # Returns the contents of the record as a nicely formatted string.
739
+ # Returns the attributes of the record as a nicely formatted string.
740
+ #
741
+ # Post.first.inspect
742
+ # #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
743
+ #
744
+ # The attributes can be limited by setting <tt>.attributes_for_inspect</tt>.
745
+ #
746
+ # Post.attributes_for_inspect = [:id, :title]
747
+ # Post.first.inspect
748
+ # #=> "#<Post id: 1, title: "Hello, World!">"
709
749
  def inspect
710
- # We check defined?(@attributes) not to issue warnings if the object is
711
- # allocated but not initialized.
712
- inspection = if defined?(@attributes) && @attributes
713
- self.class.attribute_names.collect do |name|
714
- if _has_attribute?(name)
715
- "#{name}: #{attribute_for_inspect(name)}"
716
- end
717
- end.compact.join(", ")
718
- else
719
- "not initialized"
720
- end
750
+ inspect_with_attributes(attributes_for_inspect)
751
+ end
721
752
 
722
- "#<#{self.class} #{inspection}>"
753
+ # Returns all attributes of the record as a nicely formatted string,
754
+ # ignoring <tt>.attributes_for_inspect</tt>.
755
+ #
756
+ # Post.first.full_inspect
757
+ # #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
758
+ #
759
+ def full_inspect
760
+ inspect_with_attributes(all_attributes_for_inspect)
723
761
  end
724
762
 
725
763
  # Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
@@ -727,17 +765,17 @@ module ActiveRecord
727
765
  def pretty_print(pp)
728
766
  return super if custom_inspect_method_defined?
729
767
  pp.object_address_group(self) do
730
- if defined?(@attributes) && @attributes
731
- attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) }
768
+ if @attributes
769
+ attr_names = attributes_for_inspect.select { |name| _has_attribute?(name.to_s) }
732
770
  pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
771
+ attr_name = attr_name.to_s
733
772
  pp.breakable " "
734
773
  pp.group(1) do
735
774
  pp.text attr_name
736
775
  pp.text ":"
737
776
  pp.breakable
738
- value = _read_attribute(attr_name)
739
- value = inspection_filter.filter_param(attr_name, value) unless value.nil?
740
- pp.pp value
777
+ value = attribute_for_inspect(attr_name)
778
+ pp.text value
741
779
  end
742
780
  end
743
781
  else
@@ -747,16 +785,6 @@ module ActiveRecord
747
785
  end
748
786
  end
749
787
 
750
- # Returns a hash of the given methods with their names as keys and returned values as values.
751
- def slice(*methods)
752
- methods.flatten.index_with { |method| public_send(method) }.with_indifferent_access
753
- end
754
-
755
- # Returns an array of the values returned by the given methods.
756
- def values_at(*methods)
757
- methods.flatten.map! { |method| public_send(method) }
758
- end
759
-
760
788
  private
761
789
  # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
762
790
  # the array, and then rescues from the possible +NoMethodError+. If those elements are
@@ -771,16 +799,20 @@ module ActiveRecord
771
799
  end
772
800
 
773
801
  def init_internals
774
- @primary_key = self.class.primary_key
775
802
  @readonly = false
776
803
  @previously_new_record = false
777
804
  @destroyed = false
778
805
  @marked_for_destruction = false
779
806
  @destroyed_by_association = nil
780
807
  @_start_transaction_state = nil
781
- @strict_loading = self.class.strict_loading_by_default
782
808
 
783
- self.class.define_attribute_methods
809
+ klass = self.class
810
+
811
+ @primary_key = klass.primary_key
812
+ @strict_loading = klass.strict_loading_by_default
813
+ @strict_loading_mode = :all
814
+
815
+ klass.define_attribute_methods
784
816
  end
785
817
 
786
818
  def initialize_internals_callback
@@ -800,5 +832,30 @@ module ActiveRecord
800
832
  def inspection_filter
801
833
  self.class.inspection_filter
802
834
  end
835
+
836
+ def inspect_with_attributes(attributes_to_list)
837
+ inspection = if @attributes
838
+ attributes_to_list.filter_map do |name|
839
+ name = name.to_s
840
+ if _has_attribute?(name)
841
+ "#{name}: #{attribute_for_inspect(name)}"
842
+ end
843
+ end.join(", ")
844
+ else
845
+ "not initialized"
846
+ end
847
+
848
+ "#<#{self.class} #{inspection}>"
849
+ end
850
+
851
+ def attributes_for_inspect
852
+ self.class.attributes_for_inspect == :all ? all_attributes_for_inspect : self.class.attributes_for_inspect
853
+ end
854
+
855
+ def all_attributes_for_inspect
856
+ return [] unless @attributes
857
+
858
+ attribute_names
859
+ end
803
860
  end
804
861
  end