activerecord 4.2.0 → 5.2.8.1

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