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
@@ -10,6 +10,8 @@ module ActiveRecord
10
10
  included do
11
11
  class_attribute :_reflections, instance_writer: false, default: {}
12
12
  class_attribute :aggregate_reflections, instance_writer: false, default: {}
13
+ class_attribute :automatic_scope_inversing, instance_writer: false, default: false
14
+ class_attribute :automatically_invert_plural_associations, instance_writer: false, default: false
13
15
  end
14
16
 
15
17
  class << self
@@ -20,12 +22,12 @@ module ActiveRecord
20
22
 
21
23
  def add_reflection(ar, name, reflection)
22
24
  ar.clear_reflections_cache
23
- name = -name.to_s
25
+ name = name.to_sym
24
26
  ar._reflections = ar._reflections.except(name).merge!(name => reflection)
25
27
  end
26
28
 
27
29
  def add_aggregate_reflection(ar, name, reflection)
28
- ar.aggregate_reflections = ar.aggregate_reflections.merge(-name.to_s => reflection)
30
+ ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_sym => reflection)
29
31
  end
30
32
 
31
33
  private
@@ -45,6 +47,8 @@ module ActiveRecord
45
47
  end
46
48
  end
47
49
 
50
+ # = Active Record Reflection
51
+ #
48
52
  # \Reflection enables the ability to examine the associations and aggregations of
49
53
  # Active Record classes and objects. This information, for example,
50
54
  # can be used in a form builder that takes an Active Record object
@@ -64,7 +68,7 @@ module ActiveRecord
64
68
  # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
65
69
  #
66
70
  def reflect_on_aggregation(aggregation)
67
- aggregate_reflections[aggregation.to_s]
71
+ aggregate_reflections[aggregation.to_sym]
68
72
  end
69
73
 
70
74
  # Returns a Hash of name of the reflection as the key and an AssociationReflection as the value.
@@ -72,6 +76,10 @@ module ActiveRecord
72
76
  # Account.reflections # => {"balance" => AggregateReflection}
73
77
  #
74
78
  def reflections
79
+ normalized_reflections.stringify_keys
80
+ end
81
+
82
+ def normalized_reflections # :nodoc:
75
83
  @__reflections ||= begin
76
84
  ref = {}
77
85
 
@@ -80,13 +88,13 @@ module ActiveRecord
80
88
 
81
89
  if parent_reflection
82
90
  parent_name = parent_reflection.name
83
- ref[parent_name.to_s] = parent_reflection
91
+ ref[parent_name] = parent_reflection
84
92
  else
85
93
  ref[name] = reflection
86
94
  end
87
95
  end
88
96
 
89
- ref
97
+ ref.freeze
90
98
  end
91
99
  end
92
100
 
@@ -101,7 +109,7 @@ module ActiveRecord
101
109
  # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
102
110
  #
103
111
  def reflect_on_all_associations(macro = nil)
104
- association_reflections = reflections.values
112
+ association_reflections = normalized_reflections.values
105
113
  association_reflections.select! { |reflection| reflection.macro == macro } if macro
106
114
  association_reflections
107
115
  end
@@ -112,21 +120,31 @@ module ActiveRecord
112
120
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
113
121
  #
114
122
  def reflect_on_association(association)
115
- reflections[association.to_s]
123
+ normalized_reflections[association.to_sym]
116
124
  end
117
125
 
118
- def _reflect_on_association(association) #:nodoc:
119
- _reflections[association.to_s]
126
+ def _reflect_on_association(association) # :nodoc:
127
+ _reflections[association.to_sym]
120
128
  end
121
129
 
122
130
  # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
123
131
  def reflect_on_all_autosave_associations
124
- reflections.values.select { |reflection| reflection.options[:autosave] }
132
+ reflections = normalized_reflections.values
133
+ reflections.select! { |reflection| reflection.options[:autosave] }
134
+ reflections
125
135
  end
126
136
 
127
137
  def clear_reflections_cache # :nodoc:
128
138
  @__reflections = nil
129
139
  end
140
+
141
+ private
142
+ def inherited(subclass)
143
+ super
144
+ subclass.class_eval do
145
+ @__reflections = nil
146
+ end
147
+ end
130
148
  end
131
149
 
132
150
  # Holds all the methods that are shared between MacroReflection and ThroughReflection.
@@ -143,6 +161,14 @@ module ActiveRecord
143
161
  # PolymorphicReflection
144
162
  # RuntimeReflection
145
163
  class AbstractReflection # :nodoc:
164
+ def initialize
165
+ @class_name = nil
166
+ @counter_cache_column = nil
167
+ @inverse_of = nil
168
+ @inverse_which_updates_counter_cache_defined = false
169
+ @inverse_which_updates_counter_cache = nil
170
+ end
171
+
146
172
  def through_reflection?
147
173
  false
148
174
  end
@@ -182,10 +208,14 @@ module ActiveRecord
182
208
 
183
209
  scope_chain_items.inject(klass_scope, &:merge!)
184
210
 
185
- primary_key = join_primary_key
186
- foreign_key = join_foreign_key
211
+ primary_key_column_names = Array(join_primary_key)
212
+ foreign_key_column_names = Array(join_foreign_key)
213
+
214
+ primary_foreign_key_pairs = primary_key_column_names.zip(foreign_key_column_names)
187
215
 
188
- klass_scope.where!(table[primary_key].eq(foreign_table[foreign_key]))
216
+ primary_foreign_key_pairs.each do |primary_key_column_name, foreign_key_column_name|
217
+ klass_scope.where!(table[primary_key_column_name].eq(foreign_table[foreign_key_column_name]))
218
+ end
189
219
 
190
220
  if klass.finder_needs_type_condition?
191
221
  klass_scope.where!(klass.send(:type_condition, table))
@@ -194,9 +224,9 @@ module ActiveRecord
194
224
  klass_scope
195
225
  end
196
226
 
197
- def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
227
+ def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
198
228
  if scope
199
- [scope_for(build_scope(table, predicate_builder, klass))]
229
+ [scope_for(build_scope(table, predicate_builder, klass), record)]
200
230
  else
201
231
  []
202
232
  end
@@ -212,14 +242,16 @@ module ActiveRecord
212
242
  end
213
243
 
214
244
  def counter_cache_column
215
- @counter_cache_column ||= if belongs_to?
216
- if options[:counter_cache] == true
217
- -"#{active_record.name.demodulize.underscore.pluralize}_count"
218
- elsif options[:counter_cache]
219
- -options[:counter_cache].to_s
245
+ @counter_cache_column ||= begin
246
+ counter_cache = options[:counter_cache]
247
+
248
+ if belongs_to?
249
+ if counter_cache
250
+ counter_cache[:column] || -"#{active_record.name.demodulize.underscore.pluralize}_count"
251
+ end
252
+ else
253
+ -((counter_cache && -counter_cache[:column]) || "#{name}_count")
220
254
  end
221
- else
222
- -(options[:counter_cache]&.to_s || "#{name}_count")
223
255
  end
224
256
  end
225
257
 
@@ -230,14 +262,17 @@ module ActiveRecord
230
262
  end
231
263
 
232
264
  def check_validity_of_inverse!
233
- unless polymorphic?
234
- if has_inverse? && inverse_of.nil?
265
+ if !polymorphic? && has_inverse?
266
+ if inverse_of.nil?
235
267
  raise InverseOfAssociationNotFoundError.new(self)
236
268
  end
269
+ if inverse_of == self
270
+ raise InverseOfAssociationRecursiveError.new(self)
271
+ end
237
272
  end
238
273
  end
239
274
 
240
- # This shit is nasty. We need to avoid the following situation:
275
+ # We need to avoid the following situation:
241
276
  #
242
277
  # * An associated record is deleted via record.destroy
243
278
  # * Hence the callbacks run, and they find a belongs_to on the record with a
@@ -248,10 +283,16 @@ module ActiveRecord
248
283
  #
249
284
  # Hence this method.
250
285
  def inverse_which_updates_counter_cache
251
- return @inverse_which_updates_counter_cache if defined?(@inverse_which_updates_counter_cache)
252
- @inverse_which_updates_counter_cache = klass.reflect_on_all_associations(:belongs_to).find do |inverse|
253
- inverse.counter_cache_column == counter_cache_column
286
+ unless @inverse_which_updates_counter_cache_defined
287
+ if counter_cache_column
288
+ inverse_candidates = inverse_of ? [inverse_of] : klass.reflect_on_all_associations(:belongs_to)
289
+ @inverse_which_updates_counter_cache = inverse_candidates.find do |inverse|
290
+ inverse.counter_cache_column == counter_cache_column && (inverse.polymorphic? || inverse.klass == active_record)
291
+ end
292
+ end
293
+ @inverse_which_updates_counter_cache_defined = true
254
294
  end
295
+ @inverse_which_updates_counter_cache
255
296
  end
256
297
  alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
257
298
 
@@ -259,7 +300,7 @@ module ActiveRecord
259
300
  inverse_of && inverse_which_updates_counter_cache == inverse_of
260
301
  end
261
302
 
262
- # Returns whether a counter cache should be used for this association.
303
+ # Returns whether this association has a counter cache.
263
304
  #
264
305
  # The counter_cache option must be given on either the owner or inverse
265
306
  # association, and the column must be present on the owner.
@@ -269,6 +310,17 @@ module ActiveRecord
269
310
  active_record.has_attribute?(counter_cache_column)
270
311
  end
271
312
 
313
+ # Returns whether this association has a counter cache and its column values were backfilled
314
+ # (and so it is used internally by methods like +size+/+any?+/etc).
315
+ def has_active_cached_counter?
316
+ return false unless has_cached_counter?
317
+
318
+ counter_cache = options[:counter_cache] ||
319
+ (inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache])
320
+
321
+ counter_cache[:active] != false
322
+ end
323
+
272
324
  def counter_must_be_updated_by_has_many?
273
325
  !inverse_updates_counter_in_memory? && has_cached_counter?
274
326
  end
@@ -293,6 +345,12 @@ module ActiveRecord
293
345
  options[:strict_loading]
294
346
  end
295
347
 
348
+ def strict_loading_violation_message(owner)
349
+ message = +"`#{owner}` is marked for strict_loading."
350
+ message << " The #{polymorphic? ? "polymorphic association" : "#{klass} association"}"
351
+ message << " named `:#{name}` cannot be lazily loaded."
352
+ end
353
+
296
354
  protected
297
355
  def actual_source_reflection # FIXME: this is a horrible name
298
356
  self
@@ -306,6 +364,12 @@ module ActiveRecord
306
364
  def primary_key(klass)
307
365
  klass.primary_key || raise(UnknownPrimaryKey.new(klass))
308
366
  end
367
+
368
+ def ensure_option_not_given_as_class!(option_name)
369
+ if options[option_name] && options[option_name].class == Class
370
+ raise ArgumentError, "A class was passed to `:#{option_name}` but we are expecting a string."
371
+ end
372
+ end
309
373
  end
310
374
 
311
375
  # Base class for AggregateReflection and AssociationReflection. Objects of
@@ -330,9 +394,10 @@ module ActiveRecord
330
394
  attr_reader :plural_name # :nodoc:
331
395
 
332
396
  def initialize(name, scope, options, active_record)
397
+ super()
333
398
  @name = name
334
399
  @scope = scope
335
- @options = options
400
+ @options = normalize_options(options)
336
401
  @active_record = active_record
337
402
  @klass = options[:anonymous_class]
338
403
  @plural_name = active_record.pluralize_table_names ?
@@ -363,7 +428,15 @@ module ActiveRecord
363
428
  # a new association object. Use +build_association+ or +create_association+
364
429
  # instead. This allows plugins to hook into association object creation.
365
430
  def klass
366
- @klass ||= compute_class(class_name)
431
+ @klass ||= _klass(class_name)
432
+ end
433
+
434
+ def _klass(class_name) # :nodoc:
435
+ if active_record.name.demodulize == class_name
436
+ return compute_class("::#{class_name}") rescue NameError
437
+ end
438
+
439
+ compute_class(class_name)
367
440
  end
368
441
 
369
442
  def compute_class(name)
@@ -388,11 +461,31 @@ module ActiveRecord
388
461
  def derive_class_name
389
462
  name.to_s.camelize
390
463
  end
464
+
465
+ def normalize_options(options)
466
+ counter_cache = options.delete(:counter_cache)
467
+
468
+ if counter_cache
469
+ active = true
470
+
471
+ case counter_cache
472
+ when String, Symbol
473
+ column = -counter_cache.to_s
474
+ when Hash
475
+ active = counter_cache.fetch(:active, true)
476
+ column = counter_cache[:column]&.to_s
477
+ end
478
+
479
+ options[:counter_cache] = { active: active, column: column }
480
+ end
481
+
482
+ options
483
+ end
391
484
  end
392
485
 
393
486
  # Holds all the metadata about an aggregation as it was specified in the
394
487
  # Active Record class.
395
- class AggregateReflection < MacroReflection #:nodoc:
488
+ class AggregateReflection < MacroReflection # :nodoc:
396
489
  def mapping
397
490
  mapping = options[:mapping] || [name, name]
398
491
  mapping.first.is_a?(Array) ? mapping : [mapping]
@@ -401,12 +494,29 @@ module ActiveRecord
401
494
 
402
495
  # Holds all the metadata about an association as it was specified in the
403
496
  # Active Record class.
404
- class AssociationReflection < MacroReflection #:nodoc:
497
+ class AssociationReflection < MacroReflection # :nodoc:
405
498
  def compute_class(name)
406
499
  if polymorphic?
407
500
  raise ArgumentError, "Polymorphic associations do not support computing the class."
408
501
  end
409
- active_record.send(:compute_type, name)
502
+
503
+ begin
504
+ klass = active_record.send(:compute_type, name)
505
+ rescue NameError => error
506
+ if error.name.match?(/(?:\A|::)#{name}\z/)
507
+ message = "Missing model class #{name} for the #{active_record}##{self.name} association."
508
+ message += " You can specify a different model class with the :class_name option." unless options[:class_name]
509
+ raise NameError.new(message, name)
510
+ else
511
+ raise
512
+ end
513
+ end
514
+
515
+ unless klass < ActiveRecord::Base
516
+ raise ArgumentError, "The #{name} model class for the #{active_record}##{self.name} association is not an ActiveRecord::Base subclass."
517
+ end
518
+
519
+ klass
410
520
  end
411
521
 
412
522
  attr_reader :type, :foreign_type
@@ -416,11 +526,23 @@ module ActiveRecord
416
526
  super
417
527
  @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
418
528
  @foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
419
- @constructable = calculate_constructable(macro, options)
529
+ @join_table = nil
530
+ @foreign_key = nil
531
+ @association_foreign_key = nil
532
+ @association_primary_key = nil
533
+ if options[:query_constraints]
534
+ ActiveRecord.deprecator.warn <<~MSG.squish
535
+ Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is deprecated.
536
+ To maintain current behavior, use the `foreign_key` option instead.
537
+ MSG
538
+ end
420
539
 
421
- if options[:class_name] && options[:class_name].class == Class
422
- raise ArgumentError, "A class was passed to `:class_name` but we are expecting a string."
540
+ # If the foreign key is an array, set query constraints options and don't use the foreign key
541
+ if options[:foreign_key].is_a?(Array)
542
+ options[:query_constraints] = options.delete(:foreign_key)
423
543
  end
544
+
545
+ ensure_option_not_given_as_class!(:class_name)
424
546
  end
425
547
 
426
548
  def association_scope_cache(klass, owner, &block)
@@ -428,19 +550,33 @@ module ActiveRecord
428
550
  if polymorphic?
429
551
  key = [key, owner._read_attribute(@foreign_type)]
430
552
  end
431
- klass.cached_find_by_statement(key, &block)
432
- end
433
-
434
- def constructable? # :nodoc:
435
- @constructable
553
+ klass.with_connection do |connection|
554
+ klass.cached_find_by_statement(connection, key, &block)
555
+ end
436
556
  end
437
557
 
438
558
  def join_table
439
559
  @join_table ||= -(options[:join_table]&.to_s || derive_join_table)
440
560
  end
441
561
 
442
- def foreign_key
443
- @foreign_key ||= -(options[:foreign_key]&.to_s || derive_foreign_key)
562
+ def foreign_key(infer_from_inverse_of: true)
563
+ @foreign_key ||= if options[:foreign_key]
564
+ if options[:foreign_key].is_a?(Array)
565
+ options[:foreign_key].map { |fk| fk.to_s.freeze }.freeze
566
+ else
567
+ options[:foreign_key].to_s.freeze
568
+ end
569
+ elsif options[:query_constraints]
570
+ options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
571
+ else
572
+ derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
573
+
574
+ if active_record.has_query_constraints?
575
+ derived_fk = derive_fk_query_constraints(derived_fk)
576
+ end
577
+
578
+ derived_fk
579
+ end
444
580
  end
445
581
 
446
582
  def association_foreign_key
@@ -452,36 +588,62 @@ module ActiveRecord
452
588
  end
453
589
 
454
590
  def active_record_primary_key
455
- @active_record_primary_key ||= -(options[:primary_key]&.to_s || primary_key(active_record))
591
+ custom_primary_key = options[:primary_key]
592
+ @active_record_primary_key ||= if custom_primary_key
593
+ if custom_primary_key.is_a?(Array)
594
+ custom_primary_key.map { |pk| pk.to_s.freeze }.freeze
595
+ else
596
+ custom_primary_key.to_s.freeze
597
+ end
598
+ elsif active_record.has_query_constraints? || options[:query_constraints]
599
+ active_record.query_constraints_list
600
+ elsif active_record.composite_primary_key?
601
+ # If active_record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
602
+ primary_key = primary_key(active_record)
603
+ primary_key.include?("id") ? "id" : primary_key.freeze
604
+ else
605
+ primary_key(active_record).freeze
606
+ end
456
607
  end
457
608
 
458
609
  def join_primary_key(klass = nil)
459
610
  foreign_key
460
611
  end
461
612
 
613
+ def join_primary_type
614
+ type
615
+ end
616
+
462
617
  def join_foreign_key
463
618
  active_record_primary_key
464
619
  end
465
620
 
466
621
  def check_validity!
467
622
  check_validity_of_inverse!
623
+
624
+ if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
625
+ if (has_one? || collection?) && Array(active_record_primary_key).length != Array(foreign_key).length
626
+ raise CompositePrimaryKeyMismatchError.new(self)
627
+ elsif belongs_to? && Array(association_primary_key).length != Array(foreign_key).length
628
+ raise CompositePrimaryKeyMismatchError.new(self)
629
+ end
630
+ end
468
631
  end
469
632
 
470
- def check_preloadable!
633
+ def check_eager_loadable!
471
634
  return unless scope
472
635
 
473
636
  unless scope.arity == 0
474
637
  raise ArgumentError, <<-MSG.squish
475
638
  The association scope '#{name}' is instance dependent (the scope
476
- block takes an argument). Preloading instance dependent scopes is
477
- not supported.
639
+ block takes an argument). Eager loading instance dependent scopes
640
+ is not supported.
478
641
  MSG
479
642
  end
480
643
  end
481
- alias :check_eager_loadable! :check_preloadable!
482
644
 
483
645
  def join_id_for(owner) # :nodoc:
484
- owner[join_foreign_key]
646
+ Array(join_foreign_key).map { |key| owner._read_attribute(key) }
485
647
  end
486
648
 
487
649
  def through_reflection
@@ -563,8 +725,9 @@ module ActiveRecord
563
725
  options[:polymorphic]
564
726
  end
565
727
 
566
- VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
567
- INVALID_AUTOMATIC_INVERSE_OPTIONS = [:through, :foreign_key]
728
+ def polymorphic_name
729
+ active_record.polymorphic_name
730
+ end
568
731
 
569
732
  def add_as_source(seed)
570
733
  seed
@@ -583,10 +746,6 @@ module ActiveRecord
583
746
  end
584
747
 
585
748
  private
586
- def calculate_constructable(macro, options)
587
- true
588
- end
589
-
590
749
  # Attempts to find the inverse association name automatically.
591
750
  # If it cannot find a suitable inverse association name, it returns
592
751
  # +nil+.
@@ -605,14 +764,20 @@ module ActiveRecord
605
764
 
606
765
  begin
607
766
  reflection = klass._reflect_on_association(inverse_name)
608
- rescue NameError
767
+ if !reflection && active_record.automatically_invert_plural_associations
768
+ plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
769
+ reflection = klass._reflect_on_association(plural_inverse_name)
770
+ end
771
+ rescue NameError => error
772
+ raise unless error.name.to_s == class_name
773
+
609
774
  # Give up: we couldn't compute the klass type so we won't be able
610
775
  # to find any associations either.
611
776
  reflection = false
612
777
  end
613
778
 
614
779
  if valid_inverse_reflection?(reflection)
615
- inverse_name
780
+ reflection.name
616
781
  end
617
782
  end
618
783
  end
@@ -623,9 +788,10 @@ module ActiveRecord
623
788
  # with the current reflection's klass name.
624
789
  def valid_inverse_reflection?(reflection)
625
790
  reflection &&
791
+ reflection != self &&
626
792
  foreign_key == reflection.foreign_key &&
627
793
  klass <= reflection.active_record &&
628
- can_find_inverse_of_automatically?(reflection)
794
+ can_find_inverse_of_automatically?(reflection, true)
629
795
  end
630
796
 
631
797
  # Checks to see if the reflection doesn't have any options that prevent
@@ -634,14 +800,25 @@ module ActiveRecord
634
800
  # have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
635
801
  # Third, we must not have options such as <tt>:foreign_key</tt>
636
802
  # which prevent us from correctly guessing the inverse association.
637
- #
638
- # Anything with a scope can additionally ruin our attempt at finding an
639
- # inverse, so we exclude reflections with scopes.
640
- def can_find_inverse_of_automatically?(reflection)
803
+ def can_find_inverse_of_automatically?(reflection, inverse_reflection = false)
641
804
  reflection.options[:inverse_of] != false &&
642
- VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
643
- !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
805
+ !reflection.options[:through] &&
806
+ !reflection.options[:foreign_key] &&
807
+ scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
808
+ end
809
+
810
+ # Scopes on the potential inverse reflection prevent automatic
811
+ # <tt>inverse_of</tt>, since the scope could exclude the owner record
812
+ # we would inverse from. Scopes on the reflection itself allow for
813
+ # automatic <tt>inverse_of</tt> as long as
814
+ # <tt>config.active_record.automatic_scope_inversing<tt> is set to
815
+ # +true+ (the default for new applications).
816
+ def scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
817
+ if inverse_reflection
644
818
  !reflection.scope
819
+ else
820
+ !reflection.scope || reflection.klass.automatic_scope_inversing
821
+ end
645
822
  end
646
823
 
647
824
  def derive_class_name
@@ -650,13 +827,55 @@ module ActiveRecord
650
827
  class_name.camelize
651
828
  end
652
829
 
653
- def derive_foreign_key
830
+ def derive_foreign_key(infer_from_inverse_of: true)
654
831
  if belongs_to?
655
832
  "#{name}_id"
656
833
  elsif options[:as]
657
834
  "#{options[:as]}_id"
835
+ elsif options[:inverse_of] && infer_from_inverse_of
836
+ inverse_of.foreign_key(infer_from_inverse_of: false)
658
837
  else
659
- active_record.name.foreign_key
838
+ active_record.model_name.to_s.foreign_key
839
+ end
840
+ end
841
+
842
+ def derive_fk_query_constraints(foreign_key)
843
+ primary_query_constraints = active_record.query_constraints_list
844
+ owner_pk = active_record.primary_key
845
+
846
+ if primary_query_constraints.size > 2
847
+ raise ArgumentError, <<~MSG.squish
848
+ The query constraints list on the `#{active_record}` model has more than 2
849
+ attributes. Active Record is unable to derive the query constraints
850
+ for the association. You need to explicitly define the query constraints
851
+ for this association.
852
+ MSG
853
+ end
854
+
855
+ if !primary_query_constraints.include?(owner_pk)
856
+ raise ArgumentError, <<~MSG.squish
857
+ The query constraints on the `#{active_record}` model does not include the primary
858
+ key so Active Record is unable to derive the foreign key constraints for
859
+ the association. You need to explicitly define the query constraints for this
860
+ association.
861
+ MSG
862
+ end
863
+
864
+ return foreign_key if primary_query_constraints.include?(foreign_key)
865
+
866
+ first_key, last_key = primary_query_constraints
867
+
868
+ if first_key == owner_pk
869
+ [foreign_key, last_key.to_s]
870
+ elsif last_key == owner_pk
871
+ [first_key.to_s, foreign_key]
872
+ else
873
+ raise ArgumentError, <<~MSG.squish
874
+ Active Record couldn't correctly interpret the query constraints
875
+ for the `#{active_record}` model. The query constraints on `#{active_record}` are
876
+ `#{primary_query_constraints}` and the foreign key is `#{foreign_key}`.
877
+ You need to explicitly set the query constraints for this association.
878
+ MSG
660
879
  end
661
880
  end
662
881
 
@@ -691,11 +910,6 @@ module ActiveRecord
691
910
  Associations::HasOneAssociation
692
911
  end
693
912
  end
694
-
695
- private
696
- def calculate_constructable(macro, options)
697
- !options[:through]
698
- end
699
913
  end
700
914
 
701
915
  class BelongsToReflection < AssociationReflection # :nodoc:
@@ -714,7 +928,17 @@ module ActiveRecord
714
928
  # klass option is necessary to support loading polymorphic associations
715
929
  def association_primary_key(klass = nil)
716
930
  if primary_key = options[:primary_key]
717
- @association_primary_key ||= -primary_key.to_s
931
+ @association_primary_key ||= if primary_key.is_a?(Array)
932
+ primary_key.map { |pk| pk.to_s.freeze }.freeze
933
+ else
934
+ -primary_key.to_s
935
+ end
936
+ elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
937
+ (klass || self.klass).composite_query_constraints_list
938
+ elsif (klass || self.klass).composite_primary_key?
939
+ # If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
940
+ primary_key = (klass || self.klass).primary_key
941
+ primary_key.include?("id") ? "id" : primary_key
718
942
  else
719
943
  primary_key(klass || self.klass)
720
944
  end
@@ -733,13 +957,9 @@ module ActiveRecord
733
957
  end
734
958
 
735
959
  private
736
- def can_find_inverse_of_automatically?(_)
960
+ def can_find_inverse_of_automatically?(*)
737
961
  !polymorphic? && super
738
962
  end
739
-
740
- def calculate_constructable(macro, options)
741
- !polymorphic?
742
- end
743
963
  end
744
964
 
745
965
  class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
@@ -752,14 +972,17 @@ module ActiveRecord
752
972
 
753
973
  # Holds all the metadata about a :through association as it was specified
754
974
  # in the Active Record class.
755
- class ThroughReflection < AbstractReflection #:nodoc:
975
+ class ThroughReflection < AbstractReflection # :nodoc:
756
976
  delegate :foreign_key, :foreign_type, :association_foreign_key, :join_id_for, :type,
757
977
  :active_record_primary_key, :join_foreign_key, to: :source_reflection
758
978
 
759
979
  def initialize(delegate_reflection)
980
+ super()
760
981
  @delegate_reflection = delegate_reflection
761
982
  @klass = delegate_reflection.options[:anonymous_class]
762
983
  @source_reflection_name = delegate_reflection.options[:source]
984
+
985
+ ensure_option_not_given_as_class!(:source_type)
763
986
  end
764
987
 
765
988
  def through_reflection?
@@ -767,7 +990,7 @@ module ActiveRecord
767
990
  end
768
991
 
769
992
  def klass
770
- @klass ||= delegate_reflection.compute_class(class_name)
993
+ @klass ||= delegate_reflection._klass(class_name)
771
994
  end
772
995
 
773
996
  # Returns the source of the through reflection. It checks both a singularized
@@ -788,6 +1011,8 @@ module ActiveRecord
788
1011
  # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
789
1012
  #
790
1013
  def source_reflection
1014
+ return unless source_reflection_name
1015
+
791
1016
  through_reflection.klass._reflect_on_association(source_reflection_name)
792
1017
  end
793
1018
 
@@ -840,8 +1065,8 @@ module ActiveRecord
840
1065
  source_reflection.scopes + super
841
1066
  end
842
1067
 
843
- def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
844
- source_reflection.join_scopes(table, predicate_builder, klass) + super
1068
+ def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
1069
+ source_reflection.join_scopes(table, predicate_builder, klass, record) + super
845
1070
  end
846
1071
 
847
1072
  def has_scope?
@@ -888,24 +1113,23 @@ module ActiveRecord
888
1113
  end
889
1114
 
890
1115
  def source_reflection_name # :nodoc:
891
- return @source_reflection_name if @source_reflection_name
892
-
893
- names = [name.to_s.singularize, name].collect(&:to_sym).uniq
894
- names = names.find_all { |n|
895
- through_reflection.klass._reflect_on_association(n)
896
- }
897
-
898
- if names.length > 1
899
- raise AmbiguousSourceReflectionForThroughAssociation.new(
900
- active_record.name,
901
- macro,
902
- name,
903
- options,
904
- source_reflection_names
905
- )
1116
+ @source_reflection_name ||= begin
1117
+ names = [name.to_s.singularize, name].collect(&:to_sym).uniq
1118
+ names = names.find_all { |n|
1119
+ through_reflection.klass._reflect_on_association(n)
1120
+ }
1121
+
1122
+ if names.length > 1
1123
+ raise AmbiguousSourceReflectionForThroughAssociation.new(
1124
+ active_record.name,
1125
+ macro,
1126
+ name,
1127
+ options,
1128
+ source_reflection_names
1129
+ )
1130
+ end
1131
+ names.first
906
1132
  end
907
-
908
- @source_reflection_name = names.first
909
1133
  end
910
1134
 
911
1135
  def source_options
@@ -946,7 +1170,7 @@ module ActiveRecord
946
1170
  end
947
1171
 
948
1172
  if parent_reflection.nil?
949
- reflections = active_record.reflections.keys.map(&:to_sym)
1173
+ reflections = active_record.normalized_reflections.keys
950
1174
 
951
1175
  if reflections.index(through_reflection.name) > reflections.index(name)
952
1176
  raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
@@ -1009,13 +1233,17 @@ module ActiveRecord
1009
1233
  :name, :scope_for, to: :@reflection
1010
1234
 
1011
1235
  def initialize(reflection, previous_reflection)
1236
+ super()
1012
1237
  @reflection = reflection
1013
1238
  @previous_reflection = previous_reflection
1014
1239
  end
1015
1240
 
1016
- def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
1017
- scopes = @previous_reflection.join_scopes(table, predicate_builder) + super
1018
- scopes << build_scope(table, predicate_builder, klass).instance_exec(nil, &source_type_scope)
1241
+ def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
1242
+ scopes = super
1243
+ unless @previous_reflection.through_reflection?
1244
+ scopes += @previous_reflection.join_scopes(table, predicate_builder, klass, record)
1245
+ end
1246
+ scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
1019
1247
  end
1020
1248
 
1021
1249
  def constraints
@@ -1034,6 +1262,7 @@ module ActiveRecord
1034
1262
  delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
1035
1263
 
1036
1264
  def initialize(reflection, association)
1265
+ super()
1037
1266
  @reflection = reflection
1038
1267
  @association = association
1039
1268
  end