activerecord 3.2.19 → 5.0.0
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 +7 -0
- data/CHANGELOG.md +1715 -604
- data/MIT-LICENSE +2 -2
- data/README.rdoc +40 -45
- data/examples/performance.rb +33 -22
- data/examples/simple.rb +3 -4
- data/lib/active_record/aggregations.rb +76 -51
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +54 -40
- data/lib/active_record/associations/association.rb +76 -56
- data/lib/active_record/associations/association_scope.rb +125 -93
- data/lib/active_record/associations/belongs_to_association.rb +57 -28
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +120 -32
- data/lib/active_record/associations/builder/belongs_to.rb +115 -62
- data/lib/active_record/associations/builder/collection_association.rb +61 -53
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
- data/lib/active_record/associations/builder/has_many.rb +9 -65
- data/lib/active_record/associations/builder/has_one.rb +18 -52
- data/lib/active_record/associations/builder/singular_association.rb +18 -19
- data/lib/active_record/associations/collection_association.rb +268 -186
- data/lib/active_record/associations/collection_proxy.rb +1003 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +81 -41
- data/lib/active_record/associations/has_many_through_association.rb +76 -55
- data/lib/active_record/associations/has_one_association.rb +51 -21
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +239 -155
- data/lib/active_record/associations/preloader/association.rb +97 -62
- data/lib/active_record/associations/preloader/collection_association.rb +2 -8
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +75 -33
- data/lib/active_record/associations/preloader.rb +111 -79
- data/lib/active_record/associations/singular_association.rb +35 -13
- data/lib/active_record/associations/through_association.rb +41 -19
- data/lib/active_record/associations.rb +727 -501
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +213 -0
- data/lib/active_record/attribute_assignment.rb +32 -162
- data/lib/active_record/attribute_decorators.rb +67 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +101 -61
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +7 -6
- data/lib/active_record/attribute_methods/read.rb +56 -117
- data/lib/active_record/attribute_methods/serialization.rb +43 -96
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
- data/lib/active_record/attribute_methods/write.rb +34 -45
- data/lib/active_record/attribute_methods.rb +333 -144
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set/builder.rb +108 -0
- data/lib/active_record/attribute_set.rb +108 -0
- data/lib/active_record/attributes.rb +265 -0
- data/lib/active_record/autosave_association.rb +285 -223
- data/lib/active_record/base.rb +95 -490
- data/lib/active_record/callbacks.rb +95 -61
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +28 -19
- data/lib/active_record/collection_cache_key.rb +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
- data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
- data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
- data/lib/active_record/connection_adapters/column.rb +30 -259
- data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
- data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
- data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +155 -0
- data/lib/active_record/core.rb +561 -0
- data/lib/active_record/counter_cache.rb +146 -105
- data/lib/active_record/dynamic_matchers.rb +101 -64
- data/lib/active_record/enum.rb +234 -0
- data/lib/active_record/errors.rb +153 -56
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +10 -6
- data/lib/active_record/fixture_set/file.rb +77 -0
- data/lib/active_record/fixtures.rb +355 -232
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +144 -79
- data/lib/active_record/integration.rb +66 -13
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +46 -0
- data/lib/active_record/locale/en.yml +9 -1
- data/lib/active_record/locking/optimistic.rb +77 -56
- data/lib/active_record/locking/pessimistic.rb +6 -6
- data/lib/active_record/log_subscriber.rb +53 -28
- data/lib/active_record/migration/command_recorder.rb +166 -33
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +792 -264
- data/lib/active_record/model_schema.rb +192 -130
- data/lib/active_record/nested_attributes.rb +238 -145
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +89 -0
- data/lib/active_record/persistence.rb +357 -157
- data/lib/active_record/query_cache.rb +22 -43
- data/lib/active_record/querying.rb +34 -23
- data/lib/active_record/railtie.rb +88 -48
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +5 -4
- data/lib/active_record/railties/databases.rake +170 -422
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -5
- data/lib/active_record/reflection.rb +715 -189
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/batches.rb +203 -50
- data/lib/active_record/relation/calculations.rb +203 -194
- data/lib/active_record/relation/delegation.rb +103 -25
- data/lib/active_record/relation/finder_methods.rb +457 -261
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +167 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +153 -48
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +1019 -194
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +46 -150
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/relation.rb +450 -245
- data/lib/active_record/result.rb +104 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +120 -94
- data/lib/active_record/schema.rb +28 -18
- data/lib/active_record/schema_dumper.rb +141 -74
- data/lib/active_record/schema_migration.rb +50 -0
- data/lib/active_record/scoping/default.rb +64 -57
- data/lib/active_record/scoping/named.rb +93 -108
- data/lib/active_record/scoping.rb +73 -121
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +7 -5
- data/lib/active_record/statement_cache.rb +113 -0
- data/lib/active_record/store.rb +173 -15
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +68 -0
- data/lib/active_record/tasks/database_tasks.rb +313 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
- data/lib/active_record/timestamp.rb +42 -24
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +233 -105
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +7 -0
- data/lib/active_record/type/date_time.rb +7 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/internal/abstract_json.rb +29 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +63 -0
- data/lib/active_record/type/time.rb +20 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type.rb +72 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +33 -18
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +66 -0
- data/lib/active_record/validations/uniqueness.rb +128 -68
- data/lib/active_record/validations.rb +48 -40
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +71 -47
- data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
- data/lib/rails/generators/active_record/migration.rb +18 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +188 -134
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/serializers/xml_serializer.rb +0 -203
- data/lib/active_record/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,8 +1,6 @@
|
|
1
1
|
require 'active_support/core_ext/hash/except'
|
2
2
|
require 'active_support/core_ext/object/try'
|
3
|
-
require 'active_support/core_ext/object/blank'
|
4
3
|
require 'active_support/core_ext/hash/indifferent_access'
|
5
|
-
require 'active_support/core_ext/class/attribute'
|
6
4
|
|
7
5
|
module ActiveRecord
|
8
6
|
module NestedAttributes #:nodoc:
|
@@ -12,17 +10,17 @@ module ActiveRecord
|
|
12
10
|
extend ActiveSupport::Concern
|
13
11
|
|
14
12
|
included do
|
15
|
-
class_attribute :nested_attributes_options, :
|
13
|
+
class_attribute :nested_attributes_options, instance_writer: false
|
16
14
|
self.nested_attributes_options = {}
|
17
15
|
end
|
18
16
|
|
19
17
|
# = Active Record Nested Attributes
|
20
18
|
#
|
21
19
|
# Nested attributes allow you to save attributes on associated records
|
22
|
-
# through the parent. By default nested attribute updating is turned off
|
23
|
-
# you can enable it using the accepts_nested_attributes_for class
|
24
|
-
# When you enable nested attributes an attribute writer is
|
25
|
-
# the model.
|
20
|
+
# through the parent. By default nested attribute updating is turned off
|
21
|
+
# and you can enable it using the accepts_nested_attributes_for class
|
22
|
+
# method. When you enable nested attributes an attribute writer is
|
23
|
+
# defined on the model.
|
26
24
|
#
|
27
25
|
# The attribute writer is named after the association, which means that
|
28
26
|
# in the following example, two new methods are added to your model:
|
@@ -52,15 +50,15 @@ module ActiveRecord
|
|
52
50
|
# Enabling nested attributes on a one-to-one association allows you to
|
53
51
|
# create the member and avatar in one go:
|
54
52
|
#
|
55
|
-
# params = { :
|
53
|
+
# params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
|
56
54
|
# member = Member.create(params[:member])
|
57
55
|
# member.avatar.id # => 2
|
58
56
|
# member.avatar.icon # => 'smiling'
|
59
57
|
#
|
60
58
|
# It also allows you to update the avatar through the member:
|
61
59
|
#
|
62
|
-
# params = { :
|
63
|
-
# member.
|
60
|
+
# params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
|
61
|
+
# member.update params[:member]
|
64
62
|
# member.avatar.icon # => 'sad'
|
65
63
|
#
|
66
64
|
# By default you will only be able to set and update attributes on the
|
@@ -70,19 +68,22 @@ module ActiveRecord
|
|
70
68
|
#
|
71
69
|
# class Member < ActiveRecord::Base
|
72
70
|
# has_one :avatar
|
73
|
-
# accepts_nested_attributes_for :avatar, :
|
71
|
+
# accepts_nested_attributes_for :avatar, allow_destroy: true
|
74
72
|
# end
|
75
73
|
#
|
76
74
|
# Now, when you add the <tt>_destroy</tt> key to the attributes hash, with a
|
77
75
|
# value that evaluates to +true+, you will destroy the associated model:
|
78
76
|
#
|
79
|
-
# member.avatar_attributes = { :
|
77
|
+
# member.avatar_attributes = { id: '2', _destroy: '1' }
|
80
78
|
# member.avatar.marked_for_destruction? # => true
|
81
79
|
# member.save
|
82
80
|
# member.reload.avatar # => nil
|
83
81
|
#
|
84
82
|
# Note that the model will _not_ be destroyed until the parent is saved.
|
85
83
|
#
|
84
|
+
# Also note that the model will not be destroyed unless you also specify
|
85
|
+
# its id in the updated hash.
|
86
|
+
#
|
86
87
|
# === One-to-many
|
87
88
|
#
|
88
89
|
# Consider a member that has a number of posts:
|
@@ -100,54 +101,54 @@ module ActiveRecord
|
|
100
101
|
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
|
101
102
|
# that evaluates to +true+.
|
102
103
|
#
|
103
|
-
# params = { :
|
104
|
-
# :
|
105
|
-
# { :
|
106
|
-
# { :
|
107
|
-
# { :
|
104
|
+
# params = { member: {
|
105
|
+
# name: 'joe', posts_attributes: [
|
106
|
+
# { title: 'Kari, the awesome Ruby documentation browser!' },
|
107
|
+
# { title: 'The egalitarian assumption of the modern citizen' },
|
108
|
+
# { title: '', _destroy: '1' } # this will be ignored
|
108
109
|
# ]
|
109
110
|
# }}
|
110
111
|
#
|
111
|
-
# member = Member.create(params[
|
112
|
+
# member = Member.create(params[:member])
|
112
113
|
# member.posts.length # => 2
|
113
114
|
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
|
114
115
|
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
|
115
116
|
#
|
116
|
-
# You may also set a
|
117
|
+
# You may also set a +:reject_if+ proc to silently ignore any new record
|
117
118
|
# hashes if they fail to pass your criteria. For example, the previous
|
118
119
|
# example could be rewritten as:
|
119
120
|
#
|
120
121
|
# class Member < ActiveRecord::Base
|
121
122
|
# has_many :posts
|
122
|
-
# accepts_nested_attributes_for :posts, :
|
123
|
+
# accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
|
123
124
|
# end
|
124
125
|
#
|
125
|
-
# params = { :
|
126
|
-
# :
|
127
|
-
# { :
|
128
|
-
# { :
|
129
|
-
# { :
|
126
|
+
# params = { member: {
|
127
|
+
# name: 'joe', posts_attributes: [
|
128
|
+
# { title: 'Kari, the awesome Ruby documentation browser!' },
|
129
|
+
# { title: 'The egalitarian assumption of the modern citizen' },
|
130
|
+
# { title: '' } # this will be ignored because of the :reject_if proc
|
130
131
|
# ]
|
131
132
|
# }}
|
132
133
|
#
|
133
|
-
# member = Member.create(params[
|
134
|
+
# member = Member.create(params[:member])
|
134
135
|
# member.posts.length # => 2
|
135
136
|
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
|
136
137
|
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
|
137
138
|
#
|
138
|
-
# Alternatively,
|
139
|
+
# Alternatively, +:reject_if+ also accepts a symbol for using methods:
|
139
140
|
#
|
140
141
|
# class Member < ActiveRecord::Base
|
141
142
|
# has_many :posts
|
142
|
-
# accepts_nested_attributes_for :posts, :
|
143
|
+
# accepts_nested_attributes_for :posts, reject_if: :new_record?
|
143
144
|
# end
|
144
145
|
#
|
145
146
|
# class Member < ActiveRecord::Base
|
146
147
|
# has_many :posts
|
147
|
-
# accepts_nested_attributes_for :posts, :
|
148
|
+
# accepts_nested_attributes_for :posts, reject_if: :reject_posts
|
148
149
|
#
|
149
|
-
# def reject_posts(
|
150
|
-
#
|
150
|
+
# def reject_posts(attributes)
|
151
|
+
# attributes['title'].blank?
|
151
152
|
# end
|
152
153
|
# end
|
153
154
|
#
|
@@ -155,16 +156,21 @@ module ActiveRecord
|
|
155
156
|
# associated record, the matching record will be modified:
|
156
157
|
#
|
157
158
|
# member.attributes = {
|
158
|
-
# :
|
159
|
-
# :
|
160
|
-
# { :
|
161
|
-
# { :
|
159
|
+
# name: 'Joe',
|
160
|
+
# posts_attributes: [
|
161
|
+
# { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
|
162
|
+
# { id: 2, title: '[UPDATED] other post' }
|
162
163
|
# ]
|
163
164
|
# }
|
164
165
|
#
|
165
166
|
# member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
|
166
167
|
# member.posts.second.title # => '[UPDATED] other post'
|
167
168
|
#
|
169
|
+
# However, the above applies if the parent model is being updated as well.
|
170
|
+
# For example, If you wanted to create a +member+ named _joe_ and wanted to
|
171
|
+
# update the +posts+ at the same time, that would give an
|
172
|
+
# ActiveRecord::RecordNotFound error.
|
173
|
+
#
|
168
174
|
# By default the associated records are protected from being destroyed. If
|
169
175
|
# you want to destroy any of the associated records through the attributes
|
170
176
|
# hash, you have to enable it first using the <tt>:allow_destroy</tt>
|
@@ -173,14 +179,14 @@ module ActiveRecord
|
|
173
179
|
#
|
174
180
|
# class Member < ActiveRecord::Base
|
175
181
|
# has_many :posts
|
176
|
-
# accepts_nested_attributes_for :posts, :
|
182
|
+
# accepts_nested_attributes_for :posts, allow_destroy: true
|
177
183
|
# end
|
178
184
|
#
|
179
|
-
# params = { :
|
180
|
-
# :
|
185
|
+
# params = { member: {
|
186
|
+
# posts_attributes: [{ id: '2', _destroy: '1' }]
|
181
187
|
# }}
|
182
188
|
#
|
183
|
-
# member.attributes = params[
|
189
|
+
# member.attributes = params[:member]
|
184
190
|
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
|
185
191
|
# member.posts.length # => 2
|
186
192
|
# member.save
|
@@ -189,66 +195,81 @@ module ActiveRecord
|
|
189
195
|
# Nested attributes for an associated collection can also be passed in
|
190
196
|
# the form of a hash of hashes instead of an array of hashes:
|
191
197
|
#
|
192
|
-
# Member.create(
|
193
|
-
#
|
194
|
-
#
|
198
|
+
# Member.create(
|
199
|
+
# name: 'joe',
|
200
|
+
# posts_attributes: {
|
201
|
+
# first: { title: 'Foo' },
|
202
|
+
# second: { title: 'Bar' }
|
203
|
+
# }
|
204
|
+
# )
|
195
205
|
#
|
196
206
|
# has the same effect as
|
197
207
|
#
|
198
|
-
# Member.create(
|
199
|
-
#
|
200
|
-
#
|
208
|
+
# Member.create(
|
209
|
+
# name: 'joe',
|
210
|
+
# posts_attributes: [
|
211
|
+
# { title: 'Foo' },
|
212
|
+
# { title: 'Bar' }
|
213
|
+
# ]
|
214
|
+
# )
|
201
215
|
#
|
202
216
|
# The keys of the hash which is the value for +:posts_attributes+ are
|
203
217
|
# ignored in this case.
|
204
|
-
# However, it is not allowed to use
|
218
|
+
# However, it is not allowed to use <tt>'id'</tt> or <tt>:id</tt> for one of
|
205
219
|
# such keys, otherwise the hash will be wrapped in an array and
|
206
220
|
# interpreted as an attribute hash for a single post.
|
207
221
|
#
|
208
222
|
# Passing attributes for an associated collection in the form of a hash
|
209
223
|
# of hashes can be used with hashes generated from HTTP/HTML parameters,
|
210
|
-
# where there
|
224
|
+
# where there may be no natural way to submit an array of hashes.
|
211
225
|
#
|
212
226
|
# === Saving
|
213
227
|
#
|
214
228
|
# All changes to models, including the destruction of those marked for
|
215
229
|
# destruction, are saved and destroyed automatically and atomically when
|
216
230
|
# the parent model is saved. This happens inside the transaction initiated
|
217
|
-
# by the
|
218
|
-
#
|
219
|
-
# === Using with attr_accessible
|
220
|
-
#
|
221
|
-
# The use of <tt>attr_accessible</tt> can interfere with nested attributes
|
222
|
-
# if you're not careful. For example, if the <tt>Member</tt> model above
|
223
|
-
# was using <tt>attr_accessible</tt> like this:
|
224
|
-
#
|
225
|
-
# attr_accessible :name
|
226
|
-
#
|
227
|
-
# You would need to modify it to look like this:
|
228
|
-
#
|
229
|
-
# attr_accessible :name, :posts_attributes
|
231
|
+
# by the parent's save method. See ActiveRecord::AutosaveAssociation.
|
230
232
|
#
|
231
233
|
# === Validating the presence of a parent model
|
232
234
|
#
|
233
235
|
# If you want to validate that a child record is associated with a parent
|
234
|
-
# record, you can use
|
235
|
-
#
|
236
|
+
# record, you can use the +validates_presence_of+ method and the +:inverse_of+
|
237
|
+
# key as this example illustrates:
|
236
238
|
#
|
237
239
|
# class Member < ActiveRecord::Base
|
238
|
-
# has_many :posts, :
|
240
|
+
# has_many :posts, inverse_of: :member
|
239
241
|
# accepts_nested_attributes_for :posts
|
240
242
|
# end
|
241
243
|
#
|
242
244
|
# class Post < ActiveRecord::Base
|
243
|
-
# belongs_to :member, :
|
245
|
+
# belongs_to :member, inverse_of: :posts
|
244
246
|
# validates_presence_of :member
|
245
247
|
# end
|
248
|
+
#
|
249
|
+
# Note that if you do not specify the +:inverse_of+ option, then
|
250
|
+
# Active Record will try to automatically guess the inverse association
|
251
|
+
# based on heuristics.
|
252
|
+
#
|
253
|
+
# For one-to-one nested associations, if you build the new (in-memory)
|
254
|
+
# child object yourself before assignment, then this module will not
|
255
|
+
# overwrite it, e.g.:
|
256
|
+
#
|
257
|
+
# class Member < ActiveRecord::Base
|
258
|
+
# has_one :avatar
|
259
|
+
# accepts_nested_attributes_for :avatar
|
260
|
+
#
|
261
|
+
# def avatar
|
262
|
+
# super || build_avatar(width: 200)
|
263
|
+
# end
|
264
|
+
# end
|
265
|
+
#
|
266
|
+
# member = Member.new
|
267
|
+
# member.avatar_attributes = {icon: 'sad'}
|
268
|
+
# member.avatar.width # => 200
|
246
269
|
module ClassMethods
|
247
270
|
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
|
248
271
|
|
249
|
-
# Defines an attributes writer for the specified association(s).
|
250
|
-
# are using <tt>attr_protected</tt> or <tt>attr_accessible</tt>, then you
|
251
|
-
# will need to add the attribute writer to the allowed list.
|
272
|
+
# Defines an attributes writer for the specified association(s).
|
252
273
|
#
|
253
274
|
# Supported options:
|
254
275
|
# [:allow_destroy]
|
@@ -259,31 +280,42 @@ module ActiveRecord
|
|
259
280
|
# Allows you to specify a Proc or a Symbol pointing to a method
|
260
281
|
# that checks whether a record should be built for a certain attribute
|
261
282
|
# hash. The hash is passed to the supplied Proc or the method
|
262
|
-
# and it should return either +true+ or +false+. When no
|
283
|
+
# and it should return either +true+ or +false+. When no +:reject_if+
|
263
284
|
# is specified, a record will be built for all attribute hashes that
|
264
285
|
# do not have a <tt>_destroy</tt> value that evaluates to true.
|
265
286
|
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc
|
266
287
|
# that will reject a record where all the attributes are blank excluding
|
267
|
-
# any value for _destroy
|
288
|
+
# any value for +_destroy+.
|
268
289
|
# [:limit]
|
269
|
-
# Allows you to specify the maximum number of
|
270
|
-
# can be processed with the nested attributes.
|
271
|
-
#
|
272
|
-
#
|
273
|
-
#
|
290
|
+
# Allows you to specify the maximum number of associated records that
|
291
|
+
# can be processed with the nested attributes. Limit also can be specified
|
292
|
+
# as a Proc or a Symbol pointing to a method that should return a number.
|
293
|
+
# If the size of the nested attributes array exceeds the specified limit,
|
294
|
+
# NestedAttributes::TooManyRecords exception is raised. If omitted, any
|
295
|
+
# number of associations can be processed.
|
296
|
+
# Note that the +:limit+ option is only applicable to one-to-many
|
297
|
+
# associations.
|
274
298
|
# [:update_only]
|
275
|
-
#
|
276
|
-
#
|
277
|
-
#
|
278
|
-
#
|
299
|
+
# For a one-to-one association, this option allows you to specify how
|
300
|
+
# nested attributes are going to be used when an associated record already
|
301
|
+
# exists. In general, an existing record may either be updated with the
|
302
|
+
# new set of attribute values or be replaced by a wholly new record
|
303
|
+
# containing those values. By default the +:update_only+ option is +false+
|
304
|
+
# and the nested attributes are used to update the existing record only
|
305
|
+
# if they include the record's <tt>:id</tt> value. Otherwise a new
|
306
|
+
# record will be instantiated and used to replace the existing one.
|
307
|
+
# However if the +:update_only+ option is +true+, the nested attributes
|
308
|
+
# are used to update the record's attributes always, regardless of
|
309
|
+
# whether the <tt>:id</tt> is present. The option is ignored for collection
|
310
|
+
# associations.
|
279
311
|
#
|
280
312
|
# Examples:
|
281
313
|
# # creates avatar_attributes=
|
282
|
-
# accepts_nested_attributes_for :avatar, :
|
314
|
+
# accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
|
283
315
|
# # creates avatar_attributes=
|
284
|
-
# accepts_nested_attributes_for :avatar, :
|
316
|
+
# accepts_nested_attributes_for :avatar, reject_if: :all_blank
|
285
317
|
# # creates avatar_attributes= and posts_attributes=
|
286
|
-
# accepts_nested_attributes_for :avatar, :posts, :
|
318
|
+
# accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
|
287
319
|
def accepts_nested_attributes_for(*attr_names)
|
288
320
|
options = { :allow_destroy => false, :update_only => false }
|
289
321
|
options.update(attr_names.extract_options!)
|
@@ -291,33 +323,45 @@ module ActiveRecord
|
|
291
323
|
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
|
292
324
|
|
293
325
|
attr_names.each do |association_name|
|
294
|
-
if reflection =
|
295
|
-
reflection.
|
296
|
-
|
326
|
+
if reflection = _reflect_on_association(association_name)
|
327
|
+
reflection.autosave = true
|
328
|
+
define_autosave_validation_callbacks(reflection)
|
297
329
|
|
298
330
|
nested_attributes_options = self.nested_attributes_options.dup
|
299
331
|
nested_attributes_options[association_name.to_sym] = options
|
300
332
|
self.nested_attributes_options = nested_attributes_options
|
301
333
|
|
302
334
|
type = (reflection.collection? ? :collection : :one_to_one)
|
303
|
-
|
304
|
-
# remove_possible_method :pirate_attributes=
|
305
|
-
#
|
306
|
-
# def pirate_attributes=(attributes)
|
307
|
-
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options)
|
308
|
-
# end
|
309
|
-
class_eval <<-eoruby, __FILE__, __LINE__ + 1
|
310
|
-
remove_possible_method(:#{association_name}_attributes=)
|
311
|
-
|
312
|
-
def #{association_name}_attributes=(attributes)
|
313
|
-
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options)
|
314
|
-
end
|
315
|
-
eoruby
|
335
|
+
generate_association_writer(association_name, type)
|
316
336
|
else
|
317
337
|
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
|
318
338
|
end
|
319
339
|
end
|
320
340
|
end
|
341
|
+
|
342
|
+
private
|
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
|
321
365
|
end
|
322
366
|
|
323
367
|
# Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
|
@@ -337,31 +381,45 @@ module ActiveRecord
|
|
337
381
|
|
338
382
|
# Assigns the given attributes to the association.
|
339
383
|
#
|
340
|
-
# If
|
341
|
-
#
|
342
|
-
#
|
343
|
-
#
|
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.
|
344
391
|
#
|
345
392
|
# If the given attributes include a matching <tt>:id</tt> attribute, or
|
346
393
|
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
|
347
394
|
# then the existing record will be marked for destruction.
|
348
|
-
def assign_nested_attributes_for_one_to_one_association(association_name, attributes
|
395
|
+
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
|
349
396
|
options = self.nested_attributes_options[association_name]
|
397
|
+
if attributes.respond_to?(:permitted?)
|
398
|
+
attributes = attributes.to_h
|
399
|
+
end
|
350
400
|
attributes = attributes.with_indifferent_access
|
401
|
+
existing_record = send(association_name)
|
351
402
|
|
352
|
-
if (options[:update_only] || !attributes['id'].blank?) &&
|
353
|
-
(options[:update_only] ||
|
354
|
-
assign_to_or_mark_for_destruction(
|
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)
|
355
406
|
|
356
|
-
elsif attributes['id'].present?
|
357
|
-
raise_nested_attributes_record_not_found(association_name, attributes['id'])
|
407
|
+
elsif attributes['id'].present?
|
408
|
+
raise_nested_attributes_record_not_found!(association_name, attributes['id'])
|
358
409
|
|
359
410
|
elsif !reject_new_record?(association_name, attributes)
|
360
|
-
|
361
|
-
|
362
|
-
|
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)
|
363
416
|
else
|
364
|
-
|
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
|
365
423
|
end
|
366
424
|
end
|
367
425
|
end
|
@@ -377,37 +435,38 @@ module ActiveRecord
|
|
377
435
|
# For example:
|
378
436
|
#
|
379
437
|
# assign_nested_attributes_for_collection_association(:people, {
|
380
|
-
# '1' => { :
|
381
|
-
# '2' => { :
|
382
|
-
# '3' => { :
|
438
|
+
# '1' => { id: '1', name: 'Peter' },
|
439
|
+
# '2' => { name: 'John' },
|
440
|
+
# '3' => { id: '2', _destroy: true }
|
383
441
|
# })
|
384
442
|
#
|
385
443
|
# Will update the name of the Person with ID 1, build a new associated
|
386
|
-
# person with the name
|
444
|
+
# person with the name 'John', and mark the associated Person with ID 2
|
387
445
|
# for destruction.
|
388
446
|
#
|
389
447
|
# Also accepts an Array of attribute hashes:
|
390
448
|
#
|
391
449
|
# assign_nested_attributes_for_collection_association(:people, [
|
392
|
-
# { :
|
393
|
-
# { :
|
394
|
-
# { :
|
450
|
+
# { id: '1', name: 'Peter' },
|
451
|
+
# { name: 'John' },
|
452
|
+
# { id: '2', _destroy: true }
|
395
453
|
# ])
|
396
|
-
def assign_nested_attributes_for_collection_association(association_name, attributes_collection
|
454
|
+
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
|
397
455
|
options = self.nested_attributes_options[association_name]
|
456
|
+
if attributes_collection.respond_to?(:permitted?)
|
457
|
+
attributes_collection = attributes_collection.to_h
|
458
|
+
end
|
398
459
|
|
399
460
|
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
400
461
|
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
401
462
|
end
|
402
463
|
|
403
|
-
|
404
|
-
raise TooManyRecords, "Maximum #{options[:limit]} records are allowed. Got #{attributes_collection.size} records instead."
|
405
|
-
end
|
464
|
+
check_record_limit!(options[:limit], attributes_collection)
|
406
465
|
|
407
466
|
if attributes_collection.is_a? Hash
|
408
467
|
keys = attributes_collection.keys
|
409
468
|
attributes_collection = if keys.include?('id') || keys.include?(:id)
|
410
|
-
|
469
|
+
[attributes_collection]
|
411
470
|
else
|
412
471
|
attributes_collection.values
|
413
472
|
end
|
@@ -419,62 +478,89 @@ module ActiveRecord
|
|
419
478
|
association.target
|
420
479
|
else
|
421
480
|
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
|
422
|
-
attribute_ids.empty? ? [] : association.
|
481
|
+
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
|
423
482
|
end
|
424
483
|
|
425
484
|
attributes_collection.each do |attributes|
|
485
|
+
if attributes.respond_to?(:permitted?)
|
486
|
+
attributes = attributes.to_h
|
487
|
+
end
|
426
488
|
attributes = attributes.with_indifferent_access
|
427
489
|
|
428
490
|
if attributes['id'].blank?
|
429
491
|
unless reject_new_record?(association_name, attributes)
|
430
|
-
association.build(attributes.except(*
|
492
|
+
association.build(attributes.except(*UNASSIGNABLE_KEYS))
|
431
493
|
end
|
432
494
|
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
|
433
|
-
unless
|
495
|
+
unless call_reject_if(association_name, attributes)
|
434
496
|
# Make sure we are operating on the actual object which is in the association's
|
435
497
|
# proxy_target array (either by finding it, or adding it if not found)
|
436
|
-
|
437
|
-
|
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 }
|
438
500
|
if target_record
|
439
501
|
existing_record = target_record
|
440
502
|
else
|
441
|
-
association.add_to_target(existing_record)
|
503
|
+
association.add_to_target(existing_record, :skip_callbacks)
|
442
504
|
end
|
443
505
|
|
506
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
444
507
|
end
|
508
|
+
else
|
509
|
+
raise_nested_attributes_record_not_found!(association_name, attributes['id'])
|
510
|
+
end
|
511
|
+
end
|
512
|
+
end
|
445
513
|
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
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
|
451
527
|
else
|
452
|
-
|
528
|
+
limit
|
529
|
+
end
|
530
|
+
|
531
|
+
if limit && attributes_collection.size > limit
|
532
|
+
raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
|
453
533
|
end
|
454
534
|
end
|
455
535
|
end
|
456
536
|
|
457
537
|
# Updates a record with the +attributes+ or marks it for destruction if
|
458
538
|
# +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
|
459
|
-
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy
|
460
|
-
record.assign_attributes(attributes.except(*
|
539
|
+
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
|
540
|
+
record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
|
461
541
|
record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
|
462
542
|
end
|
463
543
|
|
464
544
|
# Determines if a hash contains a truthy _destroy key.
|
465
545
|
def has_destroy_flag?(hash)
|
466
|
-
|
546
|
+
Type::Boolean.new.cast(hash['_destroy'])
|
467
547
|
end
|
468
548
|
|
469
|
-
# Determines if a new record should be
|
549
|
+
# Determines if a new record should be rejected by checking
|
470
550
|
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
|
471
551
|
# association and evaluates to +true+.
|
472
552
|
def reject_new_record?(association_name, attributes)
|
473
|
-
|
553
|
+
will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
|
474
554
|
end
|
475
555
|
|
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.
|
476
561
|
def call_reject_if(association_name, attributes)
|
477
|
-
return false if
|
562
|
+
return false if will_be_destroyed?(association_name, attributes)
|
563
|
+
|
478
564
|
case callback = self.nested_attributes_options[association_name][:reject_if]
|
479
565
|
when Symbol
|
480
566
|
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
|
@@ -483,12 +569,19 @@ module ActiveRecord
|
|
483
569
|
end
|
484
570
|
end
|
485
571
|
|
486
|
-
|
487
|
-
|
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
|
576
|
+
|
577
|
+
def allow_destroy?(association_name)
|
578
|
+
self.nested_attributes_options[association_name][:allow_destroy]
|
488
579
|
end
|
489
580
|
|
490
|
-
def
|
491
|
-
|
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)
|
492
585
|
end
|
493
586
|
end
|
494
587
|
end
|