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.

Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. 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
- class_inheritable_accessor :nested_attributes_options, :instance_writer => false
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 method.
23
- # When you enable nested attributes an attribute writer is defined on
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 = { :member => { :name => 'Jack', :avatar_attributes => { :icon => 'smiling' } } }
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 = { :member => { :avatar_attributes => { :id => '2', :icon => 'sad' } } }
62
- # member.update_attributes params[: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, :allow_destroy => true
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 = { :id => '2', :_destroy => '1' }
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 an associated post model through
95
- # the attribute hash.
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 = { :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
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['member'])
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
- # class Member < ActiveRecord::Base
119
- # has_many :posts
120
- # accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? }
121
- # end
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 = { :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
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['member'])
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
- # class Member < ActiveRecord::Base
139
- # has_many :posts
140
- # accepts_nested_attributes_for :posts, :reject_if => :new_record?
141
- # end
138
+ # class Member < ActiveRecord::Base
139
+ # has_many :posts
140
+ # accepts_nested_attributes_for :posts, reject_if: :new_record?
141
+ # end
142
142
  #
143
- # class Member < ActiveRecord::Base
144
- # has_many :posts
145
- # accepts_nested_attributes_for :posts, :reject_if => :reject_posts
143
+ # class Member < ActiveRecord::Base
144
+ # has_many :posts
145
+ # accepts_nested_attributes_for :posts, reject_if: :reject_posts
146
146
  #
147
- # def reject_posts(attributed)
148
- # attributed['title'].blank?
149
- # end
150
- # end
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
- # :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' }
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, :allow_destroy => true
174
+ # accepts_nested_attributes_for :posts, allow_destroy: true
175
175
  # end
176
176
  #
177
- # params = { :member => {
178
- # :posts_attributes => [{ :id => '2', :_destroy => '1' }]
177
+ # params = { member: {
178
+ # posts_attributes: [{ id: '2', _destroy: '1' }]
179
179
  # }}
180
180
  #
181
- # member.attributes = params['member']
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? { |_, value| value.blank? } }
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). If you
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. If the size of the
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
- # Allows you to specify that an existing record may only be updated.
222
- # A new record may only be created when there is no existing record.
223
- # This option only works for one-to-one associations and is ignored for
224
- # collection associations. This option is off by default.
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, :reject_if => proc { |attributes| attributes['name'].blank? }
292
+ # accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
229
293
  # # creates avatar_attributes=
230
- # accepts_nested_attributes_for :avatar, :reject_if => :all_blank
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, :allow_destroy => true
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
- type = (reflection.collection? ? :collection : :one_to_one)
310
+ self.nested_attributes_options = nested_attributes_options
245
311
 
246
- # def pirate_attributes=(attributes)
247
- # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
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 update_only is false and the given attributes include an <tt>:id</tt>
282
- # that matches the existing record's id, then the existing record will be
283
- # modified. If update_only is true, a new record is only created when no
284
- # object exists. Otherwise a new record will be built.
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
- check_existing_record = (options[:update_only] || !attributes['id'].blank?)
376
+ existing_record = send(association_name)
293
377
 
294
- if check_existing_record && (record = send(association_name)) &&
295
- (options[:update_only] || record.id.to_s == attributes['id'].to_s)
296
- assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])
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
- existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
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
- method = "build_#{association_name}"
305
- if respond_to?(method)
306
- send(method, attributes.except(*UNASSIGNABLE_KEYS))
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
- raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?"
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' => { :id => '1', :name => 'Peter' },
325
- # '2' => { :name => 'John' },
326
- # '3' => { :id => '2', :_destroy => true }
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 `John', and mark the associated Person with ID 2
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
- # { :id => '1', :name => 'Peter' },
337
- # { :name => 'John' },
338
- # { :id => '2', :_destroy => true }
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
- if options[:limit] && attributes_collection.size > options[:limit]
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
- attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes }
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 = send(association_name)
447
+ association = association(association_name)
356
448
 
357
449
  existing_records = if association.loaded?
358
- association.to_a
450
+ association.target
359
451
  else
360
452
  attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
361
- attribute_ids.present? ? association.all(:conditions => {association.primary_key => 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
- elsif existing_records.count == 0 #Existing record but not yet associated
373
- existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
374
- association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
375
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
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
- elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
378
- association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
379
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
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
- if has_destroy_flag?(attributes) && allow_destroy
389
- record.mark_for_destruction
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 built by checking for
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
- case callback = nested_attributes_options[association_name][:reject_if]
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