activerecord 3.2.22.5 → 4.2.11.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1632 -609
- data/MIT-LICENSE +1 -1
- data/README.rdoc +37 -41
- data/examples/performance.rb +31 -19
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +56 -42
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -36
- data/lib/active_record/associations/association.rb +73 -55
- data/lib/active_record/associations/association_scope.rb +143 -82
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +125 -31
- data/lib/active_record/associations/builder/belongs_to.rb +89 -61
- 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 -51
- data/lib/active_record/associations/builder/singular_association.rb +23 -17
- data/lib/active_record/associations/collection_association.rb +251 -177
- data/lib/active_record/associations/collection_proxy.rb +963 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +113 -22
- data/lib/active_record/associations/has_many_through_association.rb +99 -39
- data/lib/active_record/associations/has_one_association.rb +43 -20
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
- 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 +62 -33
- data/lib/active_record/associations/preloader.rb +101 -79
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +30 -16
- data/lib/active_record/associations.rb +463 -345
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +142 -151
- 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 +137 -57
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +73 -106
- data/lib/active_record/attribute_methods/serialization.rb +44 -94
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
- data/lib/active_record/attribute_methods/write.rb +57 -44
- data/lib/active_record/attribute_methods.rb +301 -141
- 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 +246 -217
- data/lib/active_record/base.rb +70 -474
- 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 +396 -219
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
- 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 +261 -169
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
- data/lib/active_record/connection_adapters/column.rb +31 -245
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
- data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
- 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 +430 -999
- data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +157 -105
- data/lib/active_record/dynamic_matchers.rb +119 -63
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +94 -36
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +9 -5
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +302 -215
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +143 -70
- data/lib/active_record/integration.rb +65 -12
- 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 +73 -52
- data/lib/active_record/locking/pessimistic.rb +5 -5
- data/lib/active_record/log_subscriber.rb +24 -21
- data/lib/active_record/migration/command_recorder.rb +124 -32
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +511 -213
- data/lib/active_record/model_schema.rb +91 -117
- data/lib/active_record/nested_attributes.rb +184 -130
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +276 -117
- data/lib/active_record/query_cache.rb +19 -37
- data/lib/active_record/querying.rb +28 -18
- data/lib/active_record/railtie.rb +73 -40
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +141 -416
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +1 -4
- data/lib/active_record/reflection.rb +513 -154
- data/lib/active_record/relation/batches.rb +91 -43
- data/lib/active_record/relation/calculations.rb +199 -161
- data/lib/active_record/relation/delegation.rb +116 -25
- data/lib/active_record/relation/finder_methods.rb +362 -248
- 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 -43
- data/lib/active_record/relation/query_methods.rb +928 -167
- data/lib/active_record/relation/spawn_methods.rb +48 -149
- data/lib/active_record/relation.rb +352 -207
- data/lib/active_record/result.rb +101 -10
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +56 -59
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +106 -63
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +50 -57
- data/lib/active_record/scoping/named.rb +73 -109
- data/lib/active_record/scoping.rb +58 -123
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +12 -22
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +168 -15
- 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 +23 -16
- data/lib/active_record/transactions.rb +125 -79
- 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 +24 -16
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +123 -64
- data/lib/active_record/validations.rb +36 -29
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +66 -46
- data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +101 -45
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/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/active_record/test_case.rb +0 -73
- 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
|
@@ -100,15 +98,15 @@ module ActiveRecord
|
|
100
98
|
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
|
101
99
|
# that evaluates to +true+.
|
102
100
|
#
|
103
|
-
# params = { :
|
104
|
-
# :
|
105
|
-
# { :
|
106
|
-
# { :
|
107
|
-
# { :
|
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
|
108
106
|
# ]
|
109
107
|
# }}
|
110
108
|
#
|
111
|
-
# member = Member.create(params[
|
109
|
+
# member = Member.create(params[:member])
|
112
110
|
# member.posts.length # => 2
|
113
111
|
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
|
114
112
|
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
|
@@ -119,18 +117,18 @@ module ActiveRecord
|
|
119
117
|
#
|
120
118
|
# class Member < ActiveRecord::Base
|
121
119
|
# has_many :posts
|
122
|
-
# accepts_nested_attributes_for :posts, :
|
120
|
+
# accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
|
123
121
|
# end
|
124
122
|
#
|
125
|
-
# params = { :
|
126
|
-
# :
|
127
|
-
# { :
|
128
|
-
# { :
|
129
|
-
# { :
|
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
|
130
128
|
# ]
|
131
129
|
# }}
|
132
130
|
#
|
133
|
-
# member = Member.create(params[
|
131
|
+
# member = Member.create(params[:member])
|
134
132
|
# member.posts.length # => 2
|
135
133
|
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
|
136
134
|
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
|
@@ -139,12 +137,12 @@ module ActiveRecord
|
|
139
137
|
#
|
140
138
|
# class Member < ActiveRecord::Base
|
141
139
|
# has_many :posts
|
142
|
-
# accepts_nested_attributes_for :posts, :
|
140
|
+
# accepts_nested_attributes_for :posts, reject_if: :new_record?
|
143
141
|
# end
|
144
142
|
#
|
145
143
|
# class Member < ActiveRecord::Base
|
146
144
|
# has_many :posts
|
147
|
-
# accepts_nested_attributes_for :posts, :
|
145
|
+
# accepts_nested_attributes_for :posts, reject_if: :reject_posts
|
148
146
|
#
|
149
147
|
# def reject_posts(attributed)
|
150
148
|
# attributed['title'].blank?
|
@@ -155,10 +153,10 @@ module ActiveRecord
|
|
155
153
|
# associated record, the matching record will be modified:
|
156
154
|
#
|
157
155
|
# member.attributes = {
|
158
|
-
# :
|
159
|
-
# :
|
160
|
-
# { :
|
161
|
-
# { :
|
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' }
|
162
160
|
# ]
|
163
161
|
# }
|
164
162
|
#
|
@@ -173,14 +171,14 @@ module ActiveRecord
|
|
173
171
|
#
|
174
172
|
# class Member < ActiveRecord::Base
|
175
173
|
# has_many :posts
|
176
|
-
# accepts_nested_attributes_for :posts, :
|
174
|
+
# accepts_nested_attributes_for :posts, allow_destroy: true
|
177
175
|
# end
|
178
176
|
#
|
179
|
-
# params = { :
|
180
|
-
# :
|
177
|
+
# params = { member: {
|
178
|
+
# posts_attributes: [{ id: '2', _destroy: '1' }]
|
181
179
|
# }}
|
182
180
|
#
|
183
|
-
# member.attributes = params[
|
181
|
+
# member.attributes = params[:member]
|
184
182
|
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
|
185
183
|
# member.posts.length # => 2
|
186
184
|
# member.save
|
@@ -189,15 +187,15 @@ module ActiveRecord
|
|
189
187
|
# Nested attributes for an associated collection can also be passed in
|
190
188
|
# the form of a hash of hashes instead of an array of hashes:
|
191
189
|
#
|
192
|
-
# Member.create(:
|
193
|
-
# :
|
194
|
-
#
|
190
|
+
# Member.create(name: 'joe',
|
191
|
+
# posts_attributes: { first: { title: 'Foo' },
|
192
|
+
# second: { title: 'Bar' } })
|
195
193
|
#
|
196
194
|
# has the same effect as
|
197
195
|
#
|
198
|
-
# Member.create(:
|
199
|
-
# :
|
200
|
-
#
|
196
|
+
# Member.create(name: 'joe',
|
197
|
+
# posts_attributes: [ { title: 'Foo' },
|
198
|
+
# { title: 'Bar' } ])
|
201
199
|
#
|
202
200
|
# The keys of the hash which is the value for +:posts_attributes+ are
|
203
201
|
# ignored in this case.
|
@@ -216,18 +214,6 @@ module ActiveRecord
|
|
216
214
|
# the parent model is saved. This happens inside the transaction initiated
|
217
215
|
# by the parents save method. See ActiveRecord::AutosaveAssociation.
|
218
216
|
#
|
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
217
|
# === Validating the presence of a parent model
|
232
218
|
#
|
233
219
|
# If you want to validate that a child record is associated with a parent
|
@@ -235,20 +221,39 @@ module ActiveRecord
|
|
235
221
|
# <tt>inverse_of</tt> as this example illustrates:
|
236
222
|
#
|
237
223
|
# class Member < ActiveRecord::Base
|
238
|
-
# has_many :posts, :
|
224
|
+
# has_many :posts, inverse_of: :member
|
239
225
|
# accepts_nested_attributes_for :posts
|
240
226
|
# end
|
241
227
|
#
|
242
228
|
# class Post < ActiveRecord::Base
|
243
|
-
# belongs_to :member, :
|
229
|
+
# belongs_to :member, inverse_of: :posts
|
244
230
|
# validates_presence_of :member
|
245
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
|
246
253
|
module ClassMethods
|
247
254
|
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
|
248
255
|
|
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.
|
256
|
+
# Defines an attributes writer for the specified association(s).
|
252
257
|
#
|
253
258
|
# Supported options:
|
254
259
|
# [:allow_destroy]
|
@@ -267,23 +272,32 @@ module ActiveRecord
|
|
267
272
|
# any value for _destroy.
|
268
273
|
# [:limit]
|
269
274
|
# Allows you to specify the maximum number of the associated records that
|
270
|
-
# 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
|
271
277
|
# nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
|
272
278
|
# exception is raised. If omitted, any number associations can be processed.
|
273
279
|
# Note that the :limit option is only applicable to one-to-many associations.
|
274
280
|
# [:update_only]
|
275
|
-
#
|
276
|
-
#
|
277
|
-
#
|
278
|
-
#
|
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.
|
279
293
|
#
|
280
294
|
# Examples:
|
281
295
|
# # creates avatar_attributes=
|
282
|
-
# accepts_nested_attributes_for :avatar, :
|
296
|
+
# accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
|
283
297
|
# # creates avatar_attributes=
|
284
|
-
# accepts_nested_attributes_for :avatar, :
|
298
|
+
# accepts_nested_attributes_for :avatar, reject_if: :all_blank
|
285
299
|
# # creates avatar_attributes= and posts_attributes=
|
286
|
-
# accepts_nested_attributes_for :avatar, :posts, :
|
300
|
+
# accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
|
287
301
|
def accepts_nested_attributes_for(*attr_names)
|
288
302
|
options = { :allow_destroy => false, :update_only => false }
|
289
303
|
options.update(attr_names.extract_options!)
|
@@ -291,33 +305,45 @@ module ActiveRecord
|
|
291
305
|
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
|
292
306
|
|
293
307
|
attr_names.each do |association_name|
|
294
|
-
if reflection =
|
295
|
-
reflection.
|
296
|
-
|
308
|
+
if reflection = _reflect_on_association(association_name)
|
309
|
+
reflection.autosave = true
|
310
|
+
define_autosave_validation_callbacks(reflection)
|
297
311
|
|
298
312
|
nested_attributes_options = self.nested_attributes_options.dup
|
299
313
|
nested_attributes_options[association_name.to_sym] = options
|
300
314
|
self.nested_attributes_options = nested_attributes_options
|
301
315
|
|
302
316
|
type = (reflection.collection? ? :collection : :one_to_one)
|
303
|
-
|
304
|
-
# remove_possible_method :pirate_attributes=
|
305
|
-
#
|
306
|
-
# def pirate_attributes=(attributes)
|
307
|
-
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options)
|
308
|
-
# end
|
309
|
-
class_eval <<-eoruby, __FILE__, __LINE__ + 1
|
310
|
-
remove_possible_method(:#{association_name}_attributes=)
|
311
|
-
|
312
|
-
def #{association_name}_attributes=(attributes)
|
313
|
-
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options)
|
314
|
-
end
|
315
|
-
eoruby
|
317
|
+
generate_association_writer(association_name, type)
|
316
318
|
else
|
317
319
|
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
|
318
320
|
end
|
319
321
|
end
|
320
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
|
321
347
|
end
|
322
348
|
|
323
349
|
# Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
|
@@ -337,31 +363,42 @@ module ActiveRecord
|
|
337
363
|
|
338
364
|
# Assigns the given attributes to the association.
|
339
365
|
#
|
340
|
-
# If
|
341
|
-
#
|
342
|
-
#
|
343
|
-
#
|
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.
|
344
373
|
#
|
345
374
|
# If the given attributes include a matching <tt>:id</tt> attribute, or
|
346
375
|
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
|
347
376
|
# then the existing record will be marked for destruction.
|
348
|
-
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)
|
349
378
|
options = self.nested_attributes_options[association_name]
|
350
379
|
attributes = attributes.with_indifferent_access
|
380
|
+
existing_record = send(association_name)
|
351
381
|
|
352
|
-
if (options[:update_only] || !attributes['id'].blank?) &&
|
353
|
-
(options[:update_only] ||
|
354
|
-
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)
|
355
385
|
|
356
|
-
elsif attributes['id'].present?
|
357
|
-
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'])
|
358
388
|
|
359
389
|
elsif !reject_new_record?(association_name, attributes)
|
360
|
-
|
361
|
-
|
362
|
-
|
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)
|
363
395
|
else
|
364
|
-
|
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
|
365
402
|
end
|
366
403
|
end
|
367
404
|
end
|
@@ -377,37 +414,35 @@ module ActiveRecord
|
|
377
414
|
# For example:
|
378
415
|
#
|
379
416
|
# assign_nested_attributes_for_collection_association(:people, {
|
380
|
-
# '1' => { :
|
381
|
-
# '2' => { :
|
382
|
-
# '3' => { :
|
417
|
+
# '1' => { id: '1', name: 'Peter' },
|
418
|
+
# '2' => { name: 'John' },
|
419
|
+
# '3' => { id: '2', _destroy: true }
|
383
420
|
# })
|
384
421
|
#
|
385
422
|
# Will update the name of the Person with ID 1, build a new associated
|
386
|
-
# person with the name
|
423
|
+
# person with the name 'John', and mark the associated Person with ID 2
|
387
424
|
# for destruction.
|
388
425
|
#
|
389
426
|
# Also accepts an Array of attribute hashes:
|
390
427
|
#
|
391
428
|
# assign_nested_attributes_for_collection_association(:people, [
|
392
|
-
# { :
|
393
|
-
# { :
|
394
|
-
# { :
|
429
|
+
# { id: '1', name: 'Peter' },
|
430
|
+
# { name: 'John' },
|
431
|
+
# { id: '2', _destroy: true }
|
395
432
|
# ])
|
396
|
-
def assign_nested_attributes_for_collection_association(association_name, attributes_collection
|
433
|
+
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
|
397
434
|
options = self.nested_attributes_options[association_name]
|
398
435
|
|
399
436
|
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
400
437
|
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
401
438
|
end
|
402
439
|
|
403
|
-
|
404
|
-
raise TooManyRecords, "Maximum #{options[:limit]} records are allowed. Got #{attributes_collection.size} records instead."
|
405
|
-
end
|
440
|
+
check_record_limit!(options[:limit], attributes_collection)
|
406
441
|
|
407
442
|
if attributes_collection.is_a? Hash
|
408
443
|
keys = attributes_collection.keys
|
409
444
|
attributes_collection = if keys.include?('id') || keys.include?(:id)
|
410
|
-
|
445
|
+
[attributes_collection]
|
411
446
|
else
|
412
447
|
attributes_collection.values
|
413
448
|
end
|
@@ -419,7 +454,7 @@ module ActiveRecord
|
|
419
454
|
association.target
|
420
455
|
else
|
421
456
|
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
|
422
|
-
attribute_ids.empty? ? [] : association.
|
457
|
+
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
|
423
458
|
end
|
424
459
|
|
425
460
|
attributes_collection.each do |attributes|
|
@@ -427,52 +462,75 @@ module ActiveRecord
|
|
427
462
|
|
428
463
|
if attributes['id'].blank?
|
429
464
|
unless reject_new_record?(association_name, attributes)
|
430
|
-
association.build(attributes.except(*
|
465
|
+
association.build(attributes.except(*UNASSIGNABLE_KEYS))
|
431
466
|
end
|
432
467
|
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
|
433
|
-
unless
|
468
|
+
unless call_reject_if(association_name, attributes)
|
434
469
|
# Make sure we are operating on the actual object which is in the association's
|
435
470
|
# proxy_target array (either by finding it, or adding it if not found)
|
436
|
-
|
437
|
-
|
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 }
|
438
473
|
if target_record
|
439
474
|
existing_record = target_record
|
440
475
|
else
|
441
|
-
association.add_to_target(existing_record)
|
476
|
+
association.add_to_target(existing_record, :skip_callbacks)
|
442
477
|
end
|
443
478
|
|
479
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
444
480
|
end
|
481
|
+
else
|
482
|
+
raise_nested_attributes_record_not_found!(association_name, attributes['id'])
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
445
486
|
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
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
|
451
500
|
else
|
452
|
-
|
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."
|
453
506
|
end
|
454
507
|
end
|
455
508
|
end
|
456
509
|
|
457
510
|
# Updates a record with the +attributes+ or marks it for destruction if
|
458
511
|
# +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(*
|
512
|
+
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
|
513
|
+
record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
|
461
514
|
record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
|
462
515
|
end
|
463
516
|
|
464
517
|
# Determines if a hash contains a truthy _destroy key.
|
465
518
|
def has_destroy_flag?(hash)
|
466
|
-
|
519
|
+
Type::Boolean.new.type_cast_from_user(hash['_destroy'])
|
467
520
|
end
|
468
521
|
|
469
|
-
# Determines if a new record should be
|
522
|
+
# Determines if a new record should be rejected by checking
|
470
523
|
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
|
471
524
|
# association and evaluates to +true+.
|
472
525
|
def reject_new_record?(association_name, attributes)
|
473
526
|
will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
|
474
527
|
end
|
475
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.
|
476
534
|
def call_reject_if(association_name, attributes)
|
477
535
|
return false if will_be_destroyed?(association_name, attributes)
|
478
536
|
|
@@ -493,12 +551,8 @@ module ActiveRecord
|
|
493
551
|
self.nested_attributes_options[association_name][:allow_destroy]
|
494
552
|
end
|
495
553
|
|
496
|
-
def raise_nested_attributes_record_not_found(association_name, record_id)
|
497
|
-
raise RecordNotFound, "Couldn't find #{self.class.
|
498
|
-
end
|
499
|
-
|
500
|
-
def unassignable_keys(assignment_opts)
|
501
|
-
assignment_opts[:without_protection] ? UNASSIGNABLE_KEYS - %w[id] : UNASSIGNABLE_KEYS
|
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}"
|
502
556
|
end
|
503
557
|
end
|
504
558
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# = Active Record No Touching
|
3
|
+
module NoTouching
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
# Lets you selectively disable calls to `touch` for the
|
8
|
+
# duration of a block.
|
9
|
+
#
|
10
|
+
# ==== Examples
|
11
|
+
# ActiveRecord::Base.no_touching do
|
12
|
+
# Project.first.touch # does nothing
|
13
|
+
# Message.first.touch # does nothing
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# Project.no_touching do
|
17
|
+
# Project.first.touch # does nothing
|
18
|
+
# Message.first.touch # works, but does not touch the associated project
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
def no_touching(&block)
|
22
|
+
NoTouching.apply_to(self, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def apply_to(klass) #:nodoc:
|
28
|
+
klasses.push(klass)
|
29
|
+
yield
|
30
|
+
ensure
|
31
|
+
klasses.pop
|
32
|
+
end
|
33
|
+
|
34
|
+
def applied_to?(klass) #:nodoc:
|
35
|
+
klasses.any? { |k| k >= klass }
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def klasses
|
40
|
+
Thread.current[:no_touching_classes] ||= []
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def no_touching?
|
45
|
+
NoTouching.applied_to?(self.class)
|
46
|
+
end
|
47
|
+
|
48
|
+
def touch(*) # :nodoc:
|
49
|
+
super unless no_touching?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|