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
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Associations
5
+ # = Active Record Collection Proxy
6
+ #
5
7
  # Collection proxies in Active Record are middlemen between an
6
8
  # <tt>association</tt>, and its <tt>target</tt> result set.
7
9
  #
@@ -27,7 +29,7 @@ module ActiveRecord
27
29
  # is computed directly through SQL and does not trigger by itself the
28
30
  # instantiation of the actual post records.
29
31
  class CollectionProxy < Relation
30
- def initialize(klass, association, **) #:nodoc:
32
+ def initialize(klass, association, **) # :nodoc:
31
33
  @association = association
32
34
  super klass
33
35
 
@@ -46,7 +48,7 @@ module ActiveRecord
46
48
  # Returns +true+ if the association has been loaded, otherwise +false+.
47
49
  #
48
50
  # person.pets.loaded? # => false
49
- # person.pets
51
+ # person.pets.records
50
52
  # person.pets.loaded? # => true
51
53
  def loaded?
52
54
  @association.loaded?
@@ -94,12 +96,12 @@ module ActiveRecord
94
96
  # receive:
95
97
  #
96
98
  # person.pets.select(:name).first.person_id
97
- # # => ActiveModel::MissingAttributeError: missing attribute: person_id
99
+ # # => ActiveModel::MissingAttributeError: missing attribute 'person_id' for Pet
98
100
  #
99
- # *Second:* You can pass a block so it can be used just like Array#select.
101
+ # *Second:* You can pass a block so it can be used just like <tt>Array#select</tt>.
100
102
  # This builds an array of objects from the database for the scope,
101
103
  # converting them into an array and iterating through them using
102
- # Array#select.
104
+ # <tt>Array#select</tt>.
103
105
  #
104
106
  # person.pets.select { |pet| /oo/.match?(pet.name) }
105
107
  # # => [
@@ -108,7 +110,7 @@ module ActiveRecord
108
110
  # # ]
109
111
 
110
112
  # Finds an object in the collection responding to the +id+. Uses the same
111
- # rules as ActiveRecord::Base.find. Returns ActiveRecord::RecordNotFound
113
+ # rules as ActiveRecord::FinderMethods.find. Returns ActiveRecord::RecordNotFound
112
114
  # error if the object cannot be found.
113
115
  #
114
116
  # class Person < ActiveRecord::Base
@@ -218,7 +220,7 @@ module ActiveRecord
218
220
  # :call-seq:
219
221
  # third_to_last()
220
222
  #
221
- # Same as #first except returns only the third-to-last record.
223
+ # Same as #last except returns only the third-to-last record.
222
224
 
223
225
  ##
224
226
  # :method: second_to_last
@@ -226,7 +228,7 @@ module ActiveRecord
226
228
  # :call-seq:
227
229
  # second_to_last()
228
230
  #
229
- # Same as #first except returns only the second-to-last record.
231
+ # Same as #last except returns only the second-to-last record.
230
232
 
231
233
  # Returns the last record, or the last +n+ records, from the collection.
232
234
  # If the collection is empty, the first form returns +nil+, and the second
@@ -260,7 +262,7 @@ module ActiveRecord
260
262
  end
261
263
 
262
264
  # Gives a record (or N records if a parameter is supplied) from the collection
263
- # using the same rules as <tt>ActiveRecord::Base.take</tt>.
265
+ # using the same rules as ActiveRecord::FinderMethods.take.
264
266
  #
265
267
  # class Person < ActiveRecord::Base
266
268
  # has_many :pets
@@ -382,7 +384,7 @@ module ActiveRecord
382
384
  # # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
383
385
  #
384
386
  # If the supplied array has an incorrect association type, it raises
385
- # an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
387
+ # an ActiveRecord::AssociationTypeMismatch error:
386
388
  #
387
389
  # person.pets.replace(["doo", "ggie", "gaga"])
388
390
  # # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
@@ -475,7 +477,7 @@ module ActiveRecord
475
477
 
476
478
  # Deletes the records of the collection directly from the database
477
479
  # ignoring the +:dependent+ option. Records are instantiated and it
478
- # invokes +before_remove+, +after_remove+ , +before_destroy+ and
480
+ # invokes +before_remove+, +after_remove+, +before_destroy+, and
479
481
  # +after_destroy+ callbacks.
480
482
  #
481
483
  # class Person < ActiveRecord::Base
@@ -813,7 +815,7 @@ module ActiveRecord
813
815
  # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
814
816
  # it is equivalent to <tt>!collection.exists?</tt>. If the collection has
815
817
  # not already been loaded and you are going to fetch the records anyway it
816
- # is better to check <tt>collection.length.zero?</tt>.
818
+ # is better to check <tt>collection.load.empty?</tt>.
817
819
  #
818
820
  # class Person < ActiveRecord::Base
819
821
  # has_many :pets
@@ -849,6 +851,11 @@ module ActiveRecord
849
851
  # person.pets.count # => 1
850
852
  # person.pets.any? # => true
851
853
  #
854
+ # Calling it without a block when the collection is not yet
855
+ # loaded is equivalent to <tt>collection.exists?</tt>.
856
+ # If you're going to load the collection anyway, it is better
857
+ # to call <tt>collection.load.any?</tt> to avoid an extra query.
858
+ #
852
859
  # You can also pass a +block+ to define criteria. The behavior
853
860
  # is the same, it returns true if the collection based on the
854
861
  # criteria is not empty.
@@ -921,11 +928,24 @@ module ActiveRecord
921
928
  !!@association.include?(record)
922
929
  end
923
930
 
924
- def proxy_association # :nodoc:
931
+ # Returns the association object for the collection.
932
+ #
933
+ # class Person < ActiveRecord::Base
934
+ # has_many :pets
935
+ # end
936
+ #
937
+ # person.pets.proxy_association
938
+ # # => #<ActiveRecord::Associations::HasManyAssociation owner="#<Person:0x00>">
939
+ #
940
+ # Returns the same object as <tt>person.association(:pets)</tt>,
941
+ # allowing you to make calls like <tt>person.pets.proxy_association.owner</tt>.
942
+ #
943
+ # See Associations::ClassMethods@Association+extensions for more.
944
+ def proxy_association
925
945
  @association
926
946
  end
927
947
 
928
- # Returns a <tt>Relation</tt> object for the records in this association
948
+ # Returns a Relation object for the records in this association
929
949
  def scope
930
950
  @scope ||= @association.scope
931
951
  end
@@ -950,10 +970,13 @@ module ActiveRecord
950
970
  # person.pets == other
951
971
  # # => true
952
972
  #
973
+ #
974
+ # Note that unpersisted records can still be seen as equal:
975
+ #
953
976
  # other = [Pet.new(id: 1), Pet.new(id: 2)]
954
977
  #
955
978
  # person.pets == other
956
- # # => false
979
+ # # => true
957
980
  def ==(other)
958
981
  load_target == other
959
982
  end
@@ -1097,13 +1120,18 @@ module ActiveRecord
1097
1120
  super
1098
1121
  end
1099
1122
 
1123
+ def pretty_print(pp) # :nodoc:
1124
+ load_target if find_from_target?
1125
+ super
1126
+ end
1127
+
1100
1128
  delegate_methods = [
1101
1129
  QueryMethods,
1102
1130
  SpawnMethods,
1103
1131
  ].flat_map { |klass|
1104
1132
  klass.public_instance_methods(false)
1105
1133
  } - self.public_instance_methods(false) - [:select] + [
1106
- :scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
1134
+ :scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all, :load_async
1107
1135
  ]
1108
1136
 
1109
1137
  delegate(*delegate_methods, to: :scope)
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class DisableJoinsAssociationScope < AssociationScope # :nodoc:
6
+ def scope(association)
7
+ source_reflection = association.reflection
8
+ owner = association.owner
9
+ unscoped = association.klass.unscoped
10
+ reverse_chain = get_chain(source_reflection, association, unscoped.alias_tracker).reverse
11
+
12
+ last_reflection, last_ordered, last_join_ids = last_scope_chain(reverse_chain, owner)
13
+
14
+ add_constraints(last_reflection, last_reflection.join_primary_key, last_join_ids, owner, last_ordered)
15
+ end
16
+
17
+ private
18
+ def last_scope_chain(reverse_chain, owner)
19
+ first_item = reverse_chain.shift
20
+ first_scope = [first_item, false, [owner._read_attribute(first_item.join_foreign_key)]]
21
+
22
+ reverse_chain.inject(first_scope) do |(reflection, ordered, join_ids), next_reflection|
23
+ key = reflection.join_primary_key
24
+ records = add_constraints(reflection, key, join_ids, owner, ordered)
25
+ foreign_key = next_reflection.join_foreign_key
26
+ record_ids = records.pluck(foreign_key)
27
+ records_ordered = records && records.order_values.any?
28
+
29
+ [next_reflection, records_ordered, record_ids]
30
+ end
31
+ end
32
+
33
+ def add_constraints(reflection, key, join_ids, owner, ordered)
34
+ scope = reflection.build_scope(reflection.aliased_table).where(key => join_ids)
35
+
36
+ relation = reflection.klass.scope_for_association
37
+ scope.merge!(
38
+ relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins)
39
+ )
40
+
41
+ scope = reflection.constraints.inject(scope) do |memo, scope_chain_item|
42
+ item = eval_scope(reflection, scope_chain_item, owner)
43
+ scope.unscope!(*item.unscope_values)
44
+ scope.where_clause += item.where_clause
45
+ scope.order_values = item.order_values | scope.order_values
46
+ scope
47
+ end
48
+
49
+ if scope.order_values.empty? && ordered
50
+ split_scope = DisableJoinsAssociationRelation.create(scope.klass, key, join_ids)
51
+ split_scope.where_clause += scope.where_clause
52
+ split_scope
53
+ else
54
+ scope
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,265 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class AssociationNotFoundError < ConfigurationError # :nodoc:
5
+ attr_reader :record, :association_name
6
+
7
+ def initialize(record = nil, association_name = nil)
8
+ @record = record
9
+ @association_name = association_name
10
+ if record && association_name
11
+ super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
12
+ else
13
+ super("Association was not found.")
14
+ end
15
+ end
16
+
17
+ if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
18
+ include DidYouMean::Correctable
19
+
20
+ def corrections
21
+ if record && association_name
22
+ @corrections ||= begin
23
+ maybe_these = record.class.reflections.keys
24
+ DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(association_name)
25
+ end
26
+ else
27
+ []
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ class InverseOfAssociationNotFoundError < ActiveRecordError # :nodoc:
34
+ attr_reader :reflection, :associated_class
35
+
36
+ def initialize(reflection = nil, associated_class = nil)
37
+ if reflection
38
+ @reflection = reflection
39
+ @associated_class = associated_class.nil? ? reflection.klass : associated_class
40
+ super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
41
+ else
42
+ super("Could not find the inverse association.")
43
+ end
44
+ end
45
+
46
+ if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
47
+ include DidYouMean::Correctable
48
+
49
+ def corrections
50
+ if reflection && associated_class
51
+ @corrections ||= begin
52
+ maybe_these = associated_class.reflections.keys
53
+ DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(reflection.options[:inverse_of].to_s)
54
+ end
55
+ else
56
+ []
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ class InverseOfAssociationRecursiveError < ActiveRecordError # :nodoc:
63
+ attr_reader :reflection
64
+ def initialize(reflection = nil)
65
+ if reflection
66
+ @reflection = reflection
67
+ super("Inverse association #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{reflection.class_name}) is recursive.")
68
+ else
69
+ super("Inverse association is recursive.")
70
+ end
71
+ end
72
+ end
73
+
74
+ class HasManyThroughAssociationNotFoundError < ActiveRecordError # :nodoc:
75
+ attr_reader :owner_class, :reflection
76
+
77
+ def initialize(owner_class = nil, reflection = nil)
78
+ if owner_class && reflection
79
+ @owner_class = owner_class
80
+ @reflection = reflection
81
+ super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class.name}")
82
+ else
83
+ super("Could not find the association.")
84
+ end
85
+ end
86
+
87
+ if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
88
+ include DidYouMean::Correctable
89
+
90
+ def corrections
91
+ if owner_class && reflection
92
+ @corrections ||= begin
93
+ maybe_these = owner_class.reflections.keys
94
+ maybe_these -= [reflection.name.to_s] # remove failing reflection
95
+ DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(reflection.options[:through].to_s)
96
+ end
97
+ else
98
+ []
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError # :nodoc:
105
+ def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil)
106
+ if owner_class_name && reflection && source_reflection
107
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.")
108
+ else
109
+ super("Cannot have a has_many :through association.")
110
+ end
111
+ end
112
+ end
113
+
114
+ class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError # :nodoc:
115
+ def initialize(owner_class_name = nil, reflection = nil)
116
+ if owner_class_name && reflection
117
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
118
+ else
119
+ super("Cannot have a has_many :through association.")
120
+ end
121
+ end
122
+ end
123
+
124
+ class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError # :nodoc:
125
+ def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil)
126
+ if owner_class_name && reflection && source_reflection
127
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
128
+ else
129
+ super("Cannot have a has_many :through association.")
130
+ end
131
+ end
132
+ end
133
+
134
+ class HasOneThroughCantAssociateThroughCollection < ActiveRecordError # :nodoc:
135
+ def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil)
136
+ if owner_class_name && reflection && through_reflection
137
+ super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.")
138
+ else
139
+ super("Cannot have a has_one :through association.")
140
+ end
141
+ end
142
+ end
143
+
144
+ class HasOneAssociationPolymorphicThroughError < ActiveRecordError # :nodoc:
145
+ def initialize(owner_class_name = nil, reflection = nil)
146
+ if owner_class_name && reflection
147
+ super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
148
+ else
149
+ super("Cannot have a has_one :through association.")
150
+ end
151
+ end
152
+ end
153
+
154
+ class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError # :nodoc:
155
+ def initialize(reflection = nil)
156
+ if reflection
157
+ through_reflection = reflection.through_reflection
158
+ source_reflection_names = reflection.source_reflection_names
159
+ source_associations = reflection.through_reflection.klass._reflections.keys
160
+ super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')}?")
161
+ else
162
+ super("Could not find the source association(s).")
163
+ end
164
+ end
165
+ end
166
+
167
+ class HasManyThroughOrderError < ActiveRecordError # :nodoc:
168
+ def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil)
169
+ if owner_class_name && reflection && through_reflection
170
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through '#{owner_class_name}##{through_reflection.name}' before the through association is defined.")
171
+ else
172
+ super("Cannot have a has_many :through association before the through association is defined.")
173
+ end
174
+ end
175
+ end
176
+
177
+ class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError # :nodoc:
178
+ def initialize(owner = nil, reflection = nil)
179
+ if owner && reflection
180
+ super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
181
+ else
182
+ super("Cannot modify association.")
183
+ end
184
+ end
185
+ end
186
+
187
+ class CompositePrimaryKeyMismatchError < ActiveRecordError # :nodoc:
188
+ attr_reader :reflection
189
+
190
+ def initialize(reflection = nil)
191
+ if reflection
192
+ if reflection.has_one? || reflection.collection?
193
+ super("Association #{reflection.active_record}##{reflection.name} primary key #{reflection.active_record_primary_key} doesn't match with foreign key #{reflection.foreign_key}. Please specify query_constraints, or primary_key and foreign_key values.")
194
+ else
195
+ super("Association #{reflection.active_record}##{reflection.name} primary key #{reflection.association_primary_key} doesn't match with foreign key #{reflection.foreign_key}. Please specify query_constraints, or primary_key and foreign_key values.")
196
+ end
197
+ else
198
+ super("Association primary key doesn't match with foreign key.")
199
+ end
200
+ end
201
+ end
202
+
203
+ class AmbiguousSourceReflectionForThroughAssociation < ActiveRecordError # :nodoc:
204
+ def initialize(klass, macro, association_name, options, possible_sources)
205
+ example_options = options.dup
206
+ example_options[:source] = possible_sources.first
207
+
208
+ super("Ambiguous source reflection for through association. Please " \
209
+ "specify a :source directive on your declaration like:\n" \
210
+ "\n" \
211
+ " class #{klass} < ActiveRecord::Base\n" \
212
+ " #{macro} :#{association_name}, #{example_options}\n" \
213
+ " end"
214
+ )
215
+ end
216
+ end
217
+
218
+ class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection # :nodoc:
219
+ end
220
+
221
+ class HasOneThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection # :nodoc:
222
+ end
223
+
224
+ class ThroughNestedAssociationsAreReadonly < ActiveRecordError # :nodoc:
225
+ def initialize(owner = nil, reflection = nil)
226
+ if owner && reflection
227
+ super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
228
+ else
229
+ super("Through nested associations are read-only.")
230
+ end
231
+ end
232
+ end
233
+
234
+ class HasManyThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly # :nodoc:
235
+ end
236
+
237
+ class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly # :nodoc:
238
+ end
239
+
240
+ # This error is raised when trying to eager load a polymorphic association using a JOIN.
241
+ # Eager loading polymorphic associations is only possible with
242
+ # {ActiveRecord::Relation#preload}[rdoc-ref:QueryMethods#preload].
243
+ class EagerLoadPolymorphicError < ActiveRecordError
244
+ def initialize(reflection = nil)
245
+ if reflection
246
+ super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}")
247
+ else
248
+ super("Eager load polymorphic error.")
249
+ end
250
+ end
251
+ end
252
+
253
+ # This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations
254
+ # (has_many, has_one) when there is at least 1 child associated instance.
255
+ # ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
256
+ class DeleteRestrictionError < ActiveRecordError # :nodoc:
257
+ def initialize(name = nil)
258
+ if name
259
+ super("Cannot delete record because of dependent #{name}")
260
+ else
261
+ super("Delete restriction error.")
262
+ end
263
+ end
264
+ end
265
+ end
@@ -12,7 +12,7 @@ module ActiveRecord::Associations
12
12
 
13
13
  def nullified_owner_attributes
14
14
  Hash.new.tap do |attrs|
15
- attrs[reflection.foreign_key] = nil
15
+ Array(reflection.foreign_key).each { |foreign_key| attrs[foreign_key] = nil }
16
16
  attrs[reflection.type] = nil if reflection.type.present?
17
17
  end
18
18
  end
@@ -22,8 +22,15 @@ module ActiveRecord::Associations
22
22
  def set_owner_attributes(record)
23
23
  return if options[:through]
24
24
 
25
- key = owner._read_attribute(reflection.join_foreign_key)
26
- record._write_attribute(reflection.join_primary_key, key)
25
+ primary_key_attribute_names = Array(reflection.join_primary_key)
26
+ foreign_key_attribute_names = Array(reflection.join_foreign_key)
27
+
28
+ primary_key_foreign_key_pairs = primary_key_attribute_names.zip(foreign_key_attribute_names)
29
+
30
+ primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
31
+ value = owner._read_attribute(foreign_key)
32
+ record._write_attribute(primary_key, value)
33
+ end
27
34
 
28
35
  if reflection.type
29
36
  record._write_attribute(reflection.type, owner.class.polymorphic_name)
@@ -3,11 +3,12 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Has Many Association
6
+ #
6
7
  # This is the proxy that handles a has many association.
7
8
  #
8
9
  # If the association has a <tt>:through</tt> option further specialization
9
10
  # is provided by its child HasManyThroughAssociation.
10
- class HasManyAssociation < CollectionAssociation #:nodoc:
11
+ class HasManyAssociation < CollectionAssociation # :nodoc:
11
12
  include ForeignAssociation
12
13
 
13
14
  def handle_dependency
@@ -33,20 +34,24 @@ module ActiveRecord
33
34
 
34
35
  unless target.empty?
35
36
  association_class = target.first.class
36
- primary_key_column = association_class.primary_key.to_sym
37
-
38
- ids = target.collect do |assoc|
39
- assoc.public_send(primary_key_column)
37
+ if association_class.query_constraints_list
38
+ primary_key_column = association_class.query_constraints_list
39
+ ids = target.collect { |assoc| primary_key_column.map { |col| assoc.public_send(col) } }
40
+ else
41
+ primary_key_column = association_class.primary_key
42
+ ids = target.collect { |assoc| assoc.public_send(primary_key_column) }
40
43
  end
41
44
 
42
- enqueue_destroy_association(
43
- owner_model_name: owner.class.to_s,
44
- owner_id: owner.id,
45
- association_class: reflection.klass.to_s,
46
- association_ids: ids,
47
- association_primary_key_column: primary_key_column,
48
- ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
49
- )
45
+ ids.each_slice(owner.class.destroy_association_async_batch_size || ids.size) do |ids_batch|
46
+ enqueue_destroy_association(
47
+ owner_model_name: owner.class.to_s,
48
+ owner_id: owner.id,
49
+ association_class: reflection.klass.to_s,
50
+ association_ids: ids_batch,
51
+ association_primary_key_column: primary_key_column,
52
+ ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
53
+ )
54
+ end
50
55
  end
51
56
  else
52
57
  delete_all
@@ -73,16 +78,19 @@ module ActiveRecord
73
78
  # If the collection is empty the target is set to an empty array and
74
79
  # the loaded flag is set to true as well.
75
80
  def count_records
76
- count = if reflection.has_cached_counter?
81
+ count = if reflection.has_active_cached_counter?
77
82
  owner.read_attribute(reflection.counter_cache_column).to_i
78
83
  else
79
84
  scope.count(:all)
80
85
  end
81
86
 
82
- # If there's nothing in the database and @target has no new records
83
- # we are certain the current target is an empty array. This is a
84
- # documented side-effect of the method that may avoid an extra SELECT.
85
- loaded! if count == 0
87
+ # If there's nothing in the database, @target should only contain new
88
+ # records or be an empty array. This is a documented side-effect of
89
+ # the method that may avoid an extra SELECT.
90
+ if count == 0
91
+ target.select!(&:new_record?)
92
+ loaded!
93
+ end
86
94
 
87
95
  [association_scope.limit_value, count].compact.min
88
96
  end
@@ -121,7 +129,9 @@ module ActiveRecord
121
129
  records.each(&:destroy!)
122
130
  update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
123
131
  else
124
- scope = self.scope.where(reflection.klass.primary_key => records)
132
+ query_constraints = reflection.klass.composite_query_constraints_list
133
+ values = records.map { |r| query_constraints.map { |col| r._read_attribute(col) } }
134
+ scope = self.scope.where(query_constraints => values)
125
135
  update_counter(-delete_count(method, scope))
126
136
  end
127
137
  end
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Has Many Through Association
6
- class HasManyThroughAssociation < HasManyAssociation #:nodoc:
6
+ class HasManyThroughAssociation < HasManyAssociation # :nodoc:
7
7
  include ThroughAssociation
8
8
 
9
9
  def initialize(owner, reflection)
@@ -59,9 +59,10 @@ module ActiveRecord
59
59
 
60
60
  attributes = through_scope_attributes
61
61
  attributes[source_reflection.name] = record
62
- attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
63
62
 
64
- through_association.build(attributes)
63
+ through_association.build(attributes).tap do |new_record|
64
+ new_record.send("#{source_reflection.foreign_type}=", options[:source_type]) if options[:source_type]
65
+ end
65
66
  end
66
67
  end
67
68
 
@@ -69,9 +70,12 @@ module ActiveRecord
69
70
 
70
71
  def through_scope_attributes
71
72
  scope = through_scope || self.scope
72
- scope.where_values_hash(through_association.reflection.name.to_s).
73
- except!(through_association.reflection.foreign_key,
74
- through_association.reflection.klass.inheritance_column)
73
+ attributes = scope.where_values_hash(through_association.reflection.klass.table_name)
74
+ except_keys = [
75
+ *Array(through_association.reflection.foreign_key),
76
+ through_association.reflection.klass.inheritance_column
77
+ ]
78
+ attributes.except!(*except_keys)
75
79
  end
76
80
 
77
81
  def save_through_record(record)
@@ -89,7 +93,13 @@ module ActiveRecord
89
93
  @through_scope = scope
90
94
  record = super
91
95
 
92
- inverse = source_reflection.inverse_of
96
+ inverse =
97
+ if source_reflection.polymorphic?
98
+ source_reflection.polymorphic_inverse_of(record.class)
99
+ else
100
+ source_reflection.inverse_of
101
+ end
102
+
93
103
  if inverse
94
104
  if inverse.collection?
95
105
  record.send(inverse.name) << build_through_record(record)
@@ -109,7 +119,7 @@ module ActiveRecord
109
119
  end
110
120
 
111
121
  def target_reflection_has_associated_record?
112
- !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
122
+ !(through_reflection.belongs_to? && Array(through_reflection.foreign_key).all? { |foreign_key_column| owner[foreign_key_column].blank? })
113
123
  end
114
124
 
115
125
  def update_through_counter?(method)
@@ -214,6 +224,7 @@ module ActiveRecord
214
224
 
215
225
  def find_target
216
226
  return [] unless target_reflection_has_associated_record?
227
+ return scope.to_a if disable_joins
217
228
  super
218
229
  end
219
230