activerecord 6.0.0 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +996 -594
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +34 -34
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +22 -20
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +41 -30
  9. data/lib/active_record/associations/association.rb +106 -41
  10. data/lib/active_record/associations/association_scope.rb +30 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +69 -14
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
  13. data/lib/active_record/associations/builder/association.rb +39 -6
  14. data/lib/active_record/associations/builder/belongs_to.rb +47 -17
  15. data/lib/active_record/associations/builder/collection_association.rb +14 -6
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
  17. data/lib/active_record/associations/builder/has_many.rb +7 -3
  18. data/lib/active_record/associations/builder/has_one.rb +13 -16
  19. data/lib/active_record/associations/builder/singular_association.rb +7 -3
  20. data/lib/active_record/associations/collection_association.rb +90 -53
  21. data/lib/active_record/associations/collection_proxy.rb +54 -19
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +21 -1
  25. data/lib/active_record/associations/has_many_association.rb +41 -10
  26. data/lib/active_record/associations/has_many_through_association.rb +29 -12
  27. data/lib/active_record/associations/has_one_association.rb +33 -9
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
  30. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  31. data/lib/active_record/associations/join_dependency.rb +97 -54
  32. data/lib/active_record/associations/nested_error.rb +47 -0
  33. data/lib/active_record/associations/preloader/association.rb +237 -54
  34. data/lib/active_record/associations/preloader/batch.rb +48 -0
  35. data/lib/active_record/associations/preloader/branch.rb +153 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +51 -17
  37. data/lib/active_record/associations/preloader.rb +55 -121
  38. data/lib/active_record/associations/singular_association.rb +16 -4
  39. data/lib/active_record/associations/through_association.rb +26 -15
  40. data/lib/active_record/associations.rb +454 -440
  41. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  42. data/lib/active_record/attribute_assignment.rb +11 -14
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
  44. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  45. data/lib/active_record/attribute_methods/dirty.rb +75 -34
  46. data/lib/active_record/attribute_methods/primary_key.rb +53 -31
  47. data/lib/active_record/attribute_methods/query.rb +31 -22
  48. data/lib/active_record/attribute_methods/read.rb +16 -17
  49. data/lib/active_record/attribute_methods/serialization.rb +177 -35
  50. data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
  51. data/lib/active_record/attribute_methods/write.rb +16 -28
  52. data/lib/active_record/attribute_methods.rb +227 -100
  53. data/lib/active_record/attributes.rb +94 -56
  54. data/lib/active_record/autosave_association.rb +119 -73
  55. data/lib/active_record/base.rb +31 -21
  56. data/lib/active_record/callbacks.rb +168 -55
  57. data/lib/active_record/coders/column_serializer.rb +61 -0
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +70 -25
  60. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
  76. data/lib/active_record/connection_adapters/column.rb +28 -1
  77. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  78. data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
  79. data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
  80. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  81. data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
  82. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
  83. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
  84. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
  85. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  87. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  88. data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
  89. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  90. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  91. data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
  92. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
  95. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  101. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
  106. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  110. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
  111. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
  121. data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  123. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  124. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
  131. data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
  132. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  133. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  134. data/lib/active_record/connection_adapters.rb +176 -0
  135. data/lib/active_record/connection_handling.rb +243 -115
  136. data/lib/active_record/core.rb +481 -199
  137. data/lib/active_record/counter_cache.rb +69 -32
  138. data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
  139. data/lib/active_record/database_configurations/database_config.rb +77 -10
  140. data/lib/active_record/database_configurations/hash_config.rb +148 -26
  141. data/lib/active_record/database_configurations/url_config.rb +44 -45
  142. data/lib/active_record/database_configurations.rb +190 -114
  143. data/lib/active_record/delegated_type.rb +279 -0
  144. data/lib/active_record/deprecator.rb +7 -0
  145. data/lib/active_record/destroy_association_async_job.rb +38 -0
  146. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  147. data/lib/active_record/dynamic_matchers.rb +5 -6
  148. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  149. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  150. data/lib/active_record/encryption/cipher.rb +53 -0
  151. data/lib/active_record/encryption/config.rb +68 -0
  152. data/lib/active_record/encryption/configurable.rb +60 -0
  153. data/lib/active_record/encryption/context.rb +42 -0
  154. data/lib/active_record/encryption/contexts.rb +76 -0
  155. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  156. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  157. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  158. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  159. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  160. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  161. data/lib/active_record/encryption/encryptor.rb +171 -0
  162. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  163. data/lib/active_record/encryption/errors.rb +15 -0
  164. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  165. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  166. data/lib/active_record/encryption/key.rb +28 -0
  167. data/lib/active_record/encryption/key_generator.rb +53 -0
  168. data/lib/active_record/encryption/key_provider.rb +46 -0
  169. data/lib/active_record/encryption/message.rb +33 -0
  170. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  171. data/lib/active_record/encryption/message_serializer.rb +96 -0
  172. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  173. data/lib/active_record/encryption/properties.rb +76 -0
  174. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  175. data/lib/active_record/encryption/scheme.rb +100 -0
  176. data/lib/active_record/encryption.rb +58 -0
  177. data/lib/active_record/enum.rb +224 -73
  178. data/lib/active_record/errors.rb +254 -36
  179. data/lib/active_record/explain.rb +30 -17
  180. data/lib/active_record/explain_registry.rb +11 -6
  181. data/lib/active_record/explain_subscriber.rb +2 -2
  182. data/lib/active_record/fixture_set/file.rb +22 -15
  183. data/lib/active_record/fixture_set/model_metadata.rb +15 -6
  184. data/lib/active_record/fixture_set/render_context.rb +3 -1
  185. data/lib/active_record/fixture_set/table_row.rb +88 -16
  186. data/lib/active_record/fixture_set/table_rows.rb +4 -5
  187. data/lib/active_record/fixtures.rb +229 -116
  188. data/lib/active_record/future_result.rb +178 -0
  189. data/lib/active_record/gem_version.rb +4 -4
  190. data/lib/active_record/inheritance.rb +121 -48
  191. data/lib/active_record/insert_all.rb +178 -29
  192. data/lib/active_record/integration.rb +16 -14
  193. data/lib/active_record/internal_metadata.rb +132 -21
  194. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  195. data/lib/active_record/locking/optimistic.rb +64 -33
  196. data/lib/active_record/locking/pessimistic.rb +21 -8
  197. data/lib/active_record/log_subscriber.rb +61 -30
  198. data/lib/active_record/marshalling.rb +59 -0
  199. data/lib/active_record/message_pack.rb +124 -0
  200. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  201. data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
  202. data/lib/active_record/middleware/database_selector.rb +25 -13
  203. data/lib/active_record/middleware/shard_selector.rb +62 -0
  204. data/lib/active_record/migration/command_recorder.rb +160 -55
  205. data/lib/active_record/migration/compatibility.rb +286 -43
  206. data/lib/active_record/migration/default_strategy.rb +22 -0
  207. data/lib/active_record/migration/execution_strategy.rb +19 -0
  208. data/lib/active_record/migration/join_table.rb +1 -2
  209. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  210. data/lib/active_record/migration.rb +421 -193
  211. data/lib/active_record/model_schema.rb +217 -125
  212. data/lib/active_record/nested_attributes.rb +62 -27
  213. data/lib/active_record/no_touching.rb +4 -4
  214. data/lib/active_record/normalization.rb +163 -0
  215. data/lib/active_record/persistence.rb +322 -319
  216. data/lib/active_record/promise.rb +84 -0
  217. data/lib/active_record/query_cache.rb +18 -15
  218. data/lib/active_record/query_logs.rb +193 -0
  219. data/lib/active_record/query_logs_formatter.rb +41 -0
  220. data/lib/active_record/querying.rb +54 -14
  221. data/lib/active_record/railtie.rb +250 -72
  222. data/lib/active_record/railties/console_sandbox.rb +2 -4
  223. data/lib/active_record/railties/controller_runtime.rb +25 -11
  224. data/lib/active_record/railties/databases.rake +312 -197
  225. data/lib/active_record/railties/job_runtime.rb +23 -0
  226. data/lib/active_record/readonly_attributes.rb +45 -3
  227. data/lib/active_record/reflection.rb +389 -146
  228. data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
  229. data/lib/active_record/relation/batches.rb +214 -73
  230. data/lib/active_record/relation/calculations.rb +379 -124
  231. data/lib/active_record/relation/delegation.rb +36 -23
  232. data/lib/active_record/relation/finder_methods.rb +159 -49
  233. data/lib/active_record/relation/from_clause.rb +5 -1
  234. data/lib/active_record/relation/merger.rb +41 -33
  235. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
  236. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
  237. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
  238. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  239. data/lib/active_record/relation/predicate_builder.rb +79 -53
  240. data/lib/active_record/relation/query_attribute.rb +30 -12
  241. data/lib/active_record/relation/query_methods.rb +1156 -279
  242. data/lib/active_record/relation/record_fetch_warning.rb +12 -11
  243. data/lib/active_record/relation/spawn_methods.rb +10 -9
  244. data/lib/active_record/relation/where_clause.rb +100 -66
  245. data/lib/active_record/relation.rb +829 -194
  246. data/lib/active_record/result.rb +76 -56
  247. data/lib/active_record/runtime_registry.rb +71 -13
  248. data/lib/active_record/sanitization.rb +86 -47
  249. data/lib/active_record/schema.rb +39 -23
  250. data/lib/active_record/schema_dumper.rb +140 -33
  251. data/lib/active_record/schema_migration.rb +74 -29
  252. data/lib/active_record/scoping/default.rb +73 -19
  253. data/lib/active_record/scoping/named.rb +10 -28
  254. data/lib/active_record/scoping.rb +65 -35
  255. data/lib/active_record/secure_password.rb +60 -0
  256. data/lib/active_record/secure_token.rb +34 -8
  257. data/lib/active_record/serialization.rb +11 -4
  258. data/lib/active_record/signed_id.rb +138 -0
  259. data/lib/active_record/statement_cache.rb +26 -10
  260. data/lib/active_record/store.rb +19 -14
  261. data/lib/active_record/suppressor.rb +15 -17
  262. data/lib/active_record/table_metadata.rb +46 -36
  263. data/lib/active_record/tasks/database_tasks.rb +371 -205
  264. data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
  265. data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
  266. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
  267. data/lib/active_record/test_databases.rb +5 -4
  268. data/lib/active_record/test_fixtures.rb +189 -104
  269. data/lib/active_record/testing/query_assertions.rb +121 -0
  270. data/lib/active_record/timestamp.rb +35 -25
  271. data/lib/active_record/token_for.rb +123 -0
  272. data/lib/active_record/touch_later.rb +31 -27
  273. data/lib/active_record/transaction.rb +132 -0
  274. data/lib/active_record/transactions.rb +131 -99
  275. data/lib/active_record/translation.rb +3 -5
  276. data/lib/active_record/type/adapter_specific_registry.rb +33 -18
  277. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  278. data/lib/active_record/type/internal/timezone.rb +7 -2
  279. data/lib/active_record/type/serialized.rb +11 -6
  280. data/lib/active_record/type/time.rb +14 -0
  281. data/lib/active_record/type/type_map.rb +17 -21
  282. data/lib/active_record/type/unsigned_integer.rb +0 -1
  283. data/lib/active_record/type.rb +7 -2
  284. data/lib/active_record/type_caster/connection.rb +4 -5
  285. data/lib/active_record/type_caster/map.rb +8 -5
  286. data/lib/active_record/validations/absence.rb +1 -1
  287. data/lib/active_record/validations/associated.rb +13 -8
  288. data/lib/active_record/validations/numericality.rb +36 -0
  289. data/lib/active_record/validations/presence.rb +5 -28
  290. data/lib/active_record/validations/uniqueness.rb +88 -18
  291. data/lib/active_record/validations.rb +15 -8
  292. data/lib/active_record/version.rb +1 -1
  293. data/lib/active_record.rb +446 -40
  294. data/lib/arel/alias_predication.rb +1 -1
  295. data/lib/arel/attributes/attribute.rb +4 -8
  296. data/lib/arel/collectors/bind.rb +8 -1
  297. data/lib/arel/collectors/composite.rb +15 -0
  298. data/lib/arel/collectors/sql_string.rb +7 -0
  299. data/lib/arel/collectors/substitute_binds.rb +7 -0
  300. data/lib/arel/crud.rb +30 -22
  301. data/lib/arel/delete_manager.rb +23 -4
  302. data/lib/arel/errors.rb +10 -0
  303. data/lib/arel/factory_methods.rb +4 -0
  304. data/lib/arel/filter_predications.rb +9 -0
  305. data/lib/arel/insert_manager.rb +2 -3
  306. data/lib/arel/nodes/binary.rb +82 -9
  307. data/lib/arel/nodes/bind_param.rb +8 -0
  308. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  309. data/lib/arel/nodes/casted.rb +22 -10
  310. data/lib/arel/nodes/cte.rb +36 -0
  311. data/lib/arel/nodes/delete_statement.rb +14 -13
  312. data/lib/arel/nodes/equality.rb +6 -9
  313. data/lib/arel/nodes/filter.rb +10 -0
  314. data/lib/arel/nodes/fragments.rb +35 -0
  315. data/lib/arel/nodes/function.rb +1 -0
  316. data/lib/arel/nodes/grouping.rb +3 -0
  317. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  318. data/lib/arel/nodes/in.rb +8 -1
  319. data/lib/arel/nodes/infix_operation.rb +13 -1
  320. data/lib/arel/nodes/insert_statement.rb +2 -2
  321. data/lib/arel/nodes/join_source.rb +1 -1
  322. data/lib/arel/nodes/leading_join.rb +8 -0
  323. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  324. data/lib/arel/nodes/node.rb +122 -11
  325. data/lib/arel/nodes/ordering.rb +27 -0
  326. data/lib/arel/nodes/select_core.rb +2 -2
  327. data/lib/arel/nodes/select_statement.rb +2 -2
  328. data/lib/arel/nodes/sql_literal.rb +16 -0
  329. data/lib/arel/nodes/table_alias.rb +11 -3
  330. data/lib/arel/nodes/unary.rb +0 -1
  331. data/lib/arel/nodes/update_statement.rb +11 -4
  332. data/lib/arel/nodes.rb +10 -3
  333. data/lib/arel/predications.rb +31 -28
  334. data/lib/arel/select_manager.rb +18 -9
  335. data/lib/arel/table.rb +21 -10
  336. data/lib/arel/tree_manager.rb +8 -15
  337. data/lib/arel/update_manager.rb +25 -5
  338. data/lib/arel/visitors/dot.rb +94 -90
  339. data/lib/arel/visitors/mysql.rb +34 -6
  340. data/lib/arel/visitors/postgresql.rb +5 -16
  341. data/lib/arel/visitors/sqlite.rb +25 -1
  342. data/lib/arel/visitors/to_sql.rb +227 -81
  343. data/lib/arel/visitors/visitor.rb +2 -3
  344. data/lib/arel/visitors.rb +0 -7
  345. data/lib/arel.rb +37 -15
  346. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  347. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  348. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  349. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  350. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
  351. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  352. data/lib/rails/generators/active_record/migration.rb +9 -3
  353. data/lib/rails/generators/active_record/model/USAGE +113 -0
  354. data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
  355. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  356. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  357. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  358. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  359. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  360. metadata +117 -30
  361. data/lib/active_record/attribute_decorators.rb +0 -90
  362. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  363. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  364. data/lib/active_record/define_callbacks.rb +0 -22
  365. data/lib/active_record/null_relation.rb +0 -68
  366. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  367. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  368. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  369. data/lib/arel/attributes.rb +0 -22
  370. data/lib/arel/visitors/depth_first.rb +0 -204
  371. data/lib/arel/visitors/ibm_db.rb +0 -34
  372. data/lib/arel/visitors/informix.rb +0 -62
  373. data/lib/arel/visitors/mssql.rb +0 -157
  374. data/lib/arel/visitors/oracle.rb +0 -159
  375. data/lib/arel/visitors/oracle12.rb +0 -66
  376. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -1,17 +1,20 @@
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
8
8
 
9
9
  def self.valid_options(options)
10
- super + [:polymorphic, :touch, :counter_cache, :optional, :default]
10
+ valid = super + [:polymorphic, :counter_cache, :optional, :default]
11
+ valid += [:foreign_type] if options[:polymorphic]
12
+ valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
13
+ valid
11
14
  end
12
15
 
13
16
  def self.valid_dependent_options
14
- [:destroy, :delete]
17
+ [:destroy, :delete, :destroy_async]
15
18
  end
16
19
 
17
20
  def self.define_callbacks(model, reflection)
@@ -27,17 +30,18 @@ module ActiveRecord::Associations::Builder # :nodoc:
27
30
  model.after_update lambda { |record|
28
31
  association = association(reflection.name)
29
32
 
30
- if association.target_changed?
33
+ if association.saved_change_to_target?
31
34
  association.increment_counters
32
35
  association.decrement_counters_before_last_save
33
36
  end
34
37
  }
35
38
 
36
39
  klass = reflection.class_name.safe_constantize
37
- 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]
38
42
  end
39
43
 
40
- 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:
41
45
  old_foreign_id = changes[foreign_key] && changes[foreign_key].first
42
46
 
43
47
  if old_foreign_id
@@ -46,7 +50,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
46
50
  if reflection.polymorphic?
47
51
  foreign_type = reflection.foreign_type
48
52
  klass = changes[foreign_type] && changes[foreign_type].first || o.public_send(foreign_type)
49
- klass = klass.constantize
53
+ klass = o.class.polymorphic_class_for(klass)
50
54
  else
51
55
  klass = association.klass
52
56
  end
@@ -55,19 +59,19 @@ module ActiveRecord::Associations::Builder # :nodoc:
55
59
 
56
60
  if old_record
57
61
  if touch != true
58
- old_record.send(touch_method, touch)
62
+ old_record.touch_later(touch)
59
63
  else
60
- old_record.send(touch_method)
64
+ old_record.touch_later
61
65
  end
62
66
  end
63
67
  end
64
68
 
65
- record = o.send name
69
+ record = o.public_send name
66
70
  if record && record.persisted?
67
71
  if touch != true
68
- record.send(touch_method, touch)
72
+ record.touch_later(touch)
69
73
  else
70
- record.send(touch_method)
74
+ record.touch_later
71
75
  end
72
76
  end
73
77
  end
@@ -78,13 +82,13 @@ module ActiveRecord::Associations::Builder # :nodoc:
78
82
  touch = reflection.options[:touch]
79
83
 
80
84
  callback = lambda { |changes_method| lambda { |record|
81
- 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)
82
86
  }}
83
87
 
84
88
  if reflection.counter_cache_column
85
89
  touch_callback = callback.(:saved_changes)
86
90
  update_callback = lambda { |record|
87
- 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?
88
92
  }
89
93
  model.after_update update_callback, if: :saved_changes?
90
94
  else
@@ -120,11 +124,37 @@ module ActiveRecord::Associations::Builder # :nodoc:
120
124
  super
121
125
 
122
126
  if required
123
- 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
124
141
  end
125
142
  end
126
143
 
127
- private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks, :define_validations,
128
- :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
129
159
  end
130
160
  end
@@ -3,12 +3,11 @@
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)
10
- super + [:table_name, :before_add,
11
- :after_add, :before_remove, :after_remove, :extend]
10
+ super + [:before_add, :after_add, :before_remove, :after_remove, :extend]
12
11
  end
13
12
 
14
13
  def self.define_callbacks(model, reflection)
@@ -31,9 +30,18 @@ module ActiveRecord::Associations::Builder # :nodoc:
31
30
  def self.define_callback(model, callback_name, name, options)
32
31
  full_callback_name = "#{callback_name}_for_#{name}"
33
32
 
34
- # TODO : why do i need method_defined? I think its because of the inheritance chain
35
- model.class_attribute full_callback_name unless model.method_defined?(full_callback_name)
36
- callbacks = Array(options[callback_name.to_sym]).map do |callback|
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
41
+ model.class_attribute(full_callback_name, instance_accessor: false, instance_predicate: false)
42
+ end
43
+
44
+ callbacks = callback_values.map do |callback|
37
45
  case callback
38
46
  when Symbol
39
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,15 +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
-
50
- def self.suppress_composite_primary_key(pk)
51
- pk unless pk.is_a?(Array)
52
- end
53
48
  }
54
49
 
55
50
  join_model.name = "HABTM_#{association_name.to_s.camelize}"
@@ -73,11 +68,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
73
68
  end
74
69
 
75
70
  private
76
-
77
71
  def middle_options(join_model)
78
72
  middle_options = {}
79
73
  middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
80
- middle_options[:source] = join_model.left_reflection.name
81
74
  if options.key? :foreign_key
82
75
  middle_options[:foreign_key] = options[:foreign_key]
83
76
  end
@@ -1,17 +1,21 @@
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
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type, :index_errors]
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
+ valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
14
+ valid
11
15
  end
12
16
 
13
17
  def self.valid_dependent_options
14
- [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
18
+ [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception, :destroy_async]
15
19
  end
16
20
 
17
21
  private_class_method :macro, :valid_options, :valid_dependent_options
@@ -1,19 +1,21 @@
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 + [:as, :touch]
11
- valid += [:through, :source, :source_type] if options[:through]
10
+ valid = super + [:as, :through]
11
+ valid += [:foreign_type] if options[:as]
12
+ valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
13
+ valid += [:source, :source_type, :disable_joins] if options[:through]
12
14
  valid
13
15
  end
14
16
 
15
17
  def self.valid_dependent_options
16
- [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
18
+ [:destroy, :destroy_async, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
17
19
  end
18
20
 
19
21
  def self.define_callbacks(model, reflection)
@@ -32,15 +34,12 @@ module ActiveRecord::Associations::Builder # :nodoc:
32
34
  end
33
35
  end
34
36
 
35
- def self.touch_record(o, name, touch)
36
- record = o.send name
37
+ def self.touch_record(record, name, touch)
38
+ instance = record.send(name)
37
39
 
38
- return unless record && record.persisted?
39
-
40
- if touch != true
41
- record.touch(touch)
42
- else
43
- record.touch
40
+ if instance&.persisted?
41
+ touch != true ?
42
+ instance.touch(touch) : instance.touch
44
43
  end
45
44
  end
46
45
 
@@ -48,11 +47,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
48
47
  name = reflection.name
49
48
  touch = reflection.options[:touch]
50
49
 
51
- callback = lambda { |record|
52
- HasOne.touch_record(record, name, touch)
53
- }
54
-
50
+ callback = -> (record) { HasOne.touch_record(record, name, touch) }
55
51
  model.after_create callback, if: :saved_changes?
52
+ model.after_create_commit { association(name).reset_negative_cache }
56
53
  model.after_update callback, if: :saved_changes?
57
54
  model.after_destroy callback
58
55
  model.after_touch callback
@@ -3,9 +3,9 @@
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
- super + [:foreign_type, :dependent, :primary_key, :inverse_of, :required]
8
+ super + [:required, :touch]
9
9
  end
10
10
 
11
11
  def self.define_accessors(model, reflection)
@@ -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
 
@@ -56,15 +62,21 @@ module ActiveRecord
56
62
  def ids_writer(ids)
57
63
  primary_key = reflection.association_primary_key
58
64
  pk_type = klass.type_for_attribute(primary_key)
59
- ids = Array(ids).reject(&:blank?)
60
- ids.map! { |i| pk_type.cast(i) }
65
+ ids = Array(ids).compact_blank
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,6 +87,7 @@ module ActiveRecord
75
87
  def reset
76
88
  super
77
89
  @target = []
90
+ @replaced_or_added_targets = Set.new.compare_by_identity
78
91
  @association_ids = nil
79
92
  end
80
93
 
@@ -101,11 +114,11 @@ module ActiveRecord
101
114
  end
102
115
  end
103
116
 
104
- def build(attributes = {}, &block)
117
+ def build(attributes = nil, &block)
105
118
  if attributes.is_a?(Array)
106
119
  attributes.collect { |attr| build(attr, &block) }
107
120
  else
108
- add_to_target(build_record(attributes, &block))
121
+ add_to_target(build_record(attributes, &block), replace: true)
109
122
  end
110
123
  end
111
124
 
@@ -114,28 +127,13 @@ module ActiveRecord
114
127
  def concat(*records)
115
128
  records = records.flatten
116
129
  if owner.new_record?
117
- load_target
130
+ skip_strict_loading { load_target }
118
131
  concat_records(records)
119
132
  else
120
133
  transaction { concat_records(records) }
121
134
  end
122
135
  end
123
136
 
124
- # Starts a transaction in the association class's database connection.
125
- #
126
- # class Author < ActiveRecord::Base
127
- # has_many :books
128
- # end
129
- #
130
- # Author.first.books.transaction do
131
- # # same effect as calling Book.transaction
132
- # end
133
- def transaction(*args)
134
- reflection.klass.transaction(*args) do
135
- yield
136
- end
137
- end
138
-
139
137
  # Removes all records from the association without calling callbacks
140
138
  # on the associated records. It honors the +:dependent+ option. However
141
139
  # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
@@ -190,7 +188,7 @@ module ActiveRecord
190
188
  end
191
189
 
192
190
  # Deletes the +records+ and removes them from this association calling
193
- # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
191
+ # +before_remove+, +after_remove+, +before_destroy+ and +after_destroy+ callbacks.
194
192
  #
195
193
  # Note that this method removes records from the database ignoring the
196
194
  # +:dependent+ option.
@@ -228,11 +226,11 @@ module ActiveRecord
228
226
  # If the collection has been loaded
229
227
  # it is equivalent to <tt>collection.size.zero?</tt>. If the
230
228
  # collection has not been loaded, it is equivalent to
231
- # <tt>collection.exists?</tt>. If the collection has not already been
229
+ # <tt>!collection.exists?</tt>. If the collection has not already been
232
230
  # loaded and you are going to fetch the records anyway it is better to
233
231
  # check <tt>collection.length.zero?</tt>.
234
232
  def empty?
235
- if loaded? || @association_ids || reflection.has_cached_counter?
233
+ if loaded? || @association_ids || reflection.has_active_cached_counter?
236
234
  size.zero?
237
235
  else
238
236
  target.empty? && !scope.exists?
@@ -243,7 +241,7 @@ module ActiveRecord
243
241
  # and delete/add only records that have changed.
244
242
  def replace(other_array)
245
243
  other_array.each { |val| raise_on_type_mismatch!(val) }
246
- original_target = load_target.dup
244
+ original_target = skip_strict_loading { load_target }.dup
247
245
 
248
246
  if owner.new_record?
249
247
  replace_records(other_array, original_target)
@@ -258,14 +256,16 @@ module ActiveRecord
258
256
  end
259
257
 
260
258
  def include?(record)
261
- if record.is_a?(reflection.klass)
262
- if record.new_record?
263
- include_in_memory?(record)
264
- else
265
- loaded? ? target.include?(record) : scope.exists?(record.id)
266
- end
259
+ klass = reflection.klass
260
+ return false unless record.is_a?(klass)
261
+
262
+ if loaded?
263
+ target.include?(record)
264
+ elsif record.new_record?
265
+ include_in_memory?(record)
267
266
  else
268
- false
267
+ record_id = klass.composite_primary_key? ? klass.primary_key.zip(record.id).to_h : record.id
268
+ scope.exists?(record_id)
269
269
  end
270
270
  end
271
271
 
@@ -278,11 +278,21 @@ module ActiveRecord
278
278
  target
279
279
  end
280
280
 
281
- def add_to_target(record, skip_callbacks = false, &block)
282
- if association_scope.distinct_value
283
- index = @target.index(record)
281
+ def add_to_target(record, skip_callbacks: false, replace: false, &block)
282
+ replace_on_target(record, skip_callbacks, replace: replace || association_scope.distinct_value, &block)
283
+ end
284
+
285
+ def target=(record)
286
+ return super unless reflection.klass.has_many_inversing
287
+
288
+ case record
289
+ when nil
290
+ # It's not possible to remove the record from the inverse association.
291
+ when Array
292
+ super
293
+ else
294
+ replace_on_target(record, true, replace: true, inversing: true)
284
295
  end
285
- replace_on_target(record, index, skip_callbacks, &block)
286
296
  end
287
297
 
288
298
  def scope
@@ -297,11 +307,21 @@ module ActiveRecord
297
307
 
298
308
  def find_from_target?
299
309
  loaded? ||
310
+ (owner.strict_loading? && owner.strict_loading_all?) ||
311
+ reflection.strict_loading? ||
300
312
  owner.new_record? ||
301
313
  target.any? { |record| record.new_record? || record.changed? }
302
314
  end
303
315
 
316
+ def collection?
317
+ true
318
+ end
319
+
304
320
  private
321
+ def transaction(&block)
322
+ reflection.klass.transaction(&block)
323
+ end
324
+
305
325
  # We have some records loaded from the database (persisted) and some that are
306
326
  # in-memory (memory). The same record may be represented in the persisted array
307
327
  # and in the memory array.
@@ -314,13 +334,12 @@ module ActiveRecord
314
334
  # * Otherwise, attributes should have the value found in the database
315
335
  def merge_target_lists(persisted, memory)
316
336
  return persisted if memory.empty?
317
- return memory if persisted.empty?
318
337
 
319
338
  persisted.map! do |record|
320
339
  if mem_record = memory.delete(record)
321
340
 
322
- ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save).each do |name|
323
- mem_record[name] = record[name]
341
+ ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save - mem_record.class._attr_readonly).each do |name|
342
+ mem_record._write_attribute(name, record[name])
324
343
  end
325
344
 
326
345
  mem_record
@@ -329,12 +348,12 @@ module ActiveRecord
329
348
  end
330
349
  end
331
350
 
332
- persisted + memory
351
+ persisted + memory.reject(&:persisted?)
333
352
  end
334
353
 
335
354
  def _create_record(attributes, raise = false, &block)
336
355
  unless owner.persisted?
337
- raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
356
+ raise ActiveRecord::RecordNotSaved.new("You cannot call create unless the parent is saved", owner)
338
357
  end
339
358
 
340
359
  if attributes.is_a?(Array)
@@ -378,7 +397,9 @@ module ActiveRecord
378
397
  end
379
398
 
380
399
  def remove_records(existing_records, records, method)
381
- records.each { |record| callback(:before_remove, record) }
400
+ catch(:abort) do
401
+ records.each { |record| callback(:before_remove, record) }
402
+ end || return
382
403
 
383
404
  delete_records(existing_records, method) if existing_records.any?
384
405
  @target -= records
@@ -410,7 +431,7 @@ module ActiveRecord
410
431
  common_records = intersection(new_target, original_target)
411
432
  common_records.each do |record|
412
433
  skip_callbacks = true
413
- replace_on_target(record, @target.index(record), skip_callbacks)
434
+ replace_on_target(record, skip_callbacks, replace: true)
414
435
  end
415
436
  end
416
437
 
@@ -433,8 +454,14 @@ module ActiveRecord
433
454
  records
434
455
  end
435
456
 
436
- def replace_on_target(record, index, skip_callbacks)
437
- callback(:before_add, record) unless skip_callbacks
457
+ def replace_on_target(record, skip_callbacks, replace:, inversing: false)
458
+ if replace && (!record.new_record? || @replaced_or_added_targets.include?(record))
459
+ index = @target.index(record)
460
+ end
461
+
462
+ catch(:abort) do
463
+ callback(:before_add, record)
464
+ end || return unless skip_callbacks
438
465
 
439
466
  set_inverse_instance(record)
440
467
 
@@ -442,6 +469,12 @@ module ActiveRecord
442
469
 
443
470
  yield(record) if block_given?
444
471
 
472
+ if !index && @replaced_or_added_targets.include?(record)
473
+ index = @target.index(record)
474
+ end
475
+
476
+ @replaced_or_added_targets << record if inversing || index || record.new_record?
477
+
445
478
  if index
446
479
  target[index] = record
447
480
  elsif @_was_loaded || !loaded?
@@ -464,7 +497,11 @@ module ActiveRecord
464
497
 
465
498
  def callbacks_for(callback_name)
466
499
  full_callback_name = "#{callback_name}_for_#{reflection.name}"
467
- owner.class.send(full_callback_name)
500
+ if owner.class.respond_to?(full_callback_name)
501
+ owner.class.send(full_callback_name)
502
+ else
503
+ []
504
+ end
468
505
  end
469
506
 
470
507
  def include_in_memory?(record)