activerecord 5.0.7.2 → 5.1.0.beta1

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 (216) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +389 -2252
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/examples/performance.rb +28 -28
  6. data/examples/simple.rb +3 -3
  7. data/lib/active_record.rb +20 -20
  8. data/lib/active_record/aggregations.rb +244 -244
  9. data/lib/active_record/association_relation.rb +5 -5
  10. data/lib/active_record/associations.rb +1579 -1569
  11. data/lib/active_record/associations/alias_tracker.rb +1 -1
  12. data/lib/active_record/associations/association.rb +23 -15
  13. data/lib/active_record/associations/association_scope.rb +83 -81
  14. data/lib/active_record/associations/belongs_to_association.rb +0 -1
  15. data/lib/active_record/associations/builder/belongs_to.rb +16 -14
  16. data/lib/active_record/associations/builder/collection_association.rb +1 -2
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +27 -27
  18. data/lib/active_record/associations/collection_association.rb +74 -241
  19. data/lib/active_record/associations/collection_proxy.rb +144 -70
  20. data/lib/active_record/associations/has_many_association.rb +15 -19
  21. data/lib/active_record/associations/has_many_through_association.rb +12 -5
  22. data/lib/active_record/associations/has_one_association.rb +22 -28
  23. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  24. data/lib/active_record/associations/join_dependency.rb +117 -115
  25. data/lib/active_record/associations/join_dependency/join_association.rb +16 -13
  26. data/lib/active_record/associations/join_dependency/join_base.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  28. data/lib/active_record/associations/preloader.rb +94 -94
  29. data/lib/active_record/associations/preloader/association.rb +87 -64
  30. data/lib/active_record/associations/preloader/belongs_to.rb +0 -2
  31. data/lib/active_record/associations/preloader/collection_association.rb +6 -6
  32. data/lib/active_record/associations/preloader/has_many.rb +0 -2
  33. data/lib/active_record/associations/preloader/singular_association.rb +6 -8
  34. data/lib/active_record/associations/preloader/through_association.rb +34 -41
  35. data/lib/active_record/associations/singular_association.rb +8 -25
  36. data/lib/active_record/associations/through_association.rb +3 -6
  37. data/lib/active_record/attribute.rb +98 -71
  38. data/lib/active_record/attribute/user_provided_default.rb +4 -2
  39. data/lib/active_record/attribute_assignment.rb +61 -61
  40. data/lib/active_record/attribute_decorators.rb +35 -13
  41. data/lib/active_record/attribute_methods.rb +56 -65
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -7
  43. data/lib/active_record/attribute_methods/dirty.rb +216 -34
  44. data/lib/active_record/attribute_methods/primary_key.rb +78 -73
  45. data/lib/active_record/attribute_methods/read.rb +39 -35
  46. data/lib/active_record/attribute_methods/serialization.rb +7 -7
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +35 -58
  48. data/lib/active_record/attribute_methods/write.rb +36 -30
  49. data/lib/active_record/attribute_mutation_tracker.rb +53 -10
  50. data/lib/active_record/attribute_set.rb +9 -6
  51. data/lib/active_record/attribute_set/builder.rb +41 -49
  52. data/lib/active_record/attribute_set/yaml_encoder.rb +41 -0
  53. data/lib/active_record/attributes.rb +21 -21
  54. data/lib/active_record/autosave_association.rb +13 -13
  55. data/lib/active_record/base.rb +24 -22
  56. data/lib/active_record/callbacks.rb +52 -14
  57. data/lib/active_record/coders/yaml_column.rb +9 -11
  58. data/lib/active_record/collection_cache_key.rb +6 -17
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +320 -278
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -3
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -34
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -27
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +44 -57
  64. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +9 -19
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +78 -79
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +53 -41
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +99 -93
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -5
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +156 -128
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +424 -382
  71. data/lib/active_record/connection_adapters/column.rb +27 -5
  72. data/lib/active_record/connection_adapters/connection_specification.rb +128 -118
  73. data/lib/active_record/connection_adapters/mysql/column.rb +6 -31
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -43
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +22 -22
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +6 -12
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +16 -19
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +49 -31
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +5 -6
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +24 -26
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -28
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -35
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +3 -3
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +22 -21
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +9 -9
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +5 -3
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -3
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +16 -16
  93. data/lib/active_record/connection_adapters/postgresql/oid/{rails_5_1_point.rb → legacy_point.rb} +9 -16
  94. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  95. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +28 -8
  97. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +28 -30
  98. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +51 -51
  100. data/lib/active_record/connection_adapters/postgresql/quoting.rb +38 -36
  101. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +37 -24
  103. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +19 -23
  104. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +161 -170
  105. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +4 -4
  106. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -7
  107. data/lib/active_record/connection_adapters/postgresql_adapter.rb +179 -152
  108. data/lib/active_record/connection_adapters/schema_cache.rb +16 -7
  109. data/lib/active_record/connection_adapters/sql_type_metadata.rb +3 -3
  110. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +1 -1
  111. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +16 -20
  112. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +1 -8
  113. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +28 -0
  114. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +17 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +187 -130
  116. data/lib/active_record/connection_adapters/statement_pool.rb +7 -7
  117. data/lib/active_record/connection_handling.rb +14 -26
  118. data/lib/active_record/core.rb +110 -93
  119. data/lib/active_record/counter_cache.rb +62 -13
  120. data/lib/active_record/define_callbacks.rb +20 -0
  121. data/lib/active_record/dynamic_matchers.rb +80 -79
  122. data/lib/active_record/enum.rb +8 -6
  123. data/lib/active_record/errors.rb +58 -15
  124. data/lib/active_record/explain.rb +1 -2
  125. data/lib/active_record/explain_registry.rb +1 -1
  126. data/lib/active_record/explain_subscriber.rb +7 -4
  127. data/lib/active_record/fixture_set/file.rb +11 -8
  128. data/lib/active_record/fixtures.rb +66 -53
  129. data/lib/active_record/gem_version.rb +3 -3
  130. data/lib/active_record/inheritance.rb +93 -79
  131. data/lib/active_record/integration.rb +7 -7
  132. data/lib/active_record/internal_metadata.rb +3 -16
  133. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  134. data/lib/active_record/locking/optimistic.rb +64 -56
  135. data/lib/active_record/locking/pessimistic.rb +10 -1
  136. data/lib/active_record/log_subscriber.rb +29 -29
  137. data/lib/active_record/migration.rb +155 -172
  138. data/lib/active_record/migration/command_recorder.rb +94 -94
  139. data/lib/active_record/migration/compatibility.rb +76 -37
  140. data/lib/active_record/migration/join_table.rb +6 -6
  141. data/lib/active_record/model_schema.rb +85 -119
  142. data/lib/active_record/nested_attributes.rb +200 -199
  143. data/lib/active_record/null_relation.rb +10 -33
  144. data/lib/active_record/persistence.rb +45 -38
  145. data/lib/active_record/query_cache.rb +4 -8
  146. data/lib/active_record/querying.rb +2 -3
  147. data/lib/active_record/railtie.rb +16 -17
  148. data/lib/active_record/railties/controller_runtime.rb +6 -2
  149. data/lib/active_record/railties/databases.rake +125 -140
  150. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  151. data/lib/active_record/readonly_attributes.rb +2 -2
  152. data/lib/active_record/reflection.rb +79 -96
  153. data/lib/active_record/relation.rb +72 -115
  154. data/lib/active_record/relation/batches.rb +87 -58
  155. data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
  156. data/lib/active_record/relation/calculations.rb +154 -160
  157. data/lib/active_record/relation/delegation.rb +30 -29
  158. data/lib/active_record/relation/finder_methods.rb +195 -226
  159. data/lib/active_record/relation/merger.rb +58 -62
  160. data/lib/active_record/relation/predicate_builder.rb +92 -89
  161. data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -5
  162. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +23 -23
  163. data/lib/active_record/relation/predicate_builder/base_handler.rb +3 -1
  164. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +0 -8
  165. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +12 -10
  166. data/lib/active_record/relation/predicate_builder/range_handler.rb +0 -8
  167. data/lib/active_record/relation/query_attribute.rb +1 -1
  168. data/lib/active_record/relation/query_methods.rb +247 -295
  169. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  170. data/lib/active_record/relation/spawn_methods.rb +4 -5
  171. data/lib/active_record/relation/where_clause.rb +79 -65
  172. data/lib/active_record/relation/where_clause_factory.rb +47 -8
  173. data/lib/active_record/result.rb +29 -31
  174. data/lib/active_record/runtime_registry.rb +3 -3
  175. data/lib/active_record/sanitization.rb +182 -197
  176. data/lib/active_record/schema.rb +3 -3
  177. data/lib/active_record/schema_dumper.rb +14 -37
  178. data/lib/active_record/schema_migration.rb +3 -3
  179. data/lib/active_record/scoping.rb +9 -10
  180. data/lib/active_record/scoping/default.rb +87 -91
  181. data/lib/active_record/scoping/named.rb +16 -28
  182. data/lib/active_record/secure_token.rb +2 -2
  183. data/lib/active_record/statement_cache.rb +13 -15
  184. data/lib/active_record/store.rb +31 -32
  185. data/lib/active_record/suppressor.rb +2 -1
  186. data/lib/active_record/table_metadata.rb +9 -5
  187. data/lib/active_record/tasks/database_tasks.rb +72 -65
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +75 -72
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +53 -48
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +18 -16
  191. data/lib/active_record/timestamp.rb +39 -25
  192. data/lib/active_record/touch_later.rb +1 -2
  193. data/lib/active_record/transactions.rb +98 -110
  194. data/lib/active_record/type.rb +17 -13
  195. data/lib/active_record/type/adapter_specific_registry.rb +46 -42
  196. data/lib/active_record/type/decimal_without_scale.rb +9 -0
  197. data/lib/active_record/type/hash_lookup_type_map.rb +3 -3
  198. data/lib/active_record/type/serialized.rb +8 -8
  199. data/lib/active_record/type/text.rb +9 -0
  200. data/lib/active_record/type/time.rb +0 -1
  201. data/lib/active_record/type/type_map.rb +11 -15
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type_caster.rb +2 -2
  204. data/lib/active_record/type_caster/connection.rb +8 -6
  205. data/lib/active_record/type_caster/map.rb +3 -1
  206. data/lib/active_record/validations.rb +4 -4
  207. data/lib/active_record/validations/associated.rb +1 -1
  208. data/lib/active_record/validations/presence.rb +2 -2
  209. data/lib/active_record/validations/uniqueness.rb +8 -39
  210. data/lib/active_record/version.rb +1 -1
  211. data/lib/rails/generators/active_record.rb +4 -4
  212. data/lib/rails/generators/active_record/migration.rb +2 -2
  213. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -34
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -9
  215. metadata +22 -13
  216. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
@@ -1,6 +1,6 @@
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
+ require "active_support/core_ext/hash/except"
2
+ require "active_support/core_ext/object/try"
3
+ require "active_support/core_ext/hash/indifferent_access"
4
4
 
5
5
  module ActiveRecord
6
6
  module NestedAttributes #:nodoc:
@@ -267,7 +267,7 @@ module ActiveRecord
267
267
  # member.avatar_attributes = {icon: 'sad'}
268
268
  # member.avatar.width # => 200
269
269
  module ClassMethods
270
- REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
270
+ REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } }
271
271
 
272
272
  # Defines an attributes writer for the specified association(s).
273
273
  #
@@ -317,7 +317,7 @@ module ActiveRecord
317
317
  # # creates avatar_attributes= and posts_attributes=
318
318
  # accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
319
319
  def accepts_nested_attributes_for(*attr_names)
320
- options = { :allow_destroy => false, :update_only => false }
320
+ options = { allow_destroy: false, update_only: false }
321
321
  options.update(attr_names.extract_options!)
322
322
  options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
323
323
  options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
@@ -341,27 +341,27 @@ module ActiveRecord
341
341
 
342
342
  private
343
343
 
344
- # Generates a writer method for this association. Serves as a point for
345
- # accessing the objects in the association. For example, this method
346
- # could generate the following:
347
- #
348
- # def pirate_attributes=(attributes)
349
- # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
350
- # end
351
- #
352
- # This redirects the attempts to write objects in an association through
353
- # the helper methods defined below. Makes it seem like the nested
354
- # associations are just regular associations.
355
- def generate_association_writer(association_name, type)
356
- generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
357
- if method_defined?(:#{association_name}_attributes=)
358
- remove_method(:#{association_name}_attributes=)
359
- end
360
- def #{association_name}_attributes=(attributes)
361
- assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
362
- end
363
- eoruby
364
- end
344
+ # Generates a writer method for this association. Serves as a point for
345
+ # accessing the objects in the association. For example, this method
346
+ # could generate the following:
347
+ #
348
+ # def pirate_attributes=(attributes)
349
+ # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
350
+ # end
351
+ #
352
+ # This redirects the attempts to write objects in an association through
353
+ # the helper methods defined below. Makes it seem like the nested
354
+ # associations are just regular associations.
355
+ def generate_association_writer(association_name, type)
356
+ generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
357
+ if method_defined?(:#{association_name}_attributes=)
358
+ remove_method(:#{association_name}_attributes=)
359
+ end
360
+ def #{association_name}_attributes=(attributes)
361
+ assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
362
+ end
363
+ eoruby
364
+ end
365
365
  end
366
366
 
367
367
  # Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
@@ -375,213 +375,214 @@ module ActiveRecord
375
375
 
376
376
  private
377
377
 
378
- # Attribute hash keys that should not be assigned as normal attributes.
379
- # These hash keys are nested attributes implementation details.
380
- UNASSIGNABLE_KEYS = %w( id _destroy )
381
-
382
- # Assigns the given attributes to the association.
383
- #
384
- # If an associated record does not yet exist, one will be instantiated. If
385
- # an associated record already exists, the method's behavior depends on
386
- # the value of the update_only option. If update_only is +false+ and the
387
- # given attributes include an <tt>:id</tt> that matches the existing record's
388
- # id, then the existing record will be modified. If no <tt>:id</tt> is provided
389
- # it will be replaced with a new record. If update_only is +true+ the existing
390
- # record will be modified regardless of whether an <tt>:id</tt> is provided.
391
- #
392
- # If the given attributes include a matching <tt>:id</tt> attribute, or
393
- # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
394
- # then the existing record will be marked for destruction.
395
- def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
396
- options = self.nested_attributes_options[association_name]
397
- if attributes.respond_to?(:permitted?)
398
- attributes = attributes.to_h
399
- end
400
- attributes = attributes.with_indifferent_access
401
- existing_record = send(association_name)
378
+ # Attribute hash keys that should not be assigned as normal attributes.
379
+ # These hash keys are nested attributes implementation details.
380
+ UNASSIGNABLE_KEYS = %w( id _destroy )
402
381
 
403
- if (options[:update_only] || !attributes['id'].blank?) && existing_record &&
404
- (options[:update_only] || existing_record.id.to_s == attributes['id'].to_s)
405
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
382
+ # Assigns the given attributes to the association.
383
+ #
384
+ # If an associated record does not yet exist, one will be instantiated. If
385
+ # an associated record already exists, the method's behavior depends on
386
+ # the value of the update_only option. If update_only is +false+ and the
387
+ # given attributes include an <tt>:id</tt> that matches the existing record's
388
+ # id, then the existing record will be modified. If no <tt>:id</tt> is provided
389
+ # it will be replaced with a new record. If update_only is +true+ the existing
390
+ # record will be modified regardless of whether an <tt>:id</tt> is provided.
391
+ #
392
+ # If the given attributes include a matching <tt>:id</tt> attribute, or
393
+ # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
394
+ # then the existing record will be marked for destruction.
395
+ def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
396
+ options = nested_attributes_options[association_name]
397
+ if attributes.respond_to?(:permitted?)
398
+ attributes = attributes.to_h
399
+ end
400
+ attributes = attributes.with_indifferent_access
401
+ existing_record = send(association_name)
406
402
 
407
- elsif attributes['id'].present?
408
- raise_nested_attributes_record_not_found!(association_name, attributes['id'])
403
+ if (options[:update_only] || !attributes["id"].blank?) && existing_record &&
404
+ (options[:update_only] || existing_record.id.to_s == attributes["id"].to_s)
405
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
409
406
 
410
- elsif !reject_new_record?(association_name, attributes)
411
- assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS)
407
+ elsif attributes["id"].present?
408
+ raise_nested_attributes_record_not_found!(association_name, attributes["id"])
412
409
 
413
- if existing_record && existing_record.new_record?
414
- existing_record.assign_attributes(assignable_attributes)
415
- association(association_name).initialize_attributes(existing_record)
416
- else
417
- method = "build_#{association_name}"
418
- if respond_to?(method)
419
- send(method, assignable_attributes)
410
+ elsif !reject_new_record?(association_name, attributes)
411
+ assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS)
412
+
413
+ if existing_record && existing_record.new_record?
414
+ existing_record.assign_attributes(assignable_attributes)
415
+ association(association_name).initialize_attributes(existing_record)
420
416
  else
421
- raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
417
+ method = "build_#{association_name}"
418
+ if respond_to?(method)
419
+ send(method, assignable_attributes)
420
+ else
421
+ raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
422
+ end
422
423
  end
423
424
  end
424
425
  end
425
- end
426
426
 
427
- # Assigns the given attributes to the collection association.
428
- #
429
- # Hashes with an <tt>:id</tt> value matching an existing associated record
430
- # will update that record. Hashes without an <tt>:id</tt> value will build
431
- # a new record for the association. Hashes with a matching <tt>:id</tt>
432
- # value and a <tt>:_destroy</tt> key set to a truthy value will mark the
433
- # matched record for destruction.
434
- #
435
- # For example:
436
- #
437
- # assign_nested_attributes_for_collection_association(:people, {
438
- # '1' => { id: '1', name: 'Peter' },
439
- # '2' => { name: 'John' },
440
- # '3' => { id: '2', _destroy: true }
441
- # })
442
- #
443
- # Will update the name of the Person with ID 1, build a new associated
444
- # person with the name 'John', and mark the associated Person with ID 2
445
- # for destruction.
446
- #
447
- # Also accepts an Array of attribute hashes:
448
- #
449
- # assign_nested_attributes_for_collection_association(:people, [
450
- # { id: '1', name: 'Peter' },
451
- # { name: 'John' },
452
- # { id: '2', _destroy: true }
453
- # ])
454
- def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
455
- options = self.nested_attributes_options[association_name]
456
- if attributes_collection.respond_to?(:permitted?)
457
- attributes_collection = attributes_collection.to_h
458
- end
427
+ # Assigns the given attributes to the collection association.
428
+ #
429
+ # Hashes with an <tt>:id</tt> value matching an existing associated record
430
+ # will update that record. Hashes without an <tt>:id</tt> value will build
431
+ # a new record for the association. Hashes with a matching <tt>:id</tt>
432
+ # value and a <tt>:_destroy</tt> key set to a truthy value will mark the
433
+ # matched record for destruction.
434
+ #
435
+ # For example:
436
+ #
437
+ # assign_nested_attributes_for_collection_association(:people, {
438
+ # '1' => { id: '1', name: 'Peter' },
439
+ # '2' => { name: 'John' },
440
+ # '3' => { id: '2', _destroy: true }
441
+ # })
442
+ #
443
+ # Will update the name of the Person with ID 1, build a new associated
444
+ # person with the name 'John', and mark the associated Person with ID 2
445
+ # for destruction.
446
+ #
447
+ # Also accepts an Array of attribute hashes:
448
+ #
449
+ # assign_nested_attributes_for_collection_association(:people, [
450
+ # { id: '1', name: 'Peter' },
451
+ # { name: 'John' },
452
+ # { id: '2', _destroy: true }
453
+ # ])
454
+ def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
455
+ options = nested_attributes_options[association_name]
456
+ if attributes_collection.respond_to?(:permitted?)
457
+ attributes_collection = attributes_collection.to_h
458
+ end
459
459
 
460
- unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
461
- raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
462
- end
460
+ unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
461
+ raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
462
+ end
463
463
 
464
- check_record_limit!(options[:limit], attributes_collection)
464
+ check_record_limit!(options[:limit], attributes_collection)
465
465
 
466
- if attributes_collection.is_a? Hash
467
- keys = attributes_collection.keys
468
- attributes_collection = if keys.include?('id') || keys.include?(:id)
469
- [attributes_collection]
470
- else
471
- attributes_collection.values
466
+ if attributes_collection.is_a? Hash
467
+ keys = attributes_collection.keys
468
+ attributes_collection = if keys.include?("id") || keys.include?(:id)
469
+ [attributes_collection]
470
+ else
471
+ attributes_collection.values
472
+ end
472
473
  end
473
- end
474
474
 
475
- association = association(association_name)
475
+ association = association(association_name)
476
476
 
477
- existing_records = if association.loaded?
478
- association.target
479
- else
480
- attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
481
- attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
482
- end
483
-
484
- attributes_collection.each do |attributes|
485
- if attributes.respond_to?(:permitted?)
486
- attributes = attributes.to_h
477
+ existing_records = if association.loaded?
478
+ association.target
479
+ else
480
+ attribute_ids = attributes_collection.map { |a| a["id"] || a[:id] }.compact
481
+ attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
487
482
  end
488
- attributes = attributes.with_indifferent_access
489
483
 
490
- if attributes['id'].blank?
491
- unless reject_new_record?(association_name, attributes)
492
- association.build(attributes.except(*UNASSIGNABLE_KEYS))
484
+ attributes_collection.each do |attributes|
485
+ if attributes.respond_to?(:permitted?)
486
+ attributes = attributes.to_h
493
487
  end
494
- elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
495
- unless call_reject_if(association_name, attributes)
496
- # Make sure we are operating on the actual object which is in the association's
497
- # proxy_target array (either by finding it, or adding it if not found)
498
- # Take into account that the proxy_target may have changed due to callbacks
499
- target_record = association.target.detect { |record| record.id.to_s == attributes['id'].to_s }
500
- if target_record
501
- existing_record = target_record
502
- else
503
- association.add_to_target(existing_record, :skip_callbacks)
504
- end
488
+ attributes = attributes.with_indifferent_access
505
489
 
506
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
490
+ if attributes["id"].blank?
491
+ unless reject_new_record?(association_name, attributes)
492
+ association.build(attributes.except(*UNASSIGNABLE_KEYS))
493
+ end
494
+ elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s }
495
+ unless call_reject_if(association_name, attributes)
496
+ # Make sure we are operating on the actual object which is in the association's
497
+ # proxy_target array (either by finding it, or adding it if not found)
498
+ # Take into account that the proxy_target may have changed due to callbacks
499
+ target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s }
500
+ if target_record
501
+ existing_record = target_record
502
+ else
503
+ association.add_to_target(existing_record, :skip_callbacks)
504
+ end
505
+
506
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
507
+ end
508
+ else
509
+ raise_nested_attributes_record_not_found!(association_name, attributes["id"])
507
510
  end
508
- else
509
- raise_nested_attributes_record_not_found!(association_name, attributes['id'])
510
511
  end
511
512
  end
512
- end
513
513
 
514
- # Takes in a limit and checks if the attributes_collection has too many
515
- # records. It accepts limit in the form of symbol, proc, or
516
- # number-like object (anything that can be compared with an integer).
517
- #
518
- # Raises TooManyRecords error if the attributes_collection is
519
- # larger than the limit.
520
- def check_record_limit!(limit, attributes_collection)
521
- if limit
522
- limit = case limit
523
- when Symbol
524
- send(limit)
525
- when Proc
526
- limit.call
527
- else
528
- limit
529
- end
514
+ # Takes in a limit and checks if the attributes_collection has too many
515
+ # records. It accepts limit in the form of symbol, proc, or
516
+ # number-like object (anything that can be compared with an integer).
517
+ #
518
+ # Raises TooManyRecords error if the attributes_collection is
519
+ # larger than the limit.
520
+ def check_record_limit!(limit, attributes_collection)
521
+ if limit
522
+ limit = \
523
+ case limit
524
+ when Symbol
525
+ send(limit)
526
+ when Proc
527
+ limit.call
528
+ else
529
+ limit
530
+ end
530
531
 
531
- if limit && attributes_collection.size > limit
532
- raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
532
+ if limit && attributes_collection.size > limit
533
+ raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
534
+ end
533
535
  end
534
536
  end
535
- end
536
537
 
537
- # Updates a record with the +attributes+ or marks it for destruction if
538
- # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
539
- def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
540
- record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
541
- record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
542
- end
538
+ # Updates a record with the +attributes+ or marks it for destruction if
539
+ # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
540
+ def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
541
+ record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
542
+ record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
543
+ end
543
544
 
544
- # Determines if a hash contains a truthy _destroy key.
545
- def has_destroy_flag?(hash)
546
- Type::Boolean.new.cast(hash['_destroy'])
547
- end
545
+ # Determines if a hash contains a truthy _destroy key.
546
+ def has_destroy_flag?(hash)
547
+ Type::Boolean.new.cast(hash["_destroy"])
548
+ end
548
549
 
549
- # Determines if a new record should be rejected by checking
550
- # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
551
- # association and evaluates to +true+.
552
- def reject_new_record?(association_name, attributes)
553
- will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
554
- end
550
+ # Determines if a new record should be rejected by checking
551
+ # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
552
+ # association and evaluates to +true+.
553
+ def reject_new_record?(association_name, attributes)
554
+ will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
555
+ end
555
556
 
556
- # Determines if a record with the particular +attributes+ should be
557
- # rejected by calling the reject_if Symbol or Proc (if defined).
558
- # The reject_if option is defined by +accepts_nested_attributes_for+.
559
- #
560
- # Returns false if there is a +destroy_flag+ on the attributes.
561
- def call_reject_if(association_name, attributes)
562
- return false if will_be_destroyed?(association_name, attributes)
557
+ # Determines if a record with the particular +attributes+ should be
558
+ # rejected by calling the reject_if Symbol or Proc (if defined).
559
+ # The reject_if option is defined by +accepts_nested_attributes_for+.
560
+ #
561
+ # Returns false if there is a +destroy_flag+ on the attributes.
562
+ def call_reject_if(association_name, attributes)
563
+ return false if will_be_destroyed?(association_name, attributes)
563
564
 
564
- case callback = self.nested_attributes_options[association_name][:reject_if]
565
- when Symbol
566
- method(callback).arity == 0 ? send(callback) : send(callback, attributes)
567
- when Proc
568
- callback.call(attributes)
565
+ case callback = nested_attributes_options[association_name][:reject_if]
566
+ when Symbol
567
+ method(callback).arity == 0 ? send(callback) : send(callback, attributes)
568
+ when Proc
569
+ callback.call(attributes)
570
+ end
569
571
  end
570
- end
571
572
 
572
- # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true
573
- def will_be_destroyed?(association_name, attributes)
574
- allow_destroy?(association_name) && has_destroy_flag?(attributes)
575
- end
573
+ # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true
574
+ def will_be_destroyed?(association_name, attributes)
575
+ allow_destroy?(association_name) && has_destroy_flag?(attributes)
576
+ end
576
577
 
577
- def allow_destroy?(association_name)
578
- self.nested_attributes_options[association_name][:allow_destroy]
579
- end
578
+ def allow_destroy?(association_name)
579
+ nested_attributes_options[association_name][:allow_destroy]
580
+ end
580
581
 
581
- def raise_nested_attributes_record_not_found!(association_name, record_id)
582
- model = self.class._reflect_on_association(association_name).klass.name
583
- raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
584
- model, 'id', record_id)
585
- end
582
+ def raise_nested_attributes_record_not_found!(association_name, record_id)
583
+ model = self.class._reflect_on_association(association_name).klass.name
584
+ raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
585
+ model, "id", record_id)
586
+ end
586
587
  end
587
588
  end