activerecord 3.2.22.5 → 4.0.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 +4 -4
- data/CHANGELOG.md +1024 -543
- data/MIT-LICENSE +1 -1
- data/README.rdoc +20 -29
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +55 -44
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/associations.rb +204 -276
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +30 -35
- data/lib/active_record/associations/association_scope.rb +40 -40
- data/lib/active_record/associations/belongs_to_association.rb +15 -2
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +35 -57
- data/lib/active_record/associations/builder/collection_association.rb +54 -40
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +13 -50
- data/lib/active_record/associations/builder/singular_association.rb +13 -13
- data/lib/active_record/associations/collection_association.rb +92 -88
- data/lib/active_record/associations/collection_proxy.rb +913 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
- data/lib/active_record/associations/has_many_association.rb +35 -9
- data/lib/active_record/associations/has_many_through_association.rb +24 -14
- data/lib/active_record/associations/has_one_association.rb +33 -13
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader.rb +14 -17
- data/lib/active_record/associations/preloader/association.rb +29 -33
- data/lib/active_record/associations/preloader/collection_association.rb +1 -1
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/through_association.rb +13 -17
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +133 -153
- data/lib/active_record/attribute_methods.rb +196 -93
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +31 -28
- data/lib/active_record/attribute_methods/primary_key.rb +38 -30
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +62 -91
- data/lib/active_record/attribute_methods/serialization.rb +97 -66
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
- data/lib/active_record/attribute_methods/write.rb +32 -39
- data/lib/active_record/autosave_association.rb +56 -70
- data/lib/active_record/base.rb +53 -450
- data/lib/active_record/callbacks.rb +53 -18
- data/lib/active_record/coders/yaml_column.rb +11 -9
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
- data/lib/active_record/connection_adapters/column.rb +46 -24
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +428 -0
- data/lib/active_record/counter_cache.rb +106 -108
- data/lib/active_record/dynamic_matchers.rb +110 -63
- data/lib/active_record/errors.rb +25 -8
- data/lib/active_record/explain.rb +8 -58
- data/lib/active_record/explain_subscriber.rb +6 -3
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +146 -148
- data/lib/active_record/inheritance.rb +77 -59
- data/lib/active_record/integration.rb +5 -5
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +38 -42
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration.rb +318 -153
- data/lib/active_record/migration/command_recorder.rb +90 -31
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +69 -92
- data/lib/active_record/nested_attributes.rb +113 -148
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +188 -97
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +91 -36
- data/lib/active_record/railties/console_sandbox.rb +0 -2
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +90 -309
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +7 -3
- data/lib/active_record/reflection.rb +72 -56
- data/lib/active_record/relation.rb +241 -157
- data/lib/active_record/relation/batches.rb +25 -22
- data/lib/active_record/relation/calculations.rb +143 -121
- data/lib/active_record/relation/delegation.rb +96 -18
- data/lib/active_record/relation/finder_methods.rb +117 -183
- data/lib/active_record/relation/merger.rb +133 -0
- data/lib/active_record/relation/predicate_builder.rb +90 -42
- data/lib/active_record/relation/query_methods.rb +666 -136
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/result.rb +33 -6
- data/lib/active_record/sanitization.rb +24 -50
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +31 -39
- data/lib/active_record/schema_migration.rb +36 -0
- data/lib/active_record/scoping.rb +0 -124
- data/lib/active_record/scoping/default.rb +48 -45
- data/lib/active_record/scoping/named.rb +74 -103
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/store.rb +119 -15
- data/lib/active_record/tasks/database_tasks.rb +158 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/test_case.rb +61 -38
- data/lib/active_record/timestamp.rb +8 -9
- data/lib/active_record/transactions.rb +65 -51
- data/lib/active_record/validations.rb +17 -15
- data/lib/active_record/validations/associated.rb +20 -14
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +93 -52
- data/lib/active_record/version.rb +4 -4
- data/lib/rails/generators/active_record.rb +3 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- metadata +53 -46
- 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/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/session_store.rb +0 -360
- data/lib/rails/generators/active_record/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,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,23 +90,22 @@ module ActiveRecord
|
|
92
90
|
# accepts_nested_attributes_for :posts
|
93
91
|
# end
|
94
92
|
#
|
95
|
-
# You can now set or update attributes on
|
96
|
-
#
|
97
|
-
# with an array of hashes of post attributes as a value.
|
93
|
+
# You can now set or update attributes on an associated post model through
|
94
|
+
# the attribute hash.
|
98
95
|
#
|
99
96
|
# For each hash that does _not_ have an <tt>id</tt> key a new record will
|
100
97
|
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
|
101
98
|
# that evaluates to +true+.
|
102
99
|
#
|
103
|
-
# params = { :
|
104
|
-
# :
|
105
|
-
# { :
|
106
|
-
# { :
|
107
|
-
# { :
|
100
|
+
# params = { member: {
|
101
|
+
# name: 'joe', posts_attributes: [
|
102
|
+
# { title: 'Kari, the awesome Ruby documentation browser!' },
|
103
|
+
# { title: 'The egalitarian assumption of the modern citizen' },
|
104
|
+
# { title: '', _destroy: '1' } # this will be ignored
|
108
105
|
# ]
|
109
106
|
# }}
|
110
107
|
#
|
111
|
-
# member = Member.create(params[
|
108
|
+
# member = Member.create(params[:member])
|
112
109
|
# member.posts.length # => 2
|
113
110
|
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
|
114
111
|
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
|
@@ -117,48 +114,48 @@ module ActiveRecord
|
|
117
114
|
# hashes if they fail to pass your criteria. For example, the previous
|
118
115
|
# example could be rewritten as:
|
119
116
|
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
123
|
-
#
|
117
|
+
# class Member < ActiveRecord::Base
|
118
|
+
# has_many :posts
|
119
|
+
# accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
|
120
|
+
# end
|
124
121
|
#
|
125
|
-
# params = { :
|
126
|
-
# :
|
127
|
-
# { :
|
128
|
-
# { :
|
129
|
-
# { :
|
122
|
+
# params = { member: {
|
123
|
+
# name: 'joe', posts_attributes: [
|
124
|
+
# { title: 'Kari, the awesome Ruby documentation browser!' },
|
125
|
+
# { title: 'The egalitarian assumption of the modern citizen' },
|
126
|
+
# { title: '' } # this will be ignored because of the :reject_if proc
|
130
127
|
# ]
|
131
128
|
# }}
|
132
129
|
#
|
133
|
-
# member = Member.create(params[
|
130
|
+
# member = Member.create(params[:member])
|
134
131
|
# member.posts.length # => 2
|
135
132
|
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
|
136
133
|
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
|
137
134
|
#
|
138
135
|
# Alternatively, :reject_if also accepts a symbol for using methods:
|
139
136
|
#
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
#
|
137
|
+
# class Member < ActiveRecord::Base
|
138
|
+
# has_many :posts
|
139
|
+
# accepts_nested_attributes_for :posts, reject_if: :new_record?
|
140
|
+
# end
|
144
141
|
#
|
145
|
-
#
|
146
|
-
#
|
147
|
-
#
|
142
|
+
# class Member < ActiveRecord::Base
|
143
|
+
# has_many :posts
|
144
|
+
# accepts_nested_attributes_for :posts, reject_if: :reject_posts
|
148
145
|
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
152
|
-
#
|
146
|
+
# def reject_posts(attributed)
|
147
|
+
# attributed['title'].blank?
|
148
|
+
# end
|
149
|
+
# end
|
153
150
|
#
|
154
151
|
# If the hash contains an <tt>id</tt> key that matches an already
|
155
152
|
# associated record, the matching record will be modified:
|
156
153
|
#
|
157
154
|
# member.attributes = {
|
158
|
-
# :
|
159
|
-
# :
|
160
|
-
# { :
|
161
|
-
# { :
|
155
|
+
# name: 'Joe',
|
156
|
+
# posts_attributes: [
|
157
|
+
# { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
|
158
|
+
# { id: 2, title: '[UPDATED] other post' }
|
162
159
|
# ]
|
163
160
|
# }
|
164
161
|
#
|
@@ -173,42 +170,19 @@ module ActiveRecord
|
|
173
170
|
#
|
174
171
|
# class Member < ActiveRecord::Base
|
175
172
|
# has_many :posts
|
176
|
-
# accepts_nested_attributes_for :posts, :
|
173
|
+
# accepts_nested_attributes_for :posts, allow_destroy: true
|
177
174
|
# end
|
178
175
|
#
|
179
|
-
# params = { :
|
180
|
-
# :
|
176
|
+
# params = { member: {
|
177
|
+
# posts_attributes: [{ id: '2', _destroy: '1' }]
|
181
178
|
# }}
|
182
179
|
#
|
183
|
-
# member.attributes = params[
|
180
|
+
# member.attributes = params[:member]
|
184
181
|
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
|
185
182
|
# member.posts.length # => 2
|
186
183
|
# member.save
|
187
184
|
# member.reload.posts.length # => 1
|
188
185
|
#
|
189
|
-
# Nested attributes for an associated collection can also be passed in
|
190
|
-
# the form of a hash of hashes instead of an array of hashes:
|
191
|
-
#
|
192
|
-
# Member.create(:name => 'joe',
|
193
|
-
# :posts_attributes => { :first => { :title => 'Foo' },
|
194
|
-
# :second => { :title => 'Bar' } })
|
195
|
-
#
|
196
|
-
# has the same effect as
|
197
|
-
#
|
198
|
-
# Member.create(:name => 'joe',
|
199
|
-
# :posts_attributes => [ { :title => 'Foo' },
|
200
|
-
# { :title => 'Bar' } ])
|
201
|
-
#
|
202
|
-
# The keys of the hash which is the value for +:posts_attributes+ are
|
203
|
-
# ignored in this case.
|
204
|
-
# However, it is not allowed to use +'id'+ or +:id+ for one of
|
205
|
-
# such keys, otherwise the hash will be wrapped in an array and
|
206
|
-
# interpreted as an attribute hash for a single post.
|
207
|
-
#
|
208
|
-
# Passing attributes for an associated collection in the form of a hash
|
209
|
-
# of hashes can be used with hashes generated from HTTP/HTML parameters,
|
210
|
-
# where there maybe no natural way to submit an array of hashes.
|
211
|
-
#
|
212
186
|
# === Saving
|
213
187
|
#
|
214
188
|
# All changes to models, including the destruction of those marked for
|
@@ -216,18 +190,6 @@ module ActiveRecord
|
|
216
190
|
# the parent model is saved. This happens inside the transaction initiated
|
217
191
|
# by the parents save method. See ActiveRecord::AutosaveAssociation.
|
218
192
|
#
|
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
|
230
|
-
#
|
231
193
|
# === Validating the presence of a parent model
|
232
194
|
#
|
233
195
|
# If you want to validate that a child record is associated with a parent
|
@@ -235,20 +197,18 @@ module ActiveRecord
|
|
235
197
|
# <tt>inverse_of</tt> as this example illustrates:
|
236
198
|
#
|
237
199
|
# class Member < ActiveRecord::Base
|
238
|
-
# has_many :posts, :
|
200
|
+
# has_many :posts, inverse_of: :member
|
239
201
|
# accepts_nested_attributes_for :posts
|
240
202
|
# end
|
241
203
|
#
|
242
204
|
# class Post < ActiveRecord::Base
|
243
|
-
# belongs_to :member, :
|
205
|
+
# belongs_to :member, inverse_of: :posts
|
244
206
|
# validates_presence_of :member
|
245
207
|
# end
|
246
208
|
module ClassMethods
|
247
209
|
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
|
248
210
|
|
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.
|
211
|
+
# Defines an attributes writer for the specified association(s).
|
252
212
|
#
|
253
213
|
# Supported options:
|
254
214
|
# [:allow_destroy]
|
@@ -267,23 +227,32 @@ module ActiveRecord
|
|
267
227
|
# any value for _destroy.
|
268
228
|
# [:limit]
|
269
229
|
# Allows you to specify the maximum number of the associated records that
|
270
|
-
# can be processed with the nested attributes.
|
230
|
+
# can be processed with the nested attributes. Limit also can be specified as a
|
231
|
+
# Proc or a Symbol pointing to a method that should return number. If the size of the
|
271
232
|
# nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
|
272
233
|
# exception is raised. If omitted, any number associations can be processed.
|
273
234
|
# Note that the :limit option is only applicable to one-to-many associations.
|
274
235
|
# [:update_only]
|
275
|
-
#
|
276
|
-
#
|
277
|
-
#
|
278
|
-
#
|
236
|
+
# For a one-to-one association, this option allows you to specify how
|
237
|
+
# nested attributes are to be used when an associated record already
|
238
|
+
# exists. In general, an existing record may either be updated with the
|
239
|
+
# new set of attribute values or be replaced by a wholly new record
|
240
|
+
# containing those values. By default the :update_only option is +false+
|
241
|
+
# and the nested attributes are used to update the existing record only
|
242
|
+
# if they include the record's <tt>:id</tt> value. Otherwise a new
|
243
|
+
# record will be instantiated and used to replace the existing one.
|
244
|
+
# However if the :update_only option is +true+, the nested attributes
|
245
|
+
# are used to update the record's attributes always, regardless of
|
246
|
+
# whether the <tt>:id</tt> is present. The option is ignored for collection
|
247
|
+
# associations.
|
279
248
|
#
|
280
249
|
# Examples:
|
281
250
|
# # creates avatar_attributes=
|
282
|
-
# accepts_nested_attributes_for :avatar, :
|
251
|
+
# accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
|
283
252
|
# # creates avatar_attributes=
|
284
|
-
# accepts_nested_attributes_for :avatar, :
|
253
|
+
# accepts_nested_attributes_for :avatar, reject_if: :all_blank
|
285
254
|
# # creates avatar_attributes= and posts_attributes=
|
286
|
-
# accepts_nested_attributes_for :avatar, :posts, :
|
255
|
+
# accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
|
287
256
|
def accepts_nested_attributes_for(*attr_names)
|
288
257
|
options = { :allow_destroy => false, :update_only => false }
|
289
258
|
options.update(attr_names.extract_options!)
|
@@ -301,16 +270,15 @@ module ActiveRecord
|
|
301
270
|
|
302
271
|
type = (reflection.collection? ? :collection : :one_to_one)
|
303
272
|
|
304
|
-
# remove_possible_method :pirate_attributes=
|
305
|
-
#
|
306
273
|
# def pirate_attributes=(attributes)
|
307
274
|
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options)
|
308
275
|
# end
|
309
|
-
|
310
|
-
|
311
|
-
|
276
|
+
generated_feature_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
|
277
|
+
if method_defined?(:#{association_name}_attributes=)
|
278
|
+
remove_method(:#{association_name}_attributes=)
|
279
|
+
end
|
312
280
|
def #{association_name}_attributes=(attributes)
|
313
|
-
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes
|
281
|
+
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
|
314
282
|
end
|
315
283
|
eoruby
|
316
284
|
else
|
@@ -337,31 +305,34 @@ module ActiveRecord
|
|
337
305
|
|
338
306
|
# Assigns the given attributes to the association.
|
339
307
|
#
|
340
|
-
# If
|
341
|
-
#
|
342
|
-
#
|
343
|
-
#
|
308
|
+
# If an associated record does not yet exist, one will be instantiated. If
|
309
|
+
# an associated record already exists, the method's behavior depends on
|
310
|
+
# the value of the update_only option. If update_only is +false+ and the
|
311
|
+
# given attributes include an <tt>:id</tt> that matches the existing record's
|
312
|
+
# id, then the existing record will be modified. If no <tt>:id</tt> is provided
|
313
|
+
# it will be replaced with a new record. If update_only is +true+ the existing
|
314
|
+
# record will be modified regardless of whether an <tt>:id</tt> is provided.
|
344
315
|
#
|
345
316
|
# If the given attributes include a matching <tt>:id</tt> attribute, or
|
346
317
|
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
|
347
318
|
# then the existing record will be marked for destruction.
|
348
|
-
def assign_nested_attributes_for_one_to_one_association(association_name, attributes
|
319
|
+
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
|
349
320
|
options = self.nested_attributes_options[association_name]
|
350
321
|
attributes = attributes.with_indifferent_access
|
351
322
|
|
352
323
|
if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) &&
|
353
324
|
(options[:update_only] || record.id.to_s == attributes['id'].to_s)
|
354
|
-
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]
|
325
|
+
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
|
355
326
|
|
356
|
-
elsif attributes['id'].present?
|
327
|
+
elsif attributes['id'].present?
|
357
328
|
raise_nested_attributes_record_not_found(association_name, attributes['id'])
|
358
329
|
|
359
330
|
elsif !reject_new_record?(association_name, attributes)
|
360
331
|
method = "build_#{association_name}"
|
361
332
|
if respond_to?(method)
|
362
|
-
send(method, attributes.except(*
|
333
|
+
send(method, attributes.except(*UNASSIGNABLE_KEYS))
|
363
334
|
else
|
364
|
-
raise ArgumentError, "Cannot build association
|
335
|
+
raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
|
365
336
|
end
|
366
337
|
end
|
367
338
|
end
|
@@ -377,37 +348,48 @@ module ActiveRecord
|
|
377
348
|
# For example:
|
378
349
|
#
|
379
350
|
# assign_nested_attributes_for_collection_association(:people, {
|
380
|
-
# '1' => { :
|
381
|
-
# '2' => { :
|
382
|
-
# '3' => { :
|
351
|
+
# '1' => { id: '1', name: 'Peter' },
|
352
|
+
# '2' => { name: 'John' },
|
353
|
+
# '3' => { id: '2', _destroy: true }
|
383
354
|
# })
|
384
355
|
#
|
385
356
|
# Will update the name of the Person with ID 1, build a new associated
|
386
|
-
# person with the name
|
357
|
+
# person with the name 'John', and mark the associated Person with ID 2
|
387
358
|
# for destruction.
|
388
359
|
#
|
389
360
|
# Also accepts an Array of attribute hashes:
|
390
361
|
#
|
391
362
|
# assign_nested_attributes_for_collection_association(:people, [
|
392
|
-
# { :
|
393
|
-
# { :
|
394
|
-
# { :
|
363
|
+
# { id: '1', name: 'Peter' },
|
364
|
+
# { name: 'John' },
|
365
|
+
# { id: '2', _destroy: true }
|
395
366
|
# ])
|
396
|
-
def assign_nested_attributes_for_collection_association(association_name, attributes_collection
|
367
|
+
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
|
397
368
|
options = self.nested_attributes_options[association_name]
|
398
369
|
|
399
370
|
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
400
371
|
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
401
372
|
end
|
402
373
|
|
403
|
-
if
|
404
|
-
|
374
|
+
if limit = options[:limit]
|
375
|
+
limit = case limit
|
376
|
+
when Symbol
|
377
|
+
send(limit)
|
378
|
+
when Proc
|
379
|
+
limit.call
|
380
|
+
else
|
381
|
+
limit
|
382
|
+
end
|
383
|
+
|
384
|
+
if limit && attributes_collection.size > limit
|
385
|
+
raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
|
386
|
+
end
|
405
387
|
end
|
406
388
|
|
407
389
|
if attributes_collection.is_a? Hash
|
408
390
|
keys = attributes_collection.keys
|
409
391
|
attributes_collection = if keys.include?('id') || keys.include?(:id)
|
410
|
-
|
392
|
+
[attributes_collection]
|
411
393
|
else
|
412
394
|
attributes_collection.values
|
413
395
|
end
|
@@ -419,7 +401,7 @@ module ActiveRecord
|
|
419
401
|
association.target
|
420
402
|
else
|
421
403
|
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
|
422
|
-
attribute_ids.empty? ? [] : association.
|
404
|
+
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
|
423
405
|
end
|
424
406
|
|
425
407
|
attributes_collection.each do |attributes|
|
@@ -427,7 +409,7 @@ module ActiveRecord
|
|
427
409
|
|
428
410
|
if attributes['id'].blank?
|
429
411
|
unless reject_new_record?(association_name, attributes)
|
430
|
-
association.build(attributes.except(*
|
412
|
+
association.build(attributes.except(*UNASSIGNABLE_KEYS))
|
431
413
|
end
|
432
414
|
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
|
433
415
|
unless association.loaded? || call_reject_if(association_name, attributes)
|
@@ -440,14 +422,11 @@ module ActiveRecord
|
|
440
422
|
else
|
441
423
|
association.add_to_target(existing_record)
|
442
424
|
end
|
443
|
-
|
444
425
|
end
|
445
426
|
|
446
427
|
if !call_reject_if(association_name, attributes)
|
447
|
-
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]
|
428
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
448
429
|
end
|
449
|
-
elsif assignment_opts[:without_protection]
|
450
|
-
association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
|
451
430
|
else
|
452
431
|
raise_nested_attributes_record_not_found(association_name, attributes['id'])
|
453
432
|
end
|
@@ -456,8 +435,8 @@ module ActiveRecord
|
|
456
435
|
|
457
436
|
# Updates a record with the +attributes+ or marks it for destruction if
|
458
437
|
# +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(*
|
438
|
+
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
|
439
|
+
record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
|
461
440
|
record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
|
462
441
|
end
|
463
442
|
|
@@ -470,12 +449,11 @@ module ActiveRecord
|
|
470
449
|
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
|
471
450
|
# association and evaluates to +true+.
|
472
451
|
def reject_new_record?(association_name, attributes)
|
473
|
-
|
452
|
+
has_destroy_flag?(attributes) || call_reject_if(association_name, attributes)
|
474
453
|
end
|
475
454
|
|
476
455
|
def call_reject_if(association_name, attributes)
|
477
|
-
return false if
|
478
|
-
|
456
|
+
return false if has_destroy_flag?(attributes)
|
479
457
|
case callback = self.nested_attributes_options[association_name][:reject_if]
|
480
458
|
when Symbol
|
481
459
|
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
|
@@ -484,21 +462,8 @@ module ActiveRecord
|
|
484
462
|
end
|
485
463
|
end
|
486
464
|
|
487
|
-
# Only take into account the destroy flag if <tt>:allow_destroy</tt> is true
|
488
|
-
def will_be_destroyed?(association_name, attributes)
|
489
|
-
allow_destroy?(association_name) && has_destroy_flag?(attributes)
|
490
|
-
end
|
491
|
-
|
492
|
-
def allow_destroy?(association_name)
|
493
|
-
self.nested_attributes_options[association_name][:allow_destroy]
|
494
|
-
end
|
495
|
-
|
496
465
|
def raise_nested_attributes_record_not_found(association_name, record_id)
|
497
466
|
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}"
|
498
467
|
end
|
499
|
-
|
500
|
-
def unassignable_keys(assignment_opts)
|
501
|
-
assignment_opts[:without_protection] ? UNASSIGNABLE_KEYS - %w[id] : UNASSIGNABLE_KEYS
|
502
|
-
end
|
503
468
|
end
|
504
469
|
end
|