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