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