activerecord 6.1.7 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -1385
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +17 -14
  7. data/lib/active_record/association_relation.rb +2 -12
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +60 -21
  10. data/lib/active_record/associations/association_scope.rb +17 -12
  11. data/lib/active_record/associations/belongs_to_association.rb +37 -11
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
  13. data/lib/active_record/associations/builder/association.rb +11 -5
  14. data/lib/active_record/associations/builder/belongs_to.rb +41 -14
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +4 -4
  19. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  20. data/lib/active_record/associations/collection_association.rb +46 -36
  21. data/lib/active_record/associations/collection_proxy.rb +44 -16
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +10 -3
  25. data/lib/active_record/associations/has_many_association.rb +29 -19
  26. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  27. data/lib/active_record/associations/has_one_association.rb +20 -10
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  30. data/lib/active_record/associations/join_dependency.rb +23 -15
  31. data/lib/active_record/associations/nested_error.rb +47 -0
  32. data/lib/active_record/associations/preloader/association.rb +212 -53
  33. data/lib/active_record/associations/preloader/batch.rb +48 -0
  34. data/lib/active_record/associations/preloader/branch.rb +153 -0
  35. data/lib/active_record/associations/preloader/through_association.rb +50 -16
  36. data/lib/active_record/associations/preloader.rb +50 -121
  37. data/lib/active_record/associations/singular_association.rb +15 -3
  38. data/lib/active_record/associations/through_association.rb +25 -14
  39. data/lib/active_record/associations.rb +404 -509
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +2 -14
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  43. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  44. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  45. data/lib/active_record/attribute_methods/primary_key.rb +47 -27
  46. data/lib/active_record/attribute_methods/query.rb +31 -19
  47. data/lib/active_record/attribute_methods/read.rb +14 -11
  48. data/lib/active_record/attribute_methods/serialization.rb +174 -37
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -9
  50. data/lib/active_record/attribute_methods/write.rb +12 -15
  51. data/lib/active_record/attribute_methods.rb +164 -52
  52. data/lib/active_record/attributes.rb +51 -49
  53. data/lib/active_record/autosave_association.rb +74 -57
  54. data/lib/active_record/base.rb +27 -5
  55. data/lib/active_record/callbacks.rb +18 -34
  56. data/lib/active_record/coders/column_serializer.rb +61 -0
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +70 -46
  59. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +327 -612
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -64
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +377 -142
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +345 -166
  75. data/lib/active_record/connection_adapters/column.rb +13 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
  78. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
  79. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  81. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  82. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  83. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
  85. data/lib/active_record/connection_adapters/pool_config.rb +26 -16
  86. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  87. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  88. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
  89. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  94. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  100. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
  103. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  104. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  105. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
  106. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  107. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +401 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +518 -251
  110. data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
  111. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  112. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
  113. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
  114. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
  119. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  120. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  121. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  122. data/lib/active_record/connection_adapters.rb +130 -6
  123. data/lib/active_record/connection_handling.rb +132 -146
  124. data/lib/active_record/core.rb +276 -251
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -3
  127. data/lib/active_record/database_configurations/database_config.rb +34 -10
  128. data/lib/active_record/database_configurations/hash_config.rb +107 -31
  129. data/lib/active_record/database_configurations/url_config.rb +38 -13
  130. data/lib/active_record/database_configurations.rb +96 -60
  131. data/lib/active_record/delegated_type.rb +90 -20
  132. data/lib/active_record/deprecator.rb +7 -0
  133. data/lib/active_record/destroy_association_async_job.rb +4 -2
  134. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  135. data/lib/active_record/dynamic_matchers.rb +3 -3
  136. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +68 -0
  140. data/lib/active_record/encryption/configurable.rb +60 -0
  141. data/lib/active_record/encryption/context.rb +42 -0
  142. data/lib/active_record/encryption/contexts.rb +76 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +170 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +53 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  159. data/lib/active_record/encryption/message_serializer.rb +96 -0
  160. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  161. data/lib/active_record/encryption/properties.rb +76 -0
  162. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  163. data/lib/active_record/encryption/scheme.rb +100 -0
  164. data/lib/active_record/encryption.rb +56 -0
  165. data/lib/active_record/enum.rb +163 -63
  166. data/lib/active_record/errors.rb +210 -27
  167. data/lib/active_record/explain.rb +21 -12
  168. data/lib/active_record/explain_registry.rb +11 -6
  169. data/lib/active_record/explain_subscriber.rb +1 -1
  170. data/lib/active_record/fixture_set/file.rb +15 -1
  171. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  172. data/lib/active_record/fixture_set/render_context.rb +2 -0
  173. data/lib/active_record/fixture_set/table_row.rb +70 -14
  174. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  175. data/lib/active_record/fixtures.rb +179 -112
  176. data/lib/active_record/future_result.rb +178 -0
  177. data/lib/active_record/gem_version.rb +4 -4
  178. data/lib/active_record/inheritance.rb +85 -31
  179. data/lib/active_record/insert_all.rb +148 -32
  180. data/lib/active_record/integration.rb +14 -10
  181. data/lib/active_record/internal_metadata.rb +123 -23
  182. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  183. data/lib/active_record/locking/optimistic.rb +43 -27
  184. data/lib/active_record/locking/pessimistic.rb +15 -6
  185. data/lib/active_record/log_subscriber.rb +41 -29
  186. data/lib/active_record/marshalling.rb +56 -0
  187. data/lib/active_record/message_pack.rb +124 -0
  188. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  189. data/lib/active_record/middleware/database_selector.rb +23 -13
  190. data/lib/active_record/middleware/shard_selector.rb +62 -0
  191. data/lib/active_record/migration/command_recorder.rb +113 -16
  192. data/lib/active_record/migration/compatibility.rb +235 -46
  193. data/lib/active_record/migration/default_strategy.rb +22 -0
  194. data/lib/active_record/migration/execution_strategy.rb +19 -0
  195. data/lib/active_record/migration/join_table.rb +1 -1
  196. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  197. data/lib/active_record/migration.rb +374 -177
  198. data/lib/active_record/model_schema.rb +143 -159
  199. data/lib/active_record/nested_attributes.rb +48 -21
  200. data/lib/active_record/no_touching.rb +3 -3
  201. data/lib/active_record/normalization.rb +163 -0
  202. data/lib/active_record/persistence.rb +282 -283
  203. data/lib/active_record/promise.rb +84 -0
  204. data/lib/active_record/query_cache.rb +19 -25
  205. data/lib/active_record/query_logs.rb +189 -0
  206. data/lib/active_record/query_logs_formatter.rb +41 -0
  207. data/lib/active_record/querying.rb +44 -9
  208. data/lib/active_record/railtie.rb +234 -71
  209. data/lib/active_record/railties/controller_runtime.rb +25 -11
  210. data/lib/active_record/railties/databases.rake +189 -256
  211. data/lib/active_record/railties/job_runtime.rb +23 -0
  212. data/lib/active_record/readonly_attributes.rb +41 -3
  213. data/lib/active_record/reflection.rb +325 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +198 -63
  216. data/lib/active_record/relation/calculations.rb +300 -111
  217. data/lib/active_record/relation/delegation.rb +33 -22
  218. data/lib/active_record/relation/finder_methods.rb +123 -52
  219. data/lib/active_record/relation/merger.rb +26 -19
  220. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  221. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  222. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  223. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  224. data/lib/active_record/relation/predicate_builder.rb +29 -22
  225. data/lib/active_record/relation/query_attribute.rb +30 -12
  226. data/lib/active_record/relation/query_methods.rb +842 -150
  227. data/lib/active_record/relation/record_fetch_warning.rb +10 -9
  228. data/lib/active_record/relation/spawn_methods.rb +7 -6
  229. data/lib/active_record/relation/where_clause.rb +15 -36
  230. data/lib/active_record/relation.rb +736 -145
  231. data/lib/active_record/result.rb +67 -54
  232. data/lib/active_record/runtime_registry.rb +71 -13
  233. data/lib/active_record/sanitization.rb +84 -34
  234. data/lib/active_record/schema.rb +39 -23
  235. data/lib/active_record/schema_dumper.rb +90 -31
  236. data/lib/active_record/schema_migration.rb +74 -23
  237. data/lib/active_record/scoping/default.rb +72 -15
  238. data/lib/active_record/scoping/named.rb +5 -13
  239. data/lib/active_record/scoping.rb +65 -34
  240. data/lib/active_record/secure_password.rb +60 -0
  241. data/lib/active_record/secure_token.rb +21 -3
  242. data/lib/active_record/serialization.rb +6 -1
  243. data/lib/active_record/signed_id.rb +30 -9
  244. data/lib/active_record/statement_cache.rb +7 -7
  245. data/lib/active_record/store.rb +10 -10
  246. data/lib/active_record/suppressor.rb +13 -15
  247. data/lib/active_record/table_metadata.rb +7 -3
  248. data/lib/active_record/tasks/database_tasks.rb +277 -149
  249. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  250. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  251. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  252. data/lib/active_record/test_databases.rb +1 -1
  253. data/lib/active_record/test_fixtures.rb +173 -155
  254. data/lib/active_record/testing/query_assertions.rb +121 -0
  255. data/lib/active_record/timestamp.rb +32 -19
  256. data/lib/active_record/token_for.rb +123 -0
  257. data/lib/active_record/touch_later.rb +12 -7
  258. data/lib/active_record/transaction.rb +132 -0
  259. data/lib/active_record/transactions.rb +118 -41
  260. data/lib/active_record/translation.rb +3 -5
  261. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  262. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  263. data/lib/active_record/type/internal/timezone.rb +7 -2
  264. data/lib/active_record/type/serialized.rb +9 -7
  265. data/lib/active_record/type/time.rb +4 -0
  266. data/lib/active_record/type/type_map.rb +17 -20
  267. data/lib/active_record/type.rb +1 -2
  268. data/lib/active_record/type_caster/connection.rb +4 -4
  269. data/lib/active_record/validations/absence.rb +1 -1
  270. data/lib/active_record/validations/associated.rb +13 -7
  271. data/lib/active_record/validations/numericality.rb +5 -4
  272. data/lib/active_record/validations/presence.rb +5 -28
  273. data/lib/active_record/validations/uniqueness.rb +64 -15
  274. data/lib/active_record/validations.rb +12 -5
  275. data/lib/active_record/version.rb +1 -1
  276. data/lib/active_record.rb +444 -32
  277. data/lib/arel/alias_predication.rb +1 -1
  278. data/lib/arel/attributes/attribute.rb +0 -8
  279. data/lib/arel/collectors/bind.rb +2 -0
  280. data/lib/arel/collectors/composite.rb +7 -0
  281. data/lib/arel/collectors/sql_string.rb +1 -1
  282. data/lib/arel/collectors/substitute_binds.rb +1 -1
  283. data/lib/arel/crud.rb +28 -22
  284. data/lib/arel/delete_manager.rb +18 -4
  285. data/lib/arel/errors.rb +10 -0
  286. data/lib/arel/factory_methods.rb +4 -0
  287. data/lib/arel/filter_predications.rb +9 -0
  288. data/lib/arel/insert_manager.rb +2 -3
  289. data/lib/arel/nodes/binary.rb +6 -7
  290. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  291. data/lib/arel/nodes/casted.rb +1 -1
  292. data/lib/arel/nodes/cte.rb +36 -0
  293. data/lib/arel/nodes/delete_statement.rb +12 -13
  294. data/lib/arel/nodes/filter.rb +10 -0
  295. data/lib/arel/nodes/fragments.rb +35 -0
  296. data/lib/arel/nodes/function.rb +1 -0
  297. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  298. data/lib/arel/nodes/insert_statement.rb +2 -2
  299. data/lib/arel/nodes/leading_join.rb +8 -0
  300. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  301. data/lib/arel/nodes/node.rb +115 -5
  302. data/lib/arel/nodes/select_core.rb +2 -2
  303. data/lib/arel/nodes/select_statement.rb +2 -2
  304. data/lib/arel/nodes/sql_literal.rb +13 -0
  305. data/lib/arel/nodes/table_alias.rb +4 -0
  306. data/lib/arel/nodes/update_statement.rb +8 -3
  307. data/lib/arel/nodes.rb +7 -2
  308. data/lib/arel/predications.rb +14 -4
  309. data/lib/arel/select_manager.rb +11 -5
  310. data/lib/arel/table.rb +9 -6
  311. data/lib/arel/tree_manager.rb +8 -15
  312. data/lib/arel/update_manager.rb +20 -5
  313. data/lib/arel/visitors/dot.rb +81 -90
  314. data/lib/arel/visitors/mysql.rb +23 -5
  315. data/lib/arel/visitors/postgresql.rb +1 -22
  316. data/lib/arel/visitors/to_sql.rb +170 -36
  317. data/lib/arel/visitors/visitor.rb +2 -2
  318. data/lib/arel.rb +23 -4
  319. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  320. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  321. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  322. data/lib/rails/generators/active_record/migration.rb +3 -1
  323. data/lib/rails/generators/active_record/model/USAGE +113 -0
  324. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  325. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  326. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  328. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  329. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  330. metadata +100 -14
  331. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  332. data/lib/active_record/null_relation.rb +0 -67
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Belongs To Association
6
- class BelongsToAssociation < SingularAssociation #:nodoc:
6
+ class BelongsToAssociation < SingularAssociation # :nodoc:
7
7
  def handle_dependency
8
8
  return unless load_target
9
9
 
@@ -11,8 +11,13 @@ module ActiveRecord
11
11
  when :destroy
12
12
  raise ActiveRecord::Rollback unless target.destroy
13
13
  when :destroy_async
14
- id = owner.public_send(reflection.foreign_key.to_sym)
15
- primary_key_column = reflection.active_record_primary_key.to_sym
14
+ if reflection.foreign_key.is_a?(Array)
15
+ primary_key_column = reflection.active_record_primary_key
16
+ id = reflection.foreign_key.map { |col| owner.public_send(col) }
17
+ else
18
+ primary_key_column = reflection.active_record_primary_key
19
+ id = owner.public_send(reflection.foreign_key)
20
+ end
16
21
 
17
22
  enqueue_destroy_association(
18
23
  owner_model_name: owner.class.to_s,
@@ -55,7 +60,8 @@ module ActiveRecord
55
60
 
56
61
  def decrement_counters_before_last_save
57
62
  if reflection.polymorphic?
58
- model_was = owner.attribute_before_last_save(reflection.foreign_type)&.constantize
63
+ model_type_was = owner.attribute_before_last_save(reflection.foreign_type)
64
+ model_was = owner.class.polymorphic_class_for(model_type_was) if model_type_was
59
65
  else
60
66
  model_was = klass
61
67
  end
@@ -68,6 +74,14 @@ module ActiveRecord
68
74
  end
69
75
 
70
76
  def target_changed?
77
+ owner.attribute_changed?(reflection.foreign_key) || (!foreign_key_present? && target&.new_record?)
78
+ end
79
+
80
+ def target_previously_changed?
81
+ owner.attribute_previously_changed?(reflection.foreign_key)
82
+ end
83
+
84
+ def saved_change_to_target?
71
85
  owner.saved_change_to_attribute?(reflection.foreign_key)
72
86
  end
73
87
 
@@ -77,6 +91,8 @@ module ActiveRecord
77
91
  raise_on_type_mismatch!(record)
78
92
  set_inverse_instance(record)
79
93
  @updated = true
94
+ elsif target
95
+ remove_inverse_instance(target)
80
96
  end
81
97
 
82
98
  replace_keys(record, force: true)
@@ -108,10 +124,21 @@ module ActiveRecord
108
124
  end
109
125
 
110
126
  def replace_keys(record, force: false)
111
- target_key = record ? record._read_attribute(primary_key(record.class)) : nil
127
+ reflection_fk = reflection.foreign_key
128
+ if reflection_fk.is_a?(Array)
129
+ target_key_values = record ? Array(primary_key(record.class)).map { |key| record._read_attribute(key) } : []
130
+
131
+ if force || reflection_fk.map { |fk| owner._read_attribute(fk) } != target_key_values
132
+ reflection_fk.each_with_index do |key, index|
133
+ owner[key] = target_key_values[index]
134
+ end
135
+ end
136
+ else
137
+ target_key_value = record ? record._read_attribute(primary_key(record.class)) : nil
112
138
 
113
- if force || owner[reflection.foreign_key] != target_key
114
- owner[reflection.foreign_key] = target_key
139
+ if force || owner._read_attribute(reflection_fk) != target_key_value
140
+ owner[reflection_fk] = target_key_value
141
+ end
115
142
  end
116
143
  end
117
144
 
@@ -120,17 +147,16 @@ module ActiveRecord
120
147
  end
121
148
 
122
149
  def foreign_key_present?
123
- owner._read_attribute(reflection.foreign_key)
150
+ Array(reflection.foreign_key).all? { |fk| owner._read_attribute(fk) }
124
151
  end
125
152
 
126
153
  def invertible_for?(record)
127
154
  inverse = inverse_reflection_for(record)
128
- inverse && (inverse.has_one? || ActiveRecord::Base.has_many_inversing)
155
+ inverse && (inverse.has_one? || inverse.klass.has_many_inversing)
129
156
  end
130
157
 
131
158
  def stale_state
132
- result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
133
- result && result.to_s
159
+ owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
134
160
  end
135
161
  end
136
162
  end
@@ -3,13 +3,21 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Belongs To Polymorphic Association
6
- class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
6
+ class BelongsToPolymorphicAssociation < BelongsToAssociation # :nodoc:
7
7
  def klass
8
8
  type = owner[reflection.foreign_type]
9
9
  type.presence && owner.class.polymorphic_class_for(type)
10
10
  end
11
11
 
12
12
  def target_changed?
13
+ super || owner.attribute_changed?(reflection.foreign_type)
14
+ end
15
+
16
+ def target_previously_changed?
17
+ super || owner.attribute_previously_changed?(reflection.foreign_type)
18
+ end
19
+
20
+ def saved_change_to_target?
13
21
  super || owner.saved_change_to_attribute?(reflection.foreign_type)
14
22
  end
15
23
 
@@ -19,7 +27,7 @@ module ActiveRecord
19
27
 
20
28
  target_type = record ? record.class.polymorphic_name : nil
21
29
 
22
- if force || owner[reflection.foreign_type] != target_type
30
+ if force || owner._read_attribute(reflection.foreign_type) != target_type
23
31
  owner[reflection.foreign_type] = target_type
24
32
  end
25
33
  end
@@ -33,8 +41,9 @@ module ActiveRecord
33
41
  end
34
42
 
35
43
  def stale_state
36
- foreign_key = super
37
- foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
44
+ if foreign_key = super
45
+ [foreign_key, owner[reflection.foreign_type]]
46
+ end
38
47
  end
39
48
  end
40
49
  end
@@ -12,14 +12,14 @@
12
12
  # - HasManyAssociation
13
13
 
14
14
  module ActiveRecord::Associations::Builder # :nodoc:
15
- class Association #:nodoc:
15
+ class Association # :nodoc:
16
16
  class << self
17
17
  attr_accessor :extensions
18
18
  end
19
19
  self.extensions = []
20
20
 
21
21
  VALID_OPTIONS = [
22
- :class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading
22
+ :class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading, :query_constraints
23
23
  ].freeze # :nodoc:
24
24
 
25
25
  def self.build(model, name, scope, options, &block)
@@ -33,6 +33,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
33
33
  define_accessors model, reflection
34
34
  define_callbacks model, reflection
35
35
  define_validations model, reflection
36
+ define_change_tracking_methods model, reflection
36
37
  reflection
37
38
  end
38
39
 
@@ -117,14 +118,18 @@ module ActiveRecord::Associations::Builder # :nodoc:
117
118
  # noop
118
119
  end
119
120
 
121
+ def self.define_change_tracking_methods(model, reflection)
122
+ # noop
123
+ end
124
+
120
125
  def self.valid_dependent_options
121
126
  raise NotImplementedError
122
127
  end
123
128
 
124
129
  def self.check_dependent_options(dependent, model)
125
130
  if dependent == :destroy_async && !model.destroy_association_async_job
126
- err_message = "ActiveJob is required to use destroy_async on associations"
127
- raise ActiveRecord::ActiveJobRequiredError, err_message
131
+ err_message = "A valid destroy_association_async_job is required to use `dependent: :destroy_async` on associations"
132
+ raise ActiveRecord::ConfigurationError, err_message
128
133
  end
129
134
  unless valid_dependent_options.include? dependent
130
135
  raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
@@ -158,6 +163,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
158
163
 
159
164
  private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions,
160
165
  :define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations,
161
- :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks, :add_after_commit_jobs_callback
166
+ :define_change_tracking_methods, :valid_dependent_options, :check_dependent_options,
167
+ :add_destroy_callbacks, :add_after_commit_jobs_callback
162
168
  end
163
169
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord::Associations::Builder # :nodoc:
4
- class BelongsTo < SingularAssociation #:nodoc:
4
+ class BelongsTo < SingularAssociation # :nodoc:
5
5
  def self.macro
6
6
  :belongs_to
7
7
  end
@@ -30,17 +30,18 @@ module ActiveRecord::Associations::Builder # :nodoc:
30
30
  model.after_update lambda { |record|
31
31
  association = association(reflection.name)
32
32
 
33
- if association.target_changed?
33
+ if association.saved_change_to_target?
34
34
  association.increment_counters
35
35
  association.decrement_counters_before_last_save
36
36
  end
37
37
  }
38
38
 
39
39
  klass = reflection.class_name.safe_constantize
40
- klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
40
+ klass._counter_cache_columns |= [cache_column] if klass && klass.respond_to?(:_counter_cache_columns)
41
+ model.counter_cached_association_names |= [reflection.name]
41
42
  end
42
43
 
43
- def self.touch_record(o, changes, foreign_key, name, touch, touch_method) # :nodoc:
44
+ def self.touch_record(o, changes, foreign_key, name, touch) # :nodoc:
44
45
  old_foreign_id = changes[foreign_key] && changes[foreign_key].first
45
46
 
46
47
  if old_foreign_id
@@ -49,7 +50,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
49
50
  if reflection.polymorphic?
50
51
  foreign_type = reflection.foreign_type
51
52
  klass = changes[foreign_type] && changes[foreign_type].first || o.public_send(foreign_type)
52
- klass = klass.constantize
53
+ klass = o.class.polymorphic_class_for(klass)
53
54
  else
54
55
  klass = association.klass
55
56
  end
@@ -58,9 +59,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
58
59
 
59
60
  if old_record
60
61
  if touch != true
61
- old_record.public_send(touch_method, touch)
62
+ old_record.touch_later(touch)
62
63
  else
63
- old_record.public_send(touch_method)
64
+ old_record.touch_later
64
65
  end
65
66
  end
66
67
  end
@@ -68,9 +69,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
68
69
  record = o.public_send name
69
70
  if record && record.persisted?
70
71
  if touch != true
71
- record.public_send(touch_method, touch)
72
+ record.touch_later(touch)
72
73
  else
73
- record.public_send(touch_method)
74
+ record.touch_later
74
75
  end
75
76
  end
76
77
  end
@@ -81,13 +82,13 @@ module ActiveRecord::Associations::Builder # :nodoc:
81
82
  touch = reflection.options[:touch]
82
83
 
83
84
  callback = lambda { |changes_method| lambda { |record|
84
- BelongsTo.touch_record(record, record.send(changes_method), foreign_key, name, touch, belongs_to_touch_method)
85
+ BelongsTo.touch_record(record, record.send(changes_method), foreign_key, name, touch)
85
86
  }}
86
87
 
87
88
  if reflection.counter_cache_column
88
89
  touch_callback = callback.(:saved_changes)
89
90
  update_callback = lambda { |record|
90
- instance_exec(record, &touch_callback) unless association(reflection.name).target_changed?
91
+ instance_exec(record, &touch_callback) unless association(reflection.name).saved_change_to_target?
91
92
  }
92
93
  model.after_update update_callback, if: :saved_changes?
93
94
  else
@@ -123,11 +124,37 @@ module ActiveRecord::Associations::Builder # :nodoc:
123
124
  super
124
125
 
125
126
  if required
126
- model.validates_presence_of reflection.name, message: :required
127
+ if ActiveRecord.belongs_to_required_validates_foreign_key
128
+ model.validates_presence_of reflection.name, message: :required
129
+ else
130
+ condition = lambda { |record|
131
+ foreign_key = reflection.foreign_key
132
+ foreign_type = reflection.foreign_type
133
+
134
+ record.read_attribute(foreign_key).nil? ||
135
+ record.attribute_changed?(foreign_key) ||
136
+ (reflection.polymorphic? && (record.read_attribute(foreign_type).nil? || record.attribute_changed?(foreign_type)))
137
+ }
138
+
139
+ model.validates_presence_of reflection.name, message: :required, if: condition
140
+ end
127
141
  end
128
142
  end
129
143
 
130
- private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks, :define_validations,
131
- :add_counter_cache_callbacks, :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks
144
+ def self.define_change_tracking_methods(model, reflection)
145
+ model.generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
146
+ def #{reflection.name}_changed?
147
+ association(:#{reflection.name}).target_changed?
148
+ end
149
+
150
+ def #{reflection.name}_previously_changed?
151
+ association(:#{reflection.name}).target_previously_changed?
152
+ end
153
+ CODE
154
+ end
155
+
156
+ private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks,
157
+ :define_validations, :define_change_tracking_methods, :add_counter_cache_callbacks,
158
+ :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks
132
159
  end
133
160
  end
@@ -3,7 +3,7 @@
3
3
  require "active_record/associations"
4
4
 
5
5
  module ActiveRecord::Associations::Builder # :nodoc:
6
- class CollectionAssociation < Association #:nodoc:
6
+ class CollectionAssociation < Association # :nodoc:
7
7
  CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
8
8
 
9
9
  def self.valid_options(options)
@@ -30,11 +30,18 @@ module ActiveRecord::Associations::Builder # :nodoc:
30
30
  def self.define_callback(model, callback_name, name, options)
31
31
  full_callback_name = "#{callback_name}_for_#{name}"
32
32
 
33
- unless model.method_defined?(full_callback_name)
33
+ callback_values = Array(options[callback_name.to_sym])
34
+ method_defined = model.respond_to?(full_callback_name)
35
+
36
+ # If there are no callbacks, we must also check if a superclass had
37
+ # previously defined this association
38
+ return if callback_values.empty? && !method_defined
39
+
40
+ unless method_defined
34
41
  model.class_attribute(full_callback_name, instance_accessor: false, instance_predicate: false)
35
42
  end
36
43
 
37
- callbacks = Array(options[callback_name.to_sym]).map do |callback|
44
+ callbacks = callback_values.map do |callback|
38
45
  case callback
39
46
  when Symbol
40
47
  ->(method, owner, record) { owner.send(callback, record) }
@@ -20,6 +20,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
20
20
  attr_accessor :right_reflection
21
21
  end
22
22
 
23
+ @table_name = nil
23
24
  def self.table_name
24
25
  # Table name needs to be resolved lazily
25
26
  # because RHS class might not have been loaded
@@ -41,14 +42,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
41
42
  self.right_reflection = _reflect_on_association(rhs_name)
42
43
  end
43
44
 
44
- def self.retrieve_connection
45
- left_model.retrieve_connection
45
+ def self.connection_pool
46
+ left_model.connection_pool
46
47
  end
47
-
48
- private
49
- def self.suppress_composite_primary_key(pk)
50
- pk unless pk.is_a?(Array)
51
- end
52
48
  }
53
49
 
54
50
  join_model.name = "HABTM_#{association_name.to_s.camelize}"
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord::Associations::Builder # :nodoc:
4
- class HasMany < CollectionAssociation #:nodoc:
4
+ class HasMany < CollectionAssociation # :nodoc:
5
5
  def self.macro
6
6
  :has_many
7
7
  end
8
8
 
9
9
  def self.valid_options(options)
10
- valid = super + [:counter_cache, :join_table, :index_errors, :ensuring_owner_was]
11
- valid += [:as, :foreign_type] if options[:as]
12
- valid += [:through, :source, :source_type] if options[:through]
10
+ valid = super + [:counter_cache, :join_table, :index_errors, :as, :through]
11
+ valid += [:foreign_type] if options[:as]
12
+ valid += [:source, :source_type, :disable_joins] if options[:through]
13
13
  valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
14
14
  valid
15
15
  end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord::Associations::Builder # :nodoc:
4
- class HasOne < SingularAssociation #:nodoc:
4
+ class HasOne < SingularAssociation # :nodoc:
5
5
  def self.macro
6
6
  :has_one
7
7
  end
8
8
 
9
9
  def self.valid_options(options)
10
- valid = super
11
- valid += [:as, :foreign_type] if options[:as]
10
+ valid = super + [:as, :through]
11
+ valid += [:foreign_type] if options[:as]
12
12
  valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
13
- valid += [:through, :source, :source_type] if options[:through]
13
+ valid += [:source, :source_type, :disable_joins] if options[:through]
14
14
  valid
15
15
  end
16
16
 
@@ -3,7 +3,7 @@
3
3
  # This class is inherited by the has_one and belongs_to association classes
4
4
 
5
5
  module ActiveRecord::Associations::Builder # :nodoc:
6
- class SingularAssociation < Association #:nodoc:
6
+ class SingularAssociation < Association # :nodoc:
7
7
  def self.valid_options(options)
8
8
  super + [:required, :touch]
9
9
  end
@@ -13,12 +13,16 @@ module ActiveRecord::Associations::Builder # :nodoc:
13
13
  mixin = model.generated_association_methods
14
14
  name = reflection.name
15
15
 
16
- define_constructors(mixin, name) if reflection.constructable?
16
+ define_constructors(mixin, name) unless reflection.polymorphic?
17
17
 
18
18
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
19
19
  def reload_#{name}
20
20
  association(:#{name}).force_reload_reader
21
21
  end
22
+
23
+ def reset_#{name}
24
+ association(:#{name}).reset
25
+ end
22
26
  CODE
23
27
  end
24
28
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
4
+
3
5
  module ActiveRecord
4
6
  module Associations
5
7
  # = Active Record Association Collection
@@ -14,7 +16,7 @@ module ActiveRecord
14
16
  #
15
17
  # The CollectionAssociation class provides common methods to the collections
16
18
  # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
17
- # the +:through association+ option.
19
+ # the <tt>:through association</tt> option.
18
20
  #
19
21
  # You need to be careful with assumptions regarding the target: The proxy
20
22
  # does not fetch records from the database until it needs them, but new
@@ -25,9 +27,13 @@ module ActiveRecord
25
27
  #
26
28
  # If you need to work on all current children, new and existing records,
27
29
  # +load_target+ and the +loaded+ flag are your friends.
28
- class CollectionAssociation < Association #:nodoc:
30
+ class CollectionAssociation < Association # :nodoc:
31
+ attr_accessor :nested_attributes_target
32
+
29
33
  # Implements the reader method, e.g. foo.items for Foo.has_many :items
30
34
  def reader
35
+ ensure_klass_exists!
36
+
31
37
  if stale_target?
32
38
  reload
33
39
  end
@@ -44,11 +50,11 @@ module ActiveRecord
44
50
  # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
45
51
  def ids_reader
46
52
  if loaded?
47
- target.pluck(reflection.association_primary_key)
53
+ target.pluck(*reflection.association_primary_key)
48
54
  elsif !target.empty?
49
- load_target.pluck(reflection.association_primary_key)
55
+ load_target.pluck(*reflection.association_primary_key)
50
56
  else
51
- @association_ids ||= scope.pluck(reflection.association_primary_key)
57
+ @association_ids ||= scope.pluck(*reflection.association_primary_key)
52
58
  end
53
59
  end
54
60
 
@@ -57,14 +63,20 @@ module ActiveRecord
57
63
  primary_key = reflection.association_primary_key
58
64
  pk_type = klass.type_for_attribute(primary_key)
59
65
  ids = Array(ids).compact_blank
60
- ids.map! { |i| pk_type.cast(i) }
66
+ ids.map! { |id| pk_type.cast(id) }
61
67
 
62
- records = klass.where(primary_key => ids).index_by do |r|
63
- r.public_send(primary_key)
68
+ records = if klass.composite_primary_key?
69
+ klass.where(primary_key => ids).index_by do |record|
70
+ primary_key.map { |primary_key| record._read_attribute(primary_key) }
71
+ end
72
+ else
73
+ klass.where(primary_key => ids).index_by do |record|
74
+ record._read_attribute(primary_key)
75
+ end
64
76
  end.values_at(*ids).compact
65
77
 
66
78
  if records.size != ids.size
67
- found_ids = records.map { |record| record.public_send(primary_key) }
79
+ found_ids = records.map { |record| record._read_attribute(primary_key) }
68
80
  not_found_ids = ids - found_ids
69
81
  klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key, not_found_ids)
70
82
  else
@@ -75,7 +87,7 @@ module ActiveRecord
75
87
  def reset
76
88
  super
77
89
  @target = []
78
- @replaced_or_added_targets = Set.new
90
+ @replaced_or_added_targets = Set.new.compare_by_identity
79
91
  @association_ids = nil
80
92
  end
81
93
 
@@ -115,28 +127,13 @@ module ActiveRecord
115
127
  def concat(*records)
116
128
  records = records.flatten
117
129
  if owner.new_record?
118
- load_target
130
+ skip_strict_loading { load_target }
119
131
  concat_records(records)
120
132
  else
121
133
  transaction { concat_records(records) }
122
134
  end
123
135
  end
124
136
 
125
- # Starts a transaction in the association class's database connection.
126
- #
127
- # class Author < ActiveRecord::Base
128
- # has_many :books
129
- # end
130
- #
131
- # Author.first.books.transaction do
132
- # # same effect as calling Book.transaction
133
- # end
134
- def transaction(*args)
135
- reflection.klass.transaction(*args) do
136
- yield
137
- end
138
- end
139
-
140
137
  # Removes all records from the association without calling callbacks
141
138
  # on the associated records. It honors the +:dependent+ option. However
142
139
  # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
@@ -191,7 +188,7 @@ module ActiveRecord
191
188
  end
192
189
 
193
190
  # Deletes the +records+ and removes them from this association calling
194
- # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
191
+ # +before_remove+, +after_remove+, +before_destroy+ and +after_destroy+ callbacks.
195
192
  #
196
193
  # Note that this method removes records from the database ignoring the
197
194
  # +:dependent+ option.
@@ -233,7 +230,7 @@ module ActiveRecord
233
230
  # loaded and you are going to fetch the records anyway it is better to
234
231
  # check <tt>collection.length.zero?</tt>.
235
232
  def empty?
236
- if loaded? || @association_ids || reflection.has_cached_counter?
233
+ if loaded? || @association_ids || reflection.has_active_cached_counter?
237
234
  size.zero?
238
235
  else
239
236
  target.empty? && !scope.exists?
@@ -244,7 +241,7 @@ module ActiveRecord
244
241
  # and delete/add only records that have changed.
245
242
  def replace(other_array)
246
243
  other_array.each { |val| raise_on_type_mismatch!(val) }
247
- original_target = load_target.dup
244
+ original_target = skip_strict_loading { load_target }.dup
248
245
 
249
246
  if owner.new_record?
250
247
  replace_records(other_array, original_target)
@@ -284,9 +281,11 @@ module ActiveRecord
284
281
  end
285
282
 
286
283
  def target=(record)
287
- return super unless ActiveRecord::Base.has_many_inversing
284
+ return super unless reflection.klass.has_many_inversing
288
285
 
289
286
  case record
287
+ when nil
288
+ # It's not possible to remove the record from the inverse association.
290
289
  when Array
291
290
  super
292
291
  else
@@ -306,13 +305,21 @@ module ActiveRecord
306
305
 
307
306
  def find_from_target?
308
307
  loaded? ||
309
- owner.strict_loading? ||
308
+ (owner.strict_loading? && owner.strict_loading_all?) ||
310
309
  reflection.strict_loading? ||
311
310
  owner.new_record? ||
312
311
  target.any? { |record| record.new_record? || record.changed? }
313
312
  end
314
313
 
314
+ def collection?
315
+ true
316
+ end
317
+
315
318
  private
319
+ def transaction(&block)
320
+ reflection.klass.transaction(&block)
321
+ end
322
+
316
323
  # We have some records loaded from the database (persisted) and some that are
317
324
  # in-memory (memory). The same record may be represented in the persisted array
318
325
  # and in the memory array.
@@ -325,13 +332,12 @@ module ActiveRecord
325
332
  # * Otherwise, attributes should have the value found in the database
326
333
  def merge_target_lists(persisted, memory)
327
334
  return persisted if memory.empty?
328
- return memory if persisted.empty?
329
335
 
330
336
  persisted.map! do |record|
331
337
  if mem_record = memory.delete(record)
332
338
 
333
- ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save).each do |name|
334
- mem_record[name] = record[name]
339
+ ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save - mem_record.class._attr_readonly).each do |name|
340
+ mem_record._write_attribute(name, record[name])
335
341
  end
336
342
 
337
343
  mem_record
@@ -345,7 +351,7 @@ module ActiveRecord
345
351
 
346
352
  def _create_record(attributes, raise = false, &block)
347
353
  unless owner.persisted?
348
- raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
354
+ raise ActiveRecord::RecordNotSaved.new("You cannot call create unless the parent is saved", owner)
349
355
  end
350
356
 
351
357
  if attributes.is_a?(Array)
@@ -489,7 +495,11 @@ module ActiveRecord
489
495
 
490
496
  def callbacks_for(callback_name)
491
497
  full_callback_name = "#{callback_name}_for_#{reflection.name}"
492
- owner.class.send(full_callback_name)
498
+ if owner.class.respond_to?(full_callback_name)
499
+ owner.class.send(full_callback_name)
500
+ else
501
+ []
502
+ end
493
503
  end
494
504
 
495
505
  def include_in_memory?(record)