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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +389 -2252
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/examples/performance.rb +28 -28
- data/examples/simple.rb +3 -3
- data/lib/active_record.rb +20 -20
- data/lib/active_record/aggregations.rb +244 -244
- data/lib/active_record/association_relation.rb +5 -5
- data/lib/active_record/associations.rb +1579 -1569
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +23 -15
- data/lib/active_record/associations/association_scope.rb +83 -81
- data/lib/active_record/associations/belongs_to_association.rb +0 -1
- data/lib/active_record/associations/builder/belongs_to.rb +16 -14
- data/lib/active_record/associations/builder/collection_association.rb +1 -2
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +27 -27
- data/lib/active_record/associations/collection_association.rb +74 -241
- data/lib/active_record/associations/collection_proxy.rb +144 -70
- data/lib/active_record/associations/has_many_association.rb +15 -19
- data/lib/active_record/associations/has_many_through_association.rb +12 -5
- data/lib/active_record/associations/has_one_association.rb +22 -28
- data/lib/active_record/associations/has_one_through_association.rb +5 -1
- data/lib/active_record/associations/join_dependency.rb +117 -115
- data/lib/active_record/associations/join_dependency/join_association.rb +16 -13
- data/lib/active_record/associations/join_dependency/join_base.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/preloader.rb +94 -94
- data/lib/active_record/associations/preloader/association.rb +87 -64
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -2
- data/lib/active_record/associations/preloader/collection_association.rb +6 -6
- data/lib/active_record/associations/preloader/has_many.rb +0 -2
- data/lib/active_record/associations/preloader/singular_association.rb +6 -8
- data/lib/active_record/associations/preloader/through_association.rb +34 -41
- data/lib/active_record/associations/singular_association.rb +8 -25
- data/lib/active_record/associations/through_association.rb +3 -6
- data/lib/active_record/attribute.rb +98 -71
- data/lib/active_record/attribute/user_provided_default.rb +4 -2
- data/lib/active_record/attribute_assignment.rb +61 -61
- data/lib/active_record/attribute_decorators.rb +35 -13
- data/lib/active_record/attribute_methods.rb +56 -65
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -7
- data/lib/active_record/attribute_methods/dirty.rb +216 -34
- data/lib/active_record/attribute_methods/primary_key.rb +78 -73
- data/lib/active_record/attribute_methods/read.rb +39 -35
- data/lib/active_record/attribute_methods/serialization.rb +7 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +35 -58
- data/lib/active_record/attribute_methods/write.rb +36 -30
- data/lib/active_record/attribute_mutation_tracker.rb +53 -10
- data/lib/active_record/attribute_set.rb +9 -6
- data/lib/active_record/attribute_set/builder.rb +41 -49
- data/lib/active_record/attribute_set/yaml_encoder.rb +41 -0
- data/lib/active_record/attributes.rb +21 -21
- data/lib/active_record/autosave_association.rb +13 -13
- data/lib/active_record/base.rb +24 -22
- data/lib/active_record/callbacks.rb +52 -14
- data/lib/active_record/coders/yaml_column.rb +9 -11
- data/lib/active_record/collection_cache_key.rb +6 -17
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +320 -278
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -34
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -27
- data/lib/active_record/connection_adapters/abstract/quoting.rb +44 -57
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +9 -19
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +78 -79
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +53 -41
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +99 -93
- data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +156 -128
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +424 -382
- data/lib/active_record/connection_adapters/column.rb +27 -5
- data/lib/active_record/connection_adapters/connection_specification.rb +128 -118
- data/lib/active_record/connection_adapters/mysql/column.rb +6 -31
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -43
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +22 -22
- data/lib/active_record/connection_adapters/mysql/quoting.rb +6 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +16 -19
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +49 -31
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +5 -6
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +24 -26
- data/lib/active_record/connection_adapters/postgresql/column.rb +1 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -35
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +22 -21
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +9 -9
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +16 -16
- data/lib/active_record/connection_adapters/postgresql/oid/{rails_5_1_point.rb → legacy_point.rb} +9 -16
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +28 -8
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +28 -30
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +51 -51
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +38 -36
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +37 -24
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +19 -23
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +161 -170
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -7
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +179 -152
- data/lib/active_record/connection_adapters/schema_cache.rb +16 -7
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +3 -3
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +16 -20
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +1 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +28 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +187 -130
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -7
- data/lib/active_record/connection_handling.rb +14 -26
- data/lib/active_record/core.rb +110 -93
- data/lib/active_record/counter_cache.rb +62 -13
- data/lib/active_record/define_callbacks.rb +20 -0
- data/lib/active_record/dynamic_matchers.rb +80 -79
- data/lib/active_record/enum.rb +8 -6
- data/lib/active_record/errors.rb +58 -15
- data/lib/active_record/explain.rb +1 -2
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +7 -4
- data/lib/active_record/fixture_set/file.rb +11 -8
- data/lib/active_record/fixtures.rb +66 -53
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +93 -79
- data/lib/active_record/integration.rb +7 -7
- data/lib/active_record/internal_metadata.rb +3 -16
- data/lib/active_record/legacy_yaml_adapter.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +64 -56
- data/lib/active_record/locking/pessimistic.rb +10 -1
- data/lib/active_record/log_subscriber.rb +29 -29
- data/lib/active_record/migration.rb +155 -172
- data/lib/active_record/migration/command_recorder.rb +94 -94
- data/lib/active_record/migration/compatibility.rb +76 -37
- data/lib/active_record/migration/join_table.rb +6 -6
- data/lib/active_record/model_schema.rb +85 -119
- data/lib/active_record/nested_attributes.rb +200 -199
- data/lib/active_record/null_relation.rb +10 -33
- data/lib/active_record/persistence.rb +45 -38
- data/lib/active_record/query_cache.rb +4 -8
- data/lib/active_record/querying.rb +2 -3
- data/lib/active_record/railtie.rb +16 -17
- data/lib/active_record/railties/controller_runtime.rb +6 -2
- data/lib/active_record/railties/databases.rake +125 -140
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -2
- data/lib/active_record/reflection.rb +79 -96
- data/lib/active_record/relation.rb +72 -115
- data/lib/active_record/relation/batches.rb +87 -58
- data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
- data/lib/active_record/relation/calculations.rb +154 -160
- data/lib/active_record/relation/delegation.rb +30 -29
- data/lib/active_record/relation/finder_methods.rb +195 -226
- data/lib/active_record/relation/merger.rb +58 -62
- data/lib/active_record/relation/predicate_builder.rb +92 -89
- data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -5
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +23 -23
- data/lib/active_record/relation/predicate_builder/base_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +0 -8
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +12 -10
- data/lib/active_record/relation/predicate_builder/range_handler.rb +0 -8
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +247 -295
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +4 -5
- data/lib/active_record/relation/where_clause.rb +79 -65
- data/lib/active_record/relation/where_clause_factory.rb +47 -8
- data/lib/active_record/result.rb +29 -31
- data/lib/active_record/runtime_registry.rb +3 -3
- data/lib/active_record/sanitization.rb +182 -197
- data/lib/active_record/schema.rb +3 -3
- data/lib/active_record/schema_dumper.rb +14 -37
- data/lib/active_record/schema_migration.rb +3 -3
- data/lib/active_record/scoping.rb +9 -10
- data/lib/active_record/scoping/default.rb +87 -91
- data/lib/active_record/scoping/named.rb +16 -28
- data/lib/active_record/secure_token.rb +2 -2
- data/lib/active_record/statement_cache.rb +13 -15
- data/lib/active_record/store.rb +31 -32
- data/lib/active_record/suppressor.rb +2 -1
- data/lib/active_record/table_metadata.rb +9 -5
- data/lib/active_record/tasks/database_tasks.rb +72 -65
- data/lib/active_record/tasks/mysql_database_tasks.rb +75 -72
- data/lib/active_record/tasks/postgresql_database_tasks.rb +53 -48
- data/lib/active_record/tasks/sqlite_database_tasks.rb +18 -16
- data/lib/active_record/timestamp.rb +39 -25
- data/lib/active_record/touch_later.rb +1 -2
- data/lib/active_record/transactions.rb +98 -110
- data/lib/active_record/type.rb +17 -13
- data/lib/active_record/type/adapter_specific_registry.rb +46 -42
- data/lib/active_record/type/decimal_without_scale.rb +9 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +3 -3
- data/lib/active_record/type/serialized.rb +8 -8
- data/lib/active_record/type/text.rb +9 -0
- data/lib/active_record/type/time.rb +0 -1
- data/lib/active_record/type/type_map.rb +11 -15
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type_caster.rb +2 -2
- data/lib/active_record/type_caster/connection.rb +8 -6
- data/lib/active_record/type_caster/map.rb +3 -1
- data/lib/active_record/validations.rb +4 -4
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/presence.rb +2 -2
- data/lib/active_record/validations/uniqueness.rb +8 -39
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record.rb +4 -4
- data/lib/rails/generators/active_record/migration.rb +2 -2
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -34
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -9
- metadata +22 -13
- data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
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 ==
|
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 = { :
|
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
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
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
|
-
|
379
|
-
|
380
|
-
|
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
|
-
|
404
|
-
|
405
|
-
|
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
|
-
|
408
|
-
|
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
|
-
|
411
|
-
|
407
|
+
elsif attributes["id"].present?
|
408
|
+
raise_nested_attributes_record_not_found!(association_name, attributes["id"])
|
412
409
|
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
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
|
-
|
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
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
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
|
-
|
461
|
-
|
462
|
-
|
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
|
-
|
464
|
+
check_record_limit!(options[:limit], attributes_collection)
|
465
465
|
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
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
|
-
|
475
|
+
association = association(association_name)
|
476
476
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
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
|
-
|
491
|
-
|
492
|
-
|
484
|
+
attributes_collection.each do |attributes|
|
485
|
+
if attributes.respond_to?(:permitted?)
|
486
|
+
attributes = attributes.to_h
|
493
487
|
end
|
494
|
-
|
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
|
-
|
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
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
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
|
-
|
532
|
-
|
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
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
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
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
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
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
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
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
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
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
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
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
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
|
-
|
578
|
-
|
579
|
-
|
578
|
+
def allow_destroy?(association_name)
|
579
|
+
nested_attributes_options[association_name][:allow_destroy]
|
580
|
+
end
|
580
581
|
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
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
|