activerecord 4.2.9 → 6.1.4.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (374) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +964 -1382
  3. data/MIT-LICENSE +4 -2
  4. data/README.rdoc +15 -14
  5. data/examples/performance.rb +33 -32
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +266 -251
  8. data/lib/active_record/association_relation.rb +40 -15
  9. data/lib/active_record/associations/alias_tracker.rb +40 -43
  10. data/lib/active_record/associations/association.rb +162 -69
  11. data/lib/active_record/associations/association_scope.rb +105 -130
  12. data/lib/active_record/associations/belongs_to_association.rb +83 -65
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
  14. data/lib/active_record/associations/builder/association.rb +57 -43
  15. data/lib/active_record/associations/builder/belongs_to.rb +74 -57
  16. data/lib/active_record/associations/builder/collection_association.rb +15 -37
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +49 -66
  18. data/lib/active_record/associations/builder/has_many.rb +13 -5
  19. data/lib/active_record/associations/builder/has_one.rb +44 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +16 -10
  21. data/lib/active_record/associations/collection_association.rb +148 -287
  22. data/lib/active_record/associations/collection_proxy.rb +252 -150
  23. data/lib/active_record/associations/foreign_association.rb +23 -1
  24. data/lib/active_record/associations/has_many_association.rb +56 -98
  25. data/lib/active_record/associations/has_many_through_association.rb +68 -89
  26. data/lib/active_record/associations/has_one_association.rb +73 -47
  27. data/lib/active_record/associations/has_one_through_association.rb +20 -11
  28. data/lib/active_record/associations/join_dependency/join_association.rb +54 -81
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
  31. data/lib/active_record/associations/join_dependency.rb +174 -169
  32. data/lib/active_record/associations/preloader/association.rb +108 -115
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -65
  34. data/lib/active_record/associations/preloader.rb +97 -94
  35. data/lib/active_record/associations/singular_association.rb +18 -39
  36. data/lib/active_record/associations/through_association.rb +39 -19
  37. data/lib/active_record/associations.rb +1845 -1598
  38. data/lib/active_record/attribute_assignment.rb +59 -185
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +18 -10
  40. data/lib/active_record/attribute_methods/dirty.rb +168 -148
  41. data/lib/active_record/attribute_methods/primary_key.rb +93 -83
  42. data/lib/active_record/attribute_methods/query.rb +8 -10
  43. data/lib/active_record/attribute_methods/read.rb +19 -79
  44. data/lib/active_record/attribute_methods/serialization.rb +49 -24
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +55 -36
  46. data/lib/active_record/attribute_methods/write.rb +24 -55
  47. data/lib/active_record/attribute_methods.rb +149 -154
  48. data/lib/active_record/attributes.rb +234 -78
  49. data/lib/active_record/autosave_association.rb +133 -60
  50. data/lib/active_record/base.rb +46 -46
  51. data/lib/active_record/callbacks.rb +234 -79
  52. data/lib/active_record/coders/json.rb +3 -1
  53. data/lib/active_record/coders/yaml_column.rb +34 -13
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +887 -323
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -41
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +292 -124
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -24
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +177 -60
  59. data/lib/active_record/connection_adapters/abstract/savepoints.rb +8 -6
  60. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +157 -93
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +473 -255
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +869 -286
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +257 -91
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +483 -230
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +557 -640
  67. data/lib/active_record/connection_adapters/column.rb +67 -40
  68. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  69. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +194 -0
  72. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +96 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +97 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +103 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +91 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
  78. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +80 -192
  80. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  81. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -160
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +49 -58
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +8 -6
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -19
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -20
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  98. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  101. data/lib/active_record/connection_adapters/postgresql/oid/{infinity.rb → oid.rb} +5 -3
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +32 -11
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +70 -34
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  109. data/lib/active_record/connection_adapters/postgresql/oid.rb +25 -25
  110. data/lib/active_record/connection_adapters/postgresql/quoting.rb +145 -48
  111. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  112. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
  114. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +49 -0
  115. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +496 -298
  116. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
  117. data/lib/active_record/connection_adapters/postgresql/utils.rb +11 -8
  118. data/lib/active_record/connection_adapters/postgresql_adapter.rb +588 -375
  119. data/lib/active_record/connection_adapters/schema_cache.rb +167 -29
  120. data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
  121. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  122. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  123. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +102 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +21 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +322 -373
  129. data/lib/active_record/connection_adapters/statement_pool.rb +33 -13
  130. data/lib/active_record/connection_adapters.rb +52 -0
  131. data/lib/active_record/connection_handling.rb +314 -41
  132. data/lib/active_record/core.rb +458 -241
  133. data/lib/active_record/counter_cache.rb +70 -49
  134. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  135. data/lib/active_record/database_configurations/database_config.rb +80 -0
  136. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  137. data/lib/active_record/database_configurations/url_config.rb +53 -0
  138. data/lib/active_record/database_configurations.rb +272 -0
  139. data/lib/active_record/delegated_type.rb +209 -0
  140. data/lib/active_record/destroy_association_async_job.rb +36 -0
  141. data/lib/active_record/dynamic_matchers.rb +87 -106
  142. data/lib/active_record/enum.rb +211 -92
  143. data/lib/active_record/errors.rb +224 -54
  144. data/lib/active_record/explain.rb +27 -11
  145. data/lib/active_record/explain_registry.rb +4 -2
  146. data/lib/active_record/explain_subscriber.rb +10 -5
  147. data/lib/active_record/fixture_set/file.rb +33 -14
  148. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  149. data/lib/active_record/fixture_set/render_context.rb +17 -0
  150. data/lib/active_record/fixture_set/table_row.rb +152 -0
  151. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  152. data/lib/active_record/fixtures.rb +275 -500
  153. data/lib/active_record/gem_version.rb +6 -4
  154. data/lib/active_record/inheritance.rb +175 -110
  155. data/lib/active_record/insert_all.rb +212 -0
  156. data/lib/active_record/integration.rb +121 -29
  157. data/lib/active_record/internal_metadata.rb +62 -0
  158. data/lib/active_record/legacy_yaml_adapter.rb +27 -5
  159. data/lib/active_record/locale/en.yml +3 -2
  160. data/lib/active_record/locking/optimistic.rb +98 -92
  161. data/lib/active_record/locking/pessimistic.rb +22 -6
  162. data/lib/active_record/log_subscriber.rb +93 -31
  163. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  164. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  165. data/lib/active_record/middleware/database_selector.rb +77 -0
  166. data/lib/active_record/migration/command_recorder.rb +185 -90
  167. data/lib/active_record/migration/compatibility.rb +295 -0
  168. data/lib/active_record/migration/join_table.rb +8 -7
  169. data/lib/active_record/migration.rb +673 -325
  170. data/lib/active_record/model_schema.rb +418 -113
  171. data/lib/active_record/nested_attributes.rb +263 -224
  172. data/lib/active_record/no_touching.rb +15 -2
  173. data/lib/active_record/null_relation.rb +24 -38
  174. data/lib/active_record/persistence.rb +572 -136
  175. data/lib/active_record/query_cache.rb +29 -23
  176. data/lib/active_record/querying.rb +50 -31
  177. data/lib/active_record/railtie.rb +170 -51
  178. data/lib/active_record/railties/console_sandbox.rb +3 -3
  179. data/lib/active_record/railties/controller_runtime.rb +34 -33
  180. data/lib/active_record/railties/databases.rake +523 -199
  181. data/lib/active_record/readonly_attributes.rb +9 -4
  182. data/lib/active_record/reflection.rb +454 -291
  183. data/lib/active_record/relation/batches/batch_enumerator.rb +85 -0
  184. data/lib/active_record/relation/batches.rb +217 -59
  185. data/lib/active_record/relation/calculations.rb +324 -249
  186. data/lib/active_record/relation/delegation.rb +76 -84
  187. data/lib/active_record/relation/finder_methods.rb +316 -242
  188. data/lib/active_record/relation/from_clause.rb +30 -0
  189. data/lib/active_record/relation/merger.rb +95 -103
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +26 -26
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
  192. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  193. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +57 -0
  194. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  195. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  196. data/lib/active_record/relation/predicate_builder.rb +136 -122
  197. data/lib/active_record/relation/query_attribute.rb +50 -0
  198. data/lib/active_record/relation/query_methods.rb +757 -413
  199. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  200. data/lib/active_record/relation/spawn_methods.rb +18 -20
  201. data/lib/active_record/relation/where_clause.rb +239 -0
  202. data/lib/active_record/relation.rb +554 -343
  203. data/lib/active_record/result.rb +91 -47
  204. data/lib/active_record/runtime_registry.rb +6 -4
  205. data/lib/active_record/sanitization.rb +134 -122
  206. data/lib/active_record/schema.rb +21 -24
  207. data/lib/active_record/schema_dumper.rb +141 -92
  208. data/lib/active_record/schema_migration.rb +24 -23
  209. data/lib/active_record/scoping/default.rb +96 -83
  210. data/lib/active_record/scoping/named.rb +78 -36
  211. data/lib/active_record/scoping.rb +45 -27
  212. data/lib/active_record/secure_token.rb +48 -0
  213. data/lib/active_record/serialization.rb +8 -6
  214. data/lib/active_record/signed_id.rb +116 -0
  215. data/lib/active_record/statement_cache.rb +89 -36
  216. data/lib/active_record/store.rb +128 -43
  217. data/lib/active_record/suppressor.rb +61 -0
  218. data/lib/active_record/table_metadata.rb +81 -0
  219. data/lib/active_record/tasks/database_tasks.rb +364 -130
  220. data/lib/active_record/tasks/mysql_database_tasks.rb +67 -113
  221. data/lib/active_record/tasks/postgresql_database_tasks.rb +86 -49
  222. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -19
  223. data/lib/active_record/test_databases.rb +24 -0
  224. data/lib/active_record/test_fixtures.rb +287 -0
  225. data/lib/active_record/timestamp.rb +86 -43
  226. data/lib/active_record/touch_later.rb +65 -0
  227. data/lib/active_record/transactions.rb +182 -163
  228. data/lib/active_record/translation.rb +3 -1
  229. data/lib/active_record/type/adapter_specific_registry.rb +126 -0
  230. data/lib/active_record/type/date.rb +4 -45
  231. data/lib/active_record/type/date_time.rb +4 -49
  232. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  233. data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
  234. data/lib/active_record/type/internal/timezone.rb +17 -0
  235. data/lib/active_record/type/json.rb +30 -0
  236. data/lib/active_record/type/serialized.rb +27 -15
  237. data/lib/active_record/type/text.rb +2 -2
  238. data/lib/active_record/type/time.rb +21 -16
  239. data/lib/active_record/type/type_map.rb +16 -19
  240. data/lib/active_record/type/unsigned_integer.rb +9 -8
  241. data/lib/active_record/type.rb +84 -23
  242. data/lib/active_record/type_caster/connection.rb +33 -0
  243. data/lib/active_record/type_caster/map.rb +23 -0
  244. data/lib/active_record/type_caster.rb +9 -0
  245. data/lib/active_record/validations/absence.rb +25 -0
  246. data/lib/active_record/validations/associated.rb +12 -4
  247. data/lib/active_record/validations/length.rb +26 -0
  248. data/lib/active_record/validations/numericality.rb +35 -0
  249. data/lib/active_record/validations/presence.rb +14 -13
  250. data/lib/active_record/validations/uniqueness.rb +63 -56
  251. data/lib/active_record/validations.rb +39 -35
  252. data/lib/active_record/version.rb +3 -1
  253. data/lib/active_record.rb +42 -29
  254. data/lib/arel/alias_predication.rb +9 -0
  255. data/lib/arel/attributes/attribute.rb +41 -0
  256. data/lib/arel/collectors/bind.rb +29 -0
  257. data/lib/arel/collectors/composite.rb +39 -0
  258. data/lib/arel/collectors/plain_string.rb +20 -0
  259. data/lib/arel/collectors/sql_string.rb +27 -0
  260. data/lib/arel/collectors/substitute_binds.rb +35 -0
  261. data/lib/arel/crud.rb +42 -0
  262. data/lib/arel/delete_manager.rb +18 -0
  263. data/lib/arel/errors.rb +9 -0
  264. data/lib/arel/expressions.rb +29 -0
  265. data/lib/arel/factory_methods.rb +49 -0
  266. data/lib/arel/insert_manager.rb +49 -0
  267. data/lib/arel/math.rb +45 -0
  268. data/lib/arel/nodes/and.rb +32 -0
  269. data/lib/arel/nodes/ascending.rb +23 -0
  270. data/lib/arel/nodes/binary.rb +126 -0
  271. data/lib/arel/nodes/bind_param.rb +44 -0
  272. data/lib/arel/nodes/case.rb +55 -0
  273. data/lib/arel/nodes/casted.rb +62 -0
  274. data/lib/arel/nodes/comment.rb +29 -0
  275. data/lib/arel/nodes/count.rb +12 -0
  276. data/lib/arel/nodes/delete_statement.rb +45 -0
  277. data/lib/arel/nodes/descending.rb +23 -0
  278. data/lib/arel/nodes/equality.rb +15 -0
  279. data/lib/arel/nodes/extract.rb +24 -0
  280. data/lib/arel/nodes/false.rb +16 -0
  281. data/lib/arel/nodes/full_outer_join.rb +8 -0
  282. data/lib/arel/nodes/function.rb +44 -0
  283. data/lib/arel/nodes/grouping.rb +11 -0
  284. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  285. data/lib/arel/nodes/in.rb +15 -0
  286. data/lib/arel/nodes/infix_operation.rb +92 -0
  287. data/lib/arel/nodes/inner_join.rb +8 -0
  288. data/lib/arel/nodes/insert_statement.rb +37 -0
  289. data/lib/arel/nodes/join_source.rb +20 -0
  290. data/lib/arel/nodes/matches.rb +18 -0
  291. data/lib/arel/nodes/named_function.rb +23 -0
  292. data/lib/arel/nodes/node.rb +51 -0
  293. data/lib/arel/nodes/node_expression.rb +13 -0
  294. data/lib/arel/nodes/ordering.rb +27 -0
  295. data/lib/arel/nodes/outer_join.rb +8 -0
  296. data/lib/arel/nodes/over.rb +15 -0
  297. data/lib/arel/nodes/regexp.rb +16 -0
  298. data/lib/arel/nodes/right_outer_join.rb +8 -0
  299. data/lib/arel/nodes/select_core.rb +67 -0
  300. data/lib/arel/nodes/select_statement.rb +41 -0
  301. data/lib/arel/nodes/sql_literal.rb +19 -0
  302. data/lib/arel/nodes/string_join.rb +11 -0
  303. data/lib/arel/nodes/table_alias.rb +31 -0
  304. data/lib/arel/nodes/terminal.rb +16 -0
  305. data/lib/arel/nodes/true.rb +16 -0
  306. data/lib/arel/nodes/unary.rb +44 -0
  307. data/lib/arel/nodes/unary_operation.rb +20 -0
  308. data/lib/arel/nodes/unqualified_column.rb +22 -0
  309. data/lib/arel/nodes/update_statement.rb +41 -0
  310. data/lib/arel/nodes/values_list.rb +9 -0
  311. data/lib/arel/nodes/window.rb +126 -0
  312. data/lib/arel/nodes/with.rb +11 -0
  313. data/lib/arel/nodes.rb +70 -0
  314. data/lib/arel/order_predications.rb +13 -0
  315. data/lib/arel/predications.rb +250 -0
  316. data/lib/arel/select_manager.rb +270 -0
  317. data/lib/arel/table.rb +118 -0
  318. data/lib/arel/tree_manager.rb +72 -0
  319. data/lib/arel/update_manager.rb +34 -0
  320. data/lib/arel/visitors/dot.rb +308 -0
  321. data/lib/arel/visitors/mysql.rb +93 -0
  322. data/lib/arel/visitors/postgresql.rb +120 -0
  323. data/lib/arel/visitors/sqlite.rb +38 -0
  324. data/lib/arel/visitors/to_sql.rb +899 -0
  325. data/lib/arel/visitors/visitor.rb +45 -0
  326. data/lib/arel/visitors.rb +13 -0
  327. data/lib/arel/window_predications.rb +9 -0
  328. data/lib/arel.rb +54 -0
  329. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  330. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  331. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -37
  332. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +26 -0
  333. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +13 -4
  334. data/lib/rails/generators/active_record/migration.rb +35 -1
  335. data/lib/rails/generators/active_record/model/model_generator.rb +55 -22
  336. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  337. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  338. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  339. data/lib/rails/generators/active_record.rb +7 -5
  340. metadata +172 -65
  341. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  342. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  343. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  344. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  345. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  346. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  347. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  348. data/lib/active_record/attribute.rb +0 -163
  349. data/lib/active_record/attribute_decorators.rb +0 -66
  350. data/lib/active_record/attribute_set/builder.rb +0 -106
  351. data/lib/active_record/attribute_set.rb +0 -81
  352. data/lib/active_record/connection_adapters/connection_specification.rb +0 -275
  353. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  354. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  355. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  356. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  357. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  358. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  359. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  360. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  361. data/lib/active_record/type/big_integer.rb +0 -13
  362. data/lib/active_record/type/binary.rb +0 -50
  363. data/lib/active_record/type/boolean.rb +0 -31
  364. data/lib/active_record/type/decimal.rb +0 -64
  365. data/lib/active_record/type/decorator.rb +0 -14
  366. data/lib/active_record/type/float.rb +0 -19
  367. data/lib/active_record/type/integer.rb +0 -59
  368. data/lib/active_record/type/mutable.rb +0 -16
  369. data/lib/active_record/type/numeric.rb +0 -36
  370. data/lib/active_record/type/string.rb +0 -40
  371. data/lib/active_record/type/time_value.rb +0 -38
  372. data/lib/active_record/type/value.rb +0 -110
  373. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
  374. data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -1,6 +1,8 @@
1
- require 'active_support/core_ext/hash/except'
2
- require 'active_support/core_ext/object/try'
3
- require 'active_support/core_ext/hash/indifferent_access'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/except"
4
+ require "active_support/core_ext/module/redefine_method"
5
+ require "active_support/core_ext/hash/indifferent_access"
4
6
 
5
7
  module ActiveRecord
6
8
  module NestedAttributes #:nodoc:
@@ -10,8 +12,7 @@ module ActiveRecord
10
12
  extend ActiveSupport::Concern
11
13
 
12
14
  included do
13
- class_attribute :nested_attributes_options, instance_writer: false
14
- self.nested_attributes_options = {}
15
+ class_attribute :nested_attributes_options, instance_writer: false, default: {}
15
16
  end
16
17
 
17
18
  # = Active Record Nested Attributes
@@ -61,6 +62,18 @@ module ActiveRecord
61
62
  # member.update params[:member]
62
63
  # member.avatar.icon # => 'sad'
63
64
  #
65
+ # If you want to update the current avatar without providing the id, you must add <tt>:update_only</tt> option.
66
+ #
67
+ # class Member < ActiveRecord::Base
68
+ # has_one :avatar
69
+ # accepts_nested_attributes_for :avatar, update_only: true
70
+ # end
71
+ #
72
+ # params = { member: { avatar_attributes: { icon: 'sad' } } }
73
+ # member.update params[:member]
74
+ # member.avatar.id # => 2
75
+ # member.avatar.icon # => 'sad'
76
+ #
64
77
  # By default you will only be able to set and update attributes on the
65
78
  # associated model. If you want to destroy the associated model through the
66
79
  # attributes hash, you have to enable it first using the
@@ -81,6 +94,9 @@ module ActiveRecord
81
94
  #
82
95
  # Note that the model will _not_ be destroyed until the parent is saved.
83
96
  #
97
+ # Also note that the model will not be destroyed unless you also specify
98
+ # its id in the updated hash.
99
+ #
84
100
  # === One-to-many
85
101
  #
86
102
  # Consider a member that has a number of posts:
@@ -111,7 +127,7 @@ module ActiveRecord
111
127
  # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
112
128
  # member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
113
129
  #
114
- # You may also set a :reject_if proc to silently ignore any new record
130
+ # You may also set a +:reject_if+ proc to silently ignore any new record
115
131
  # hashes if they fail to pass your criteria. For example, the previous
116
132
  # example could be rewritten as:
117
133
  #
@@ -133,7 +149,7 @@ module ActiveRecord
133
149
  # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
134
150
  # member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
135
151
  #
136
- # Alternatively, :reject_if also accepts a symbol for using methods:
152
+ # Alternatively, +:reject_if+ also accepts a symbol for using methods:
137
153
  #
138
154
  # class Member < ActiveRecord::Base
139
155
  # has_many :posts
@@ -144,8 +160,8 @@ module ActiveRecord
144
160
  # has_many :posts
145
161
  # accepts_nested_attributes_for :posts, reject_if: :reject_posts
146
162
  #
147
- # def reject_posts(attributed)
148
- # attributed['title'].blank?
163
+ # def reject_posts(attributes)
164
+ # attributes['title'].blank?
149
165
  # end
150
166
  # end
151
167
  #
@@ -163,6 +179,11 @@ module ActiveRecord
163
179
  # member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
164
180
  # member.posts.second.title # => '[UPDATED] other post'
165
181
  #
182
+ # However, the above applies if the parent model is being updated as well.
183
+ # For example, If you wanted to create a +member+ named _joe_ and wanted to
184
+ # update the +posts+ at the same time, that would give an
185
+ # ActiveRecord::RecordNotFound error.
186
+ #
166
187
  # By default the associated records are protected from being destroyed. If
167
188
  # you want to destroy any of the associated records through the attributes
168
189
  # hash, you have to enable it first using the <tt>:allow_destroy</tt>
@@ -187,38 +208,46 @@ module ActiveRecord
187
208
  # Nested attributes for an associated collection can also be passed in
188
209
  # the form of a hash of hashes instead of an array of hashes:
189
210
  #
190
- # Member.create(name: 'joe',
191
- # posts_attributes: { first: { title: 'Foo' },
192
- # second: { title: 'Bar' } })
211
+ # Member.create(
212
+ # name: 'joe',
213
+ # posts_attributes: {
214
+ # first: { title: 'Foo' },
215
+ # second: { title: 'Bar' }
216
+ # }
217
+ # )
193
218
  #
194
219
  # has the same effect as
195
220
  #
196
- # Member.create(name: 'joe',
197
- # posts_attributes: [ { title: 'Foo' },
198
- # { title: 'Bar' } ])
221
+ # Member.create(
222
+ # name: 'joe',
223
+ # posts_attributes: [
224
+ # { title: 'Foo' },
225
+ # { title: 'Bar' }
226
+ # ]
227
+ # )
199
228
  #
200
229
  # The keys of the hash which is the value for +:posts_attributes+ are
201
230
  # ignored in this case.
202
- # However, it is not allowed to use +'id'+ or +:id+ for one of
231
+ # However, it is not allowed to use <tt>'id'</tt> or <tt>:id</tt> for one of
203
232
  # such keys, otherwise the hash will be wrapped in an array and
204
233
  # interpreted as an attribute hash for a single post.
205
234
  #
206
235
  # Passing attributes for an associated collection in the form of a hash
207
236
  # of hashes can be used with hashes generated from HTTP/HTML parameters,
208
- # where there maybe no natural way to submit an array of hashes.
237
+ # where there may be no natural way to submit an array of hashes.
209
238
  #
210
239
  # === Saving
211
240
  #
212
241
  # All changes to models, including the destruction of those marked for
213
242
  # destruction, are saved and destroyed automatically and atomically when
214
243
  # the parent model is saved. This happens inside the transaction initiated
215
- # by the parents save method. See ActiveRecord::AutosaveAssociation.
244
+ # by the parent's save method. See ActiveRecord::AutosaveAssociation.
216
245
  #
217
246
  # === Validating the presence of a parent model
218
247
  #
219
248
  # If you want to validate that a child record is associated with a parent
220
- # record, you can use <tt>validates_presence_of</tt> and
221
- # <tt>inverse_of</tt> as this example illustrates:
249
+ # record, you can use the +validates_presence_of+ method and the +:inverse_of+
250
+ # key as this example illustrates:
222
251
  #
223
252
  # class Member < ActiveRecord::Base
224
253
  # has_many :posts, inverse_of: :member
@@ -230,7 +259,7 @@ module ActiveRecord
230
259
  # validates_presence_of :member
231
260
  # end
232
261
  #
233
- # Note that if you do not specify the <tt>inverse_of</tt> option, then
262
+ # Note that if you do not specify the +:inverse_of+ option, then
234
263
  # Active Record will try to automatically guess the inverse association
235
264
  # based on heuristics.
236
265
  #
@@ -251,7 +280,7 @@ module ActiveRecord
251
280
  # member.avatar_attributes = {icon: 'sad'}
252
281
  # member.avatar.width # => 200
253
282
  module ClassMethods
254
- REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
283
+ REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } }
255
284
 
256
285
  # Defines an attributes writer for the specified association(s).
257
286
  #
@@ -259,34 +288,36 @@ module ActiveRecord
259
288
  # [:allow_destroy]
260
289
  # If true, destroys any members from the attributes hash with a
261
290
  # <tt>_destroy</tt> key and a value that evaluates to +true+
262
- # (eg. 1, '1', true, or 'true'). This option is off by default.
291
+ # (e.g. 1, '1', true, or 'true'). This option is off by default.
263
292
  # [:reject_if]
264
293
  # Allows you to specify a Proc or a Symbol pointing to a method
265
294
  # that checks whether a record should be built for a certain attribute
266
295
  # hash. The hash is passed to the supplied Proc or the method
267
- # and it should return either +true+ or +false+. When no :reject_if
296
+ # and it should return either +true+ or +false+. When no +:reject_if+
268
297
  # is specified, a record will be built for all attribute hashes that
269
298
  # do not have a <tt>_destroy</tt> value that evaluates to true.
270
299
  # Passing <tt>:all_blank</tt> instead of a Proc will create a proc
271
300
  # that will reject a record where all the attributes are blank excluding
272
- # any value for _destroy.
301
+ # any value for +_destroy+.
273
302
  # [:limit]
274
- # Allows you to specify the maximum number of the associated records that
275
- # can be processed with the nested attributes. Limit also can be specified as a
276
- # Proc or a Symbol pointing to a method that should return number. If the size of the
277
- # nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
278
- # exception is raised. If omitted, any number associations can be processed.
279
- # Note that the :limit option is only applicable to one-to-many associations.
303
+ # Allows you to specify the maximum number of associated records that
304
+ # can be processed with the nested attributes. Limit also can be specified
305
+ # as a Proc or a Symbol pointing to a method that should return a number.
306
+ # If the size of the nested attributes array exceeds the specified limit,
307
+ # NestedAttributes::TooManyRecords exception is raised. If omitted, any
308
+ # number of associations can be processed.
309
+ # Note that the +:limit+ option is only applicable to one-to-many
310
+ # associations.
280
311
  # [:update_only]
281
312
  # For a one-to-one association, this option allows you to specify how
282
- # nested attributes are to be used when an associated record already
313
+ # nested attributes are going to be used when an associated record already
283
314
  # exists. In general, an existing record may either be updated with the
284
315
  # new set of attribute values or be replaced by a wholly new record
285
- # containing those values. By default the :update_only option is +false+
316
+ # containing those values. By default the +:update_only+ option is +false+
286
317
  # and the nested attributes are used to update the existing record only
287
318
  # if they include the record's <tt>:id</tt> value. Otherwise a new
288
319
  # record will be instantiated and used to replace the existing one.
289
- # However if the :update_only option is +true+, the nested attributes
320
+ # However if the +:update_only+ option is +true+, the nested attributes
290
321
  # are used to update the record's attributes always, regardless of
291
322
  # whether the <tt>:id</tt> is present. The option is ignored for collection
292
323
  # associations.
@@ -299,7 +330,7 @@ module ActiveRecord
299
330
  # # creates avatar_attributes= and posts_attributes=
300
331
  # accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
301
332
  def accepts_nested_attributes_for(*attr_names)
302
- options = { :allow_destroy => false, :update_only => false }
333
+ options = { allow_destroy: false, update_only: false }
303
334
  options.update(attr_names.extract_options!)
304
335
  options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
305
336
  options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
@@ -322,28 +353,25 @@ module ActiveRecord
322
353
  end
323
354
 
324
355
  private
325
-
326
- # Generates a writer method for this association. Serves as a point for
327
- # accessing the objects in the association. For example, this method
328
- # could generate the following:
329
- #
330
- # def pirate_attributes=(attributes)
331
- # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
332
- # end
333
- #
334
- # This redirects the attempts to write objects in an association through
335
- # the helper methods defined below. Makes it seem like the nested
336
- # associations are just regular associations.
337
- def generate_association_writer(association_name, type)
338
- generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
339
- if method_defined?(:#{association_name}_attributes=)
340
- remove_method(:#{association_name}_attributes=)
341
- end
342
- def #{association_name}_attributes=(attributes)
343
- assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
344
- end
345
- eoruby
346
- end
356
+ # Generates a writer method for this association. Serves as a point for
357
+ # accessing the objects in the association. For example, this method
358
+ # could generate the following:
359
+ #
360
+ # def pirate_attributes=(attributes)
361
+ # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
362
+ # end
363
+ #
364
+ # This redirects the attempts to write objects in an association through
365
+ # the helper methods defined below. Makes it seem like the nested
366
+ # associations are just regular associations.
367
+ def generate_association_writer(association_name, type)
368
+ generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
369
+ silence_redefinition_of_method :#{association_name}_attributes=
370
+ def #{association_name}_attributes=(attributes)
371
+ assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
372
+ end
373
+ eoruby
374
+ end
347
375
  end
348
376
 
349
377
  # Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
@@ -356,203 +384,214 @@ module ActiveRecord
356
384
  end
357
385
 
358
386
  private
387
+ # Attribute hash keys that should not be assigned as normal attributes.
388
+ # These hash keys are nested attributes implementation details.
389
+ UNASSIGNABLE_KEYS = %w( id _destroy )
359
390
 
360
- # Attribute hash keys that should not be assigned as normal attributes.
361
- # These hash keys are nested attributes implementation details.
362
- UNASSIGNABLE_KEYS = %w( id _destroy )
363
-
364
- # Assigns the given attributes to the association.
365
- #
366
- # If an associated record does not yet exist, one will be instantiated. If
367
- # an associated record already exists, the method's behavior depends on
368
- # the value of the update_only option. If update_only is +false+ and the
369
- # given attributes include an <tt>:id</tt> that matches the existing record's
370
- # id, then the existing record will be modified. If no <tt>:id</tt> is provided
371
- # it will be replaced with a new record. If update_only is +true+ the existing
372
- # record will be modified regardless of whether an <tt>:id</tt> is provided.
373
- #
374
- # If the given attributes include a matching <tt>:id</tt> attribute, or
375
- # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
376
- # then the existing record will be marked for destruction.
377
- def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
378
- options = self.nested_attributes_options[association_name]
379
- attributes = attributes.with_indifferent_access
380
- existing_record = send(association_name)
381
-
382
- if (options[:update_only] || !attributes['id'].blank?) && existing_record &&
383
- (options[:update_only] || existing_record.id.to_s == attributes['id'].to_s)
384
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
385
-
386
- elsif attributes['id'].present?
387
- raise_nested_attributes_record_not_found!(association_name, attributes['id'])
388
-
389
- elsif !reject_new_record?(association_name, attributes)
390
- assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS)
391
-
392
- if existing_record && existing_record.new_record?
393
- existing_record.assign_attributes(assignable_attributes)
394
- association(association_name).initialize_attributes(existing_record)
395
- else
396
- method = "build_#{association_name}"
397
- if respond_to?(method)
398
- send(method, assignable_attributes)
391
+ # Assigns the given attributes to the association.
392
+ #
393
+ # If an associated record does not yet exist, one will be instantiated. If
394
+ # an associated record already exists, the method's behavior depends on
395
+ # the value of the update_only option. If update_only is +false+ and the
396
+ # given attributes include an <tt>:id</tt> that matches the existing record's
397
+ # id, then the existing record will be modified. If no <tt>:id</tt> is provided
398
+ # it will be replaced with a new record. If update_only is +true+ the existing
399
+ # record will be modified regardless of whether an <tt>:id</tt> is provided.
400
+ #
401
+ # If the given attributes include a matching <tt>:id</tt> attribute, or
402
+ # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
403
+ # then the existing record will be marked for destruction.
404
+ def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
405
+ options = nested_attributes_options[association_name]
406
+ if attributes.respond_to?(:permitted?)
407
+ attributes = attributes.to_h
408
+ end
409
+ attributes = attributes.with_indifferent_access
410
+ existing_record = send(association_name)
411
+
412
+ if (options[:update_only] || !attributes["id"].blank?) && existing_record &&
413
+ (options[:update_only] || existing_record.id.to_s == attributes["id"].to_s)
414
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
415
+
416
+ elsif attributes["id"].present?
417
+ raise_nested_attributes_record_not_found!(association_name, attributes["id"])
418
+
419
+ elsif !reject_new_record?(association_name, attributes)
420
+ assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS)
421
+
422
+ if existing_record && existing_record.new_record?
423
+ existing_record.assign_attributes(assignable_attributes)
424
+ association(association_name).initialize_attributes(existing_record)
399
425
  else
400
- raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
426
+ method = :"build_#{association_name}"
427
+ if respond_to?(method)
428
+ send(method, assignable_attributes)
429
+ else
430
+ raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
431
+ end
401
432
  end
402
433
  end
403
434
  end
404
- end
405
435
 
406
- # Assigns the given attributes to the collection association.
407
- #
408
- # Hashes with an <tt>:id</tt> value matching an existing associated record
409
- # will update that record. Hashes without an <tt>:id</tt> value will build
410
- # a new record for the association. Hashes with a matching <tt>:id</tt>
411
- # value and a <tt>:_destroy</tt> key set to a truthy value will mark the
412
- # matched record for destruction.
413
- #
414
- # For example:
415
- #
416
- # assign_nested_attributes_for_collection_association(:people, {
417
- # '1' => { id: '1', name: 'Peter' },
418
- # '2' => { name: 'John' },
419
- # '3' => { id: '2', _destroy: true }
420
- # })
421
- #
422
- # Will update the name of the Person with ID 1, build a new associated
423
- # person with the name 'John', and mark the associated Person with ID 2
424
- # for destruction.
425
- #
426
- # Also accepts an Array of attribute hashes:
427
- #
428
- # assign_nested_attributes_for_collection_association(:people, [
429
- # { id: '1', name: 'Peter' },
430
- # { name: 'John' },
431
- # { id: '2', _destroy: true }
432
- # ])
433
- def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
434
- options = self.nested_attributes_options[association_name]
436
+ # Assigns the given attributes to the collection association.
437
+ #
438
+ # Hashes with an <tt>:id</tt> value matching an existing associated record
439
+ # will update that record. Hashes without an <tt>:id</tt> value will build
440
+ # a new record for the association. Hashes with a matching <tt>:id</tt>
441
+ # value and a <tt>:_destroy</tt> key set to a truthy value will mark the
442
+ # matched record for destruction.
443
+ #
444
+ # For example:
445
+ #
446
+ # assign_nested_attributes_for_collection_association(:people, {
447
+ # '1' => { id: '1', name: 'Peter' },
448
+ # '2' => { name: 'John' },
449
+ # '3' => { id: '2', _destroy: true }
450
+ # })
451
+ #
452
+ # Will update the name of the Person with ID 1, build a new associated
453
+ # person with the name 'John', and mark the associated Person with ID 2
454
+ # for destruction.
455
+ #
456
+ # Also accepts an Array of attribute hashes:
457
+ #
458
+ # assign_nested_attributes_for_collection_association(:people, [
459
+ # { id: '1', name: 'Peter' },
460
+ # { name: 'John' },
461
+ # { id: '2', _destroy: true }
462
+ # ])
463
+ def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
464
+ options = nested_attributes_options[association_name]
465
+ if attributes_collection.respond_to?(:permitted?)
466
+ attributes_collection = attributes_collection.to_h
467
+ end
435
468
 
436
- unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
437
- raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
438
- end
469
+ unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
470
+ raise ArgumentError, "Hash or Array expected for attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
471
+ end
439
472
 
440
- check_record_limit!(options[:limit], attributes_collection)
473
+ check_record_limit!(options[:limit], attributes_collection)
441
474
 
442
- if attributes_collection.is_a? Hash
443
- keys = attributes_collection.keys
444
- attributes_collection = if keys.include?('id') || keys.include?(:id)
445
- [attributes_collection]
446
- else
447
- attributes_collection.values
475
+ if attributes_collection.is_a? Hash
476
+ keys = attributes_collection.keys
477
+ attributes_collection = if keys.include?("id") || keys.include?(:id)
478
+ [attributes_collection]
479
+ else
480
+ attributes_collection.values
481
+ end
448
482
  end
449
- end
450
-
451
- association = association(association_name)
452
483
 
453
- existing_records = if association.loaded?
454
- association.target
455
- else
456
- attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
457
- attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
458
- end
484
+ association = association(association_name)
459
485
 
460
- attributes_collection.each do |attributes|
461
- attributes = attributes.with_indifferent_access
486
+ existing_records = if association.loaded?
487
+ association.target
488
+ else
489
+ attribute_ids = attributes_collection.map { |a| a["id"] || a[:id] }.compact
490
+ attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
491
+ end
462
492
 
463
- if attributes['id'].blank?
464
- unless reject_new_record?(association_name, attributes)
465
- association.build(attributes.except(*UNASSIGNABLE_KEYS))
493
+ attributes_collection.each do |attributes|
494
+ if attributes.respond_to?(:permitted?)
495
+ attributes = attributes.to_h
466
496
  end
467
- elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
468
- unless call_reject_if(association_name, attributes)
469
- # Make sure we are operating on the actual object which is in the association's
470
- # proxy_target array (either by finding it, or adding it if not found)
471
- # Take into account that the proxy_target may have changed due to callbacks
472
- target_record = association.target.detect { |record| record.id.to_s == attributes['id'].to_s }
473
- if target_record
474
- existing_record = target_record
475
- else
476
- association.add_to_target(existing_record, :skip_callbacks)
477
- end
497
+ attributes = attributes.with_indifferent_access
478
498
 
479
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
499
+ if attributes["id"].blank?
500
+ unless reject_new_record?(association_name, attributes)
501
+ association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
502
+ end
503
+ elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s }
504
+ unless call_reject_if(association_name, attributes)
505
+ # Make sure we are operating on the actual object which is in the association's
506
+ # proxy_target array (either by finding it, or adding it if not found)
507
+ # Take into account that the proxy_target may have changed due to callbacks
508
+ target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s }
509
+ if target_record
510
+ existing_record = target_record
511
+ else
512
+ association.add_to_target(existing_record, skip_callbacks: true)
513
+ end
514
+
515
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
516
+ end
517
+ else
518
+ raise_nested_attributes_record_not_found!(association_name, attributes["id"])
480
519
  end
481
- else
482
- raise_nested_attributes_record_not_found!(association_name, attributes['id'])
483
520
  end
484
521
  end
485
- end
486
522
 
487
- # Takes in a limit and checks if the attributes_collection has too many
488
- # records. It accepts limit in the form of symbol, proc, or
489
- # number-like object (anything that can be compared with an integer).
490
- #
491
- # Raises TooManyRecords error if the attributes_collection is
492
- # larger than the limit.
493
- def check_record_limit!(limit, attributes_collection)
494
- if limit
495
- limit = case limit
496
- when Symbol
497
- send(limit)
498
- when Proc
499
- limit.call
500
- else
501
- limit
502
- end
523
+ # Takes in a limit and checks if the attributes_collection has too many
524
+ # records. It accepts limit in the form of symbol, proc, or
525
+ # number-like object (anything that can be compared with an integer).
526
+ #
527
+ # Raises TooManyRecords error if the attributes_collection is
528
+ # larger than the limit.
529
+ def check_record_limit!(limit, attributes_collection)
530
+ if limit
531
+ limit = \
532
+ case limit
533
+ when Symbol
534
+ send(limit)
535
+ when Proc
536
+ limit.call
537
+ else
538
+ limit
539
+ end
503
540
 
504
- if limit && attributes_collection.size > limit
505
- raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
541
+ if limit && attributes_collection.size > limit
542
+ raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
543
+ end
506
544
  end
507
545
  end
508
- end
509
546
 
510
- # Updates a record with the +attributes+ or marks it for destruction if
511
- # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
512
- def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
513
- record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
514
- record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
515
- end
547
+ # Updates a record with the +attributes+ or marks it for destruction if
548
+ # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
549
+ def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
550
+ record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
551
+ record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
552
+ end
516
553
 
517
- # Determines if a hash contains a truthy _destroy key.
518
- def has_destroy_flag?(hash)
519
- Type::Boolean.new.type_cast_from_user(hash['_destroy'])
520
- end
554
+ # Determines if a hash contains a truthy _destroy key.
555
+ def has_destroy_flag?(hash)
556
+ Type::Boolean.new.cast(hash["_destroy"])
557
+ end
521
558
 
522
- # Determines if a new record should be rejected by checking
523
- # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
524
- # association and evaluates to +true+.
525
- def reject_new_record?(association_name, attributes)
526
- will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
527
- end
559
+ # Determines if a new record should be rejected by checking
560
+ # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
561
+ # association and evaluates to +true+.
562
+ def reject_new_record?(association_name, attributes)
563
+ will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
564
+ end
528
565
 
529
- # Determines if a record with the particular +attributes+ should be
530
- # rejected by calling the reject_if Symbol or Proc (if defined).
531
- # The reject_if option is defined by +accepts_nested_attributes_for+.
532
- #
533
- # Returns false if there is a +destroy_flag+ on the attributes.
534
- def call_reject_if(association_name, attributes)
535
- return false if will_be_destroyed?(association_name, attributes)
566
+ # Determines if a record with the particular +attributes+ should be
567
+ # rejected by calling the reject_if Symbol or Proc (if defined).
568
+ # The reject_if option is defined by +accepts_nested_attributes_for+.
569
+ #
570
+ # Returns false if there is a +destroy_flag+ on the attributes.
571
+ def call_reject_if(association_name, attributes)
572
+ return false if will_be_destroyed?(association_name, attributes)
536
573
 
537
- case callback = self.nested_attributes_options[association_name][:reject_if]
538
- when Symbol
539
- method(callback).arity == 0 ? send(callback) : send(callback, attributes)
540
- when Proc
541
- callback.call(attributes)
574
+ case callback = nested_attributes_options[association_name][:reject_if]
575
+ when Symbol
576
+ method(callback).arity == 0 ? send(callback) : send(callback, attributes)
577
+ when Proc
578
+ callback.call(attributes)
579
+ end
542
580
  end
543
- end
544
581
 
545
- # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true
546
- def will_be_destroyed?(association_name, attributes)
547
- allow_destroy?(association_name) && has_destroy_flag?(attributes)
548
- end
582
+ # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true
583
+ def will_be_destroyed?(association_name, attributes)
584
+ allow_destroy?(association_name) && has_destroy_flag?(attributes)
585
+ end
549
586
 
550
- def allow_destroy?(association_name)
551
- self.nested_attributes_options[association_name][:allow_destroy]
552
- end
587
+ def allow_destroy?(association_name)
588
+ nested_attributes_options[association_name][:allow_destroy]
589
+ end
553
590
 
554
- def raise_nested_attributes_record_not_found!(association_name, record_id)
555
- raise RecordNotFound, "Couldn't find #{self.class._reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
556
- end
591
+ def raise_nested_attributes_record_not_found!(association_name, record_id)
592
+ model = self.class._reflect_on_association(association_name).klass.name
593
+ raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
594
+ model, "id", record_id)
595
+ end
557
596
  end
558
597
  end