activerecord 5.2.0 → 5.2.8.1

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +361 -0
  3. data/lib/active_record/association_relation.rb +3 -3
  4. data/lib/active_record/associations/alias_tracker.rb +1 -1
  5. data/lib/active_record/associations/association.rb +25 -10
  6. data/lib/active_record/associations/belongs_to_association.rb +14 -5
  7. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +4 -1
  8. data/lib/active_record/associations/builder/belongs_to.rb +11 -2
  9. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  10. data/lib/active_record/associations/collection_association.rb +19 -15
  11. data/lib/active_record/associations/collection_proxy.rb +8 -34
  12. data/lib/active_record/associations/has_many_association.rb +9 -0
  13. data/lib/active_record/associations/has_many_through_association.rb +29 -12
  14. data/lib/active_record/associations/has_one_association.rb +8 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  16. data/lib/active_record/associations/join_dependency/join_association.rb +39 -24
  17. data/lib/active_record/associations/join_dependency/join_part.rb +7 -0
  18. data/lib/active_record/associations/join_dependency.rb +39 -64
  19. data/lib/active_record/associations/preloader.rb +1 -1
  20. data/lib/active_record/associations/singular_association.rb +4 -10
  21. data/lib/active_record/associations/through_association.rb +1 -1
  22. data/lib/active_record/associations.rb +9 -9
  23. data/lib/active_record/attribute_methods/dirty.rb +15 -10
  24. data/lib/active_record/attribute_methods/read.rb +1 -1
  25. data/lib/active_record/autosave_association.rb +27 -8
  26. data/lib/active_record/callbacks.rb +4 -0
  27. data/lib/active_record/coders/yaml_column.rb +13 -1
  28. data/lib/active_record/collection_cache_key.rb +2 -2
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +36 -11
  30. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  31. data/lib/active_record/connection_adapters/abstract/database_statements.rb +19 -6
  32. data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -3
  33. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
  34. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -4
  35. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +6 -15
  36. data/lib/active_record/connection_adapters/abstract/transaction.rb +23 -14
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
  38. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -19
  39. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  40. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +11 -2
  41. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -0
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -2
  43. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +11 -1
  44. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  45. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -0
  46. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  47. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -26
  48. data/lib/active_record/connection_adapters/postgresql/utils.rb +1 -1
  49. data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -1
  50. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -0
  51. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -1
  52. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -6
  53. data/lib/active_record/core.rb +12 -1
  54. data/lib/active_record/counter_cache.rb +17 -13
  55. data/lib/active_record/enum.rb +1 -0
  56. data/lib/active_record/errors.rb +18 -12
  57. data/lib/active_record/gem_version.rb +2 -2
  58. data/lib/active_record/log_subscriber.rb +1 -1
  59. data/lib/active_record/migration/compatibility.rb +15 -15
  60. data/lib/active_record/migration.rb +1 -1
  61. data/lib/active_record/model_schema.rb +1 -1
  62. data/lib/active_record/persistence.rb +6 -5
  63. data/lib/active_record/query_cache.rb +4 -11
  64. data/lib/active_record/querying.rb +1 -1
  65. data/lib/active_record/railtie.rb +19 -3
  66. data/lib/active_record/reflection.rb +10 -14
  67. data/lib/active_record/relation/calculations.rb +16 -12
  68. data/lib/active_record/relation/delegation.rb +30 -0
  69. data/lib/active_record/relation/finder_methods.rb +10 -8
  70. data/lib/active_record/relation/merger.rb +10 -11
  71. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  72. data/lib/active_record/relation/predicate_builder.rb +20 -14
  73. data/lib/active_record/relation/query_attribute.rb +5 -3
  74. data/lib/active_record/relation/query_methods.rb +50 -22
  75. data/lib/active_record/relation/spawn_methods.rb +1 -1
  76. data/lib/active_record/relation.rb +39 -20
  77. data/lib/active_record/scoping/default.rb +2 -2
  78. data/lib/active_record/scoping/named.rb +2 -0
  79. data/lib/active_record/statement_cache.rb +2 -2
  80. data/lib/active_record/tasks/database_tasks.rb +1 -1
  81. data/lib/active_record/timestamp.rb +8 -1
  82. data/lib/active_record/transactions.rb +24 -21
  83. data/lib/active_record/type/serialized.rb +4 -0
  84. metadata +12 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a81ce578a98474e193314f6d806ee2f910d9d651fab445a14595154c41f6401
4
- data.tar.gz: 1a45823e0f6e0af4759af73c37e3a1b76bb9584e168b553bcfbca3208d2211e2
3
+ metadata.gz: 86737eb3187422c20dfc6d14b175ae9d541dfe670178da64a024c0968693ef81
4
+ data.tar.gz: 767900fd0d4a68dae1536cb7750e5456bc2bac72fde2ce7518ff69b1e5c8c706
5
5
  SHA512:
6
- metadata.gz: f6390719d7140cda11b175cfb8c4ad62e502204a202a70d6bf532131f323b49f120c30ca8e28d9e35335bb03d52a596ae7dcdfb3b05e762ae78150c70625adae
7
- data.tar.gz: cc85711ffcbec0a1b04b4f9940c0752687f9b98e7b65950cf46b28df14c13c9399d15699134bb3c5835bd76fdc8b944b0a661483c685075605b270f3bbe774d0
6
+ metadata.gz: 0af8b7124c2d152f219220f518615b135137bb610d747b520d9c28370c73c966110121c3bede44a3ca0b9c36a4650f7ed300548e658eda7c888f7298b2f45162
7
+ data.tar.gz: 1a72afb896390c797673d4a100d7d9abd055a63fea21cef26e896a0cb424a64de661e99abb8cd80347643720d5c529dabe92af619dcaa7ff8610f4b3c1782483
data/CHANGELOG.md CHANGED
@@ -1,3 +1,364 @@
1
+ ## Rails 5.2.8.1 (July 12, 2022) ##
2
+
3
+ * Change ActiveRecord::Coders::YAMLColumn default to safe_load
4
+
5
+ This adds two new configuration options The configuration options are as
6
+ follows:
7
+
8
+ * `config.active_storage.use_yaml_unsafe_load`
9
+
10
+ When set to true, this configuration option tells Rails to use the old
11
+ "unsafe" YAML loading strategy, maintaining the existing behavior but leaving
12
+ the possible escalation vulnerability in place. Setting this option to true
13
+ is *not* recommended, but can aid in upgrading.
14
+
15
+ * `config.active_record.yaml_column_permitted_classes`
16
+
17
+ The "safe YAML" loading method does not allow all classes to be deserialized
18
+ by default. This option allows you to specify classes deemed "safe" in your
19
+ application. For example, if your application uses Symbol and Time in
20
+ serialized data, you can add Symbol and Time to the allowed list as follows:
21
+
22
+ ```
23
+ config.active_record.yaml_column_permitted_classes = [Symbol, Date, Time]
24
+ ```
25
+
26
+ [CVE-2022-32224]
27
+
28
+
29
+ ## Rails 5.2.8 (May 09, 2022) ##
30
+
31
+ * No changes.
32
+
33
+
34
+ ## Rails 5.2.7.1 (April 26, 2022) ##
35
+
36
+ * No changes.
37
+
38
+
39
+ ## Rails 5.2.7 (March 10, 2022) ##
40
+
41
+ * No changes.
42
+
43
+
44
+ ## Rails 5.2.6.3 (March 08, 2022) ##
45
+
46
+ * No changes.
47
+
48
+
49
+ ## Rails 5.2.6.2 (February 11, 2022) ##
50
+
51
+ * No changes.
52
+
53
+
54
+ ## Rails 5.2.6.1 (February 11, 2022) ##
55
+
56
+ * No changes.
57
+
58
+
59
+ ## Rails 5.2.6 (May 05, 2021) ##
60
+
61
+ * No changes.
62
+
63
+
64
+ ## Rails 5.2.5 (March 26, 2021) ##
65
+
66
+ * No changes.
67
+
68
+
69
+ ## Rails 5.2.4.6 (May 05, 2021) ##
70
+
71
+ * No changes.
72
+
73
+
74
+ ## Rails 5.2.4.5 (February 10, 2021) ##
75
+
76
+ * Fix possible DoS vector in PostgreSQL money type
77
+
78
+ Carefully crafted input can cause a DoS via the regular expressions used
79
+ for validating the money format in the PostgreSQL adapter. This patch
80
+ fixes the regexp.
81
+
82
+ Thanks to @dee-see from Hackerone for this patch!
83
+
84
+ [CVE-2021-22880]
85
+
86
+ *Aaron Patterson*
87
+
88
+
89
+ ## Rails 5.2.4.4 (September 09, 2020) ##
90
+
91
+ * No changes.
92
+
93
+
94
+ ## Rails 5.2.4.3 (May 18, 2020) ##
95
+
96
+ * No changes.
97
+
98
+ ## Rails 5.2.4.2 (March 19, 2020) ##
99
+
100
+ * No changes.
101
+
102
+
103
+ ## Rails 5.2.4.1 (December 18, 2019) ##
104
+
105
+ * No changes.
106
+
107
+
108
+ ## Rails 5.2.4 (November 27, 2019) ##
109
+
110
+ * Fix circular `autosave: true` causes invalid records to be saved.
111
+
112
+ Prior to the fix, when there was a circular series of `autosave: true`
113
+ associations, the callback for a `has_many` association was run while
114
+ another instance of the same callback on the same association hadn't
115
+ finished running. When control returned to the first instance of the
116
+ callback, the instance variable had changed, and subsequent associated
117
+ records weren't saved correctly. Specifically, the ID field for the
118
+ `belongs_to` corresponding to the `has_many` was `nil`.
119
+
120
+ Fixes #28080.
121
+
122
+ *Larry Reid*
123
+
124
+ * PostgreSQL: Fix GROUP BY with ORDER BY virtual count attribute.
125
+
126
+ Fixes #36022.
127
+
128
+ *Ryuta Kamizono*
129
+
130
+ * Fix sqlite3 collation parsing when using decimal columns.
131
+
132
+ *Martin R. Schuster*
133
+
134
+ * Make ActiveRecord `ConnectionPool.connections` method thread-safe.
135
+
136
+ Fixes #36465.
137
+
138
+ *Jeff Doering*
139
+
140
+ * Assign all attributes before calling `build` to ensure the child record is visible in
141
+ `before_add` and `after_add` callbacks for `has_many :through` associations.
142
+
143
+ Fixes #33249.
144
+
145
+ *Ryan H. Kerr*
146
+
147
+
148
+ ## Rails 5.2.3 (March 27, 2019) ##
149
+
150
+ * Fix different `count` calculation when using `size` with manual `select` with DISTINCT.
151
+
152
+ Fixes #35214.
153
+
154
+ *Juani Villarejo*
155
+
156
+ * Fix prepared statements caching to be enabled even when query caching is enabled.
157
+
158
+ *Ryuta Kamizono*
159
+
160
+ * Don't allow `where` with invalid value matches to nil values.
161
+
162
+ Fixes #33624.
163
+
164
+ *Ryuta Kamizono*
165
+
166
+ * Restore an ability that class level `update` without giving ids.
167
+
168
+ Fixes #34743.
169
+
170
+ *Ryuta Kamizono*
171
+
172
+ * Fix join table column quoting with SQLite.
173
+
174
+ *Gannon McGibbon*
175
+
176
+ * Ensure that `delete_all` on collection proxy returns affected count.
177
+
178
+ *Ryuta Kamizono*
179
+
180
+ * Reset scope after delete on collection association to clear stale offsets of removed records.
181
+
182
+ *Gannon McGibbon*
183
+
184
+
185
+ ## Rails 5.2.2.1 (March 11, 2019) ##
186
+
187
+ * No changes.
188
+
189
+
190
+ ## Rails 5.2.2 (December 04, 2018) ##
191
+
192
+ * Do not ignore the scoping with query methods in the scope block.
193
+
194
+ *Ryuta Kamizono*
195
+
196
+ * Allow aliased attributes to be used in `#update_columns` and `#update`.
197
+
198
+ *Gannon McGibbon*
199
+
200
+ * Allow spaces in postgres table names.
201
+
202
+ Fixes issue where "user post" is misinterpreted as "\"user\".\"post\"" when quoting table names with the postgres
203
+ adapter.
204
+
205
+ *Gannon McGibbon*
206
+
207
+ * Cached columns_hash fields should be excluded from ResultSet#column_types
208
+
209
+ PR #34528 addresses the inconsistent behaviour when attribute is defined for an ignored column. The following test
210
+ was passing for SQLite and MySQL, but failed for PostgreSQL:
211
+
212
+ ```ruby
213
+ class DeveloperName < ActiveRecord::Type::String
214
+ def deserialize(value)
215
+ "Developer: #{value}"
216
+ end
217
+ end
218
+
219
+ class AttributedDeveloper < ActiveRecord::Base
220
+ self.table_name = "developers"
221
+
222
+ attribute :name, DeveloperName.new
223
+
224
+ self.ignored_columns += ["name"]
225
+ end
226
+
227
+ developer = AttributedDeveloper.create
228
+ developer.update_column :name, "name"
229
+
230
+ loaded_developer = AttributedDeveloper.where(id: developer.id).select("*").first
231
+ puts loaded_developer.name # should be "Developer: name" but it's just "name"
232
+ ```
233
+
234
+ *Dmitry Tsepelev*
235
+
236
+ * Values of enum are frozen, raising an error when attempting to modify them.
237
+
238
+ *Emmanuel Byrd*
239
+
240
+ * `update_columns` now correctly raises `ActiveModel::MissingAttributeError`
241
+ if the attribute does not exist.
242
+
243
+ *Sean Griffin*
244
+
245
+ * Do not use prepared statement in queries that have a large number of binds.
246
+
247
+ *Ryuta Kamizono*
248
+
249
+ * Fix query cache to load before first request.
250
+
251
+ *Eileen M. Uchitelle*
252
+
253
+ * Fix collection cache key with limit and custom select to avoid ambiguous timestamp column error.
254
+
255
+ Fixes #33056.
256
+
257
+ *Federico Martinez*
258
+
259
+ * Fix duplicated record creation when using nested attributes with `create_with`.
260
+
261
+ *Darwin Wu*
262
+
263
+ * Fix regression setting children record in parent `before_save` callback.
264
+
265
+ *Guo Xiang Tan*
266
+
267
+ * Prevent leaking of user's DB credentials on `rails db:create` failure.
268
+
269
+ *bogdanvlviv*
270
+
271
+ * Clear mutation tracker before continuing the around callbacks.
272
+
273
+ *Yuya Tanaka*
274
+
275
+ * Prevent deadlocks when waiting for connection from pool.
276
+
277
+ *Brent Wheeldon*
278
+
279
+ * Avoid extra scoping when using `Relation#update` that was causing this method to change the current scope.
280
+
281
+ *Ryuta Kamizono*
282
+
283
+ * Fix numericality validator not to be affected by custom getter.
284
+
285
+ *Ryuta Kamizono*
286
+
287
+ * Fix bulk change table ignores comment option on PostgreSQL.
288
+
289
+ *Yoshiyuki Kinjo*
290
+
291
+
292
+ ## Rails 5.2.1.1 (November 27, 2018) ##
293
+
294
+ * No changes.
295
+
296
+
297
+ ## Rails 5.2.1 (August 07, 2018) ##
298
+
299
+ * PostgreSQL: Support new relkind for partitioned tables.
300
+
301
+ Fixes #33008.
302
+
303
+ *Yannick Schutz*
304
+
305
+ * Rollback parent transaction when children fails to update.
306
+
307
+ *Guillaume Malette*
308
+
309
+ * Fix default value for MySQL time types with specified precision.
310
+
311
+ *Nikolay Kondratyev*
312
+
313
+ * Fix `touch` option to behave consistently with `Persistence#touch` method.
314
+
315
+ *Ryuta Kamizono*
316
+
317
+ * Fix `save` in `after_create_commit` won't invoke extra `after_create_commit`.
318
+
319
+ Fixes #32831.
320
+
321
+ *Ryuta Kamizono*
322
+
323
+ * Fix logic on disabling commit callbacks so they are not called unexpectedly when errors occur.
324
+
325
+ *Brian Durand*
326
+
327
+ * Fix parent record should not get saved with duplicate children records.
328
+
329
+ Fixes #32940.
330
+
331
+ *Santosh Wadghule*
332
+
333
+ * Fix that association's after_touch is not called with counter cache.
334
+
335
+ Fixes #31559.
336
+
337
+ *Ryuta Kamizono*
338
+
339
+ * `becomes` should clear the mutation tracker which is created in `after_initialize`.
340
+
341
+ Fixes #32867.
342
+
343
+ *Ryuta Kamizono*
344
+
345
+ * Allow a belonging to parent object to be created from a new record.
346
+
347
+ *Jolyon Pawlyn*
348
+
349
+ * Fix that building record with assigning multiple has_one associations
350
+ wrongly persists through record.
351
+
352
+ Fixes #32511.
353
+
354
+ *Sam DeCesare*
355
+
356
+ * Fix relation merging when one of the relations is going to skip the
357
+ query cache.
358
+
359
+ *James Williams*
360
+
361
+
1
362
  ## Rails 5.2.0 (April 09, 2018) ##
2
363
 
3
364
  * MySQL: Support mysql2 0.5.x.
@@ -31,9 +31,9 @@ module ActiveRecord
31
31
  private
32
32
 
33
33
  def exec_queries
34
- super do |r|
35
- @association.set_inverse_instance r
36
- yield r if block_given?
34
+ super do |record|
35
+ @association.set_inverse_instance_from_queries(record)
36
+ yield record if block_given?
37
37
  end
38
38
  end
39
39
  end
@@ -33,7 +33,7 @@ module ActiveRecord
33
33
  elsif join.is_a?(Arel::Nodes::Join)
34
34
  join.left.name == name ? 1 : 0
35
35
  elsif join.is_a?(Hash)
36
- join.fetch(name, 0)
36
+ join[name]
37
37
  else
38
38
  raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join"
39
39
  end
@@ -19,7 +19,6 @@ module ActiveRecord
19
19
  # HasManyThroughAssociation + ThroughAssociation
20
20
  class Association #:nodoc:
21
21
  attr_reader :owner, :target, :reflection
22
- attr_accessor :inversed
23
22
 
24
23
  delegate :options, to: :reflection
25
24
 
@@ -67,7 +66,7 @@ module ActiveRecord
67
66
  #
68
67
  # Note that if the target has not been loaded, it is not considered stale.
69
68
  def stale_target?
70
- !inversed && loaded? && @stale_state != stale_state
69
+ !@inversed && loaded? && @stale_state != stale_state
71
70
  end
72
71
 
73
72
  # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
@@ -98,23 +97,32 @@ module ActiveRecord
98
97
 
99
98
  # Set the inverse association, if possible
100
99
  def set_inverse_instance(record)
101
- if invertible_for?(record)
102
- inverse = record.association(inverse_reflection_for(record).name)
103
- inverse.target = owner
104
- inverse.inversed = true
100
+ if inverse = inverse_association_for(record)
101
+ inverse.inversed_from(owner)
102
+ end
103
+ record
104
+ end
105
+
106
+ def set_inverse_instance_from_queries(record)
107
+ if inverse = inverse_association_for(record)
108
+ inverse.inversed_from_queries(owner)
105
109
  end
106
110
  record
107
111
  end
108
112
 
109
113
  # Remove the inverse association, if possible
110
114
  def remove_inverse_instance(record)
111
- if invertible_for?(record)
112
- inverse = record.association(inverse_reflection_for(record).name)
113
- inverse.target = nil
114
- inverse.inversed = false
115
+ if inverse = inverse_association_for(record)
116
+ inverse.inversed_from(nil)
115
117
  end
116
118
  end
117
119
 
120
+ def inversed_from(record)
121
+ self.target = record
122
+ @inversed = !!record
123
+ end
124
+ alias :inversed_from_queries :inversed_from
125
+
118
126
  # Returns the class of the target. belongs_to polymorphic overrides this to look at the
119
127
  # polymorphic_type field on the owner.
120
128
  def klass
@@ -240,6 +248,12 @@ module ActiveRecord
240
248
  end
241
249
  end
242
250
 
251
+ def inverse_association_for(record)
252
+ if invertible_for?(record)
253
+ record.association(inverse_reflection_for(record).name)
254
+ end
255
+ end
256
+
243
257
  # Can be redefined by subclasses, notably polymorphic belongs_to
244
258
  # The record parameter is necessary to support polymorphic inverses as we must check for
245
259
  # the association in the specific class of the record.
@@ -269,6 +283,7 @@ module ActiveRecord
269
283
  def build_record(attributes)
270
284
  reflection.build_association(attributes) do |record|
271
285
  initialize_attributes(record, attributes)
286
+ yield(record) if block_given?
272
287
  end
273
288
  end
274
289
 
@@ -26,10 +26,12 @@ module ActiveRecord
26
26
  decrement_counters
27
27
  end
28
28
 
29
+ replace_keys(record)
30
+
29
31
  self.target = record
30
32
  end
31
33
 
32
- def target=(record)
34
+ def inversed_from(record)
33
35
  replace_keys(record)
34
36
  super
35
37
  end
@@ -55,6 +57,10 @@ module ActiveRecord
55
57
  update_counters(1)
56
58
  end
57
59
 
60
+ def target_changed?
61
+ owner.saved_change_to_attribute?(reflection.foreign_key)
62
+ end
63
+
58
64
  private
59
65
 
60
66
  def update_counters(by)
@@ -78,19 +84,22 @@ module ActiveRecord
78
84
  def update_counters_on_replace(record)
79
85
  if require_counter_update? && different_target?(record)
80
86
  owner.instance_variable_set :@_after_replace_counter_called, true
81
- record.increment!(reflection.counter_cache_column)
87
+ record.increment!(reflection.counter_cache_column, touch: reflection.options[:touch])
82
88
  decrement_counters
83
89
  end
84
90
  end
85
91
 
86
92
  # Checks whether record is different to the current target, without loading it
87
93
  def different_target?(record)
88
- record.id != owner._read_attribute(reflection.foreign_key)
94
+ record._read_attribute(primary_key(record)) != owner._read_attribute(reflection.foreign_key)
89
95
  end
90
96
 
91
97
  def replace_keys(record)
92
- owner[reflection.foreign_key] = record ?
93
- record._read_attribute(reflection.association_primary_key(record.class)) : nil
98
+ owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record)) : nil
99
+ end
100
+
101
+ def primary_key(record)
102
+ reflection.association_primary_key(record.class)
94
103
  end
95
104
 
96
105
  def foreign_key_present?
@@ -9,8 +9,11 @@ module ActiveRecord
9
9
  type.presence && type.constantize
10
10
  end
11
11
 
12
- private
12
+ def target_changed?
13
+ super || owner.saved_change_to_attribute?(reflection.foreign_type)
14
+ end
13
15
 
16
+ private
14
17
  def replace_keys(record)
15
18
  super
16
19
  owner[reflection.foreign_type] = record ? record.class.polymorphic_name : nil
@@ -36,7 +36,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
36
36
 
37
37
  if (@_after_replace_counter_called ||= false)
38
38
  @_after_replace_counter_called = false
39
- elsif saved_change_to_attribute?(foreign_key) && !new_record?
39
+ elsif association(reflection.name).target_changed?
40
40
  if reflection.polymorphic?
41
41
  model = attribute_in_database(reflection.foreign_type).try(:constantize)
42
42
  model_was = attribute_before_last_save(reflection.foreign_type).try(:constantize)
@@ -49,14 +49,22 @@ module ActiveRecord::Associations::Builder # :nodoc:
49
49
  foreign_key = attribute_in_database foreign_key
50
50
 
51
51
  if foreign_key && model.respond_to?(:increment_counter)
52
+ foreign_key = counter_cache_target(reflection, model, foreign_key)
52
53
  model.increment_counter(cache_column, foreign_key)
53
54
  end
54
55
 
55
56
  if foreign_key_was && model_was.respond_to?(:decrement_counter)
57
+ foreign_key_was = counter_cache_target(reflection, model_was, foreign_key_was)
56
58
  model_was.decrement_counter(cache_column, foreign_key_was)
57
59
  end
58
60
  end
59
61
  end
62
+
63
+ private
64
+ def counter_cache_target(reflection, model, foreign_key)
65
+ primary_key = reflection.association_primary_key(model)
66
+ model.unscoped.where!(primary_key => foreign_key)
67
+ end
60
68
  end
61
69
  end
62
70
 
@@ -84,7 +92,8 @@ module ActiveRecord::Associations::Builder # :nodoc:
84
92
  else
85
93
  klass = association.klass
86
94
  end
87
- old_record = klass.find_by(klass.primary_key => old_foreign_id)
95
+ primary_key = reflection.association_primary_key(klass)
96
+ old_record = klass.find_by(primary_key => old_foreign_id)
88
97
 
89
98
  if old_record
90
99
  if touch != true
@@ -20,10 +20,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
20
20
  }
21
21
  end
22
22
 
23
- def self.define_extensions(model, name)
23
+ def self.define_extensions(model, name, &block)
24
24
  if block_given?
25
25
  extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
26
- extension = Module.new(&Proc.new)
26
+ extension = Module.new(&block)
27
27
  model.parent.const_set(extension_module_name, extension)
28
28
  end
29
29
  end
@@ -45,6 +45,8 @@ module ActiveRecord
45
45
  def ids_reader
46
46
  if loaded?
47
47
  target.pluck(reflection.association_primary_key)
48
+ elsif !target.empty?
49
+ load_target.pluck(reflection.association_primary_key)
48
50
  else
49
51
  @association_ids ||= scope.pluck(reflection.association_primary_key)
50
52
  end
@@ -103,15 +105,12 @@ module ActiveRecord
103
105
  if attributes.is_a?(Array)
104
106
  attributes.collect { |attr| build(attr, &block) }
105
107
  else
106
- add_to_target(build_record(attributes)) do |record|
107
- yield(record) if block_given?
108
- end
108
+ add_to_target(build_record(attributes, &block))
109
109
  end
110
110
  end
111
111
 
112
- # Add +records+ to this association. Returns +self+ so method calls may
113
- # be chained. Since << flattens its argument list and inserts each record,
114
- # +push+ and +concat+ behave identically.
112
+ # Add +records+ to this association. Since +<<+ flattens its argument list
113
+ # and inserts each record, +push+ and +concat+ behave identically.
115
114
  def concat(*records)
116
115
  records = records.flatten
117
116
  if owner.new_record?
@@ -356,15 +355,17 @@ module ActiveRecord
356
355
  if attributes.is_a?(Array)
357
356
  attributes.collect { |attr| _create_record(attr, raise, &block) }
358
357
  else
358
+ record = build_record(attributes, &block)
359
359
  transaction do
360
- add_to_target(build_record(attributes)) do |record|
361
- yield(record) if block_given?
362
- insert_record(record, true, raise) {
360
+ result = nil
361
+ add_to_target(record) do
362
+ result = insert_record(record, true, raise) {
363
363
  @_was_loaded = loaded?
364
- @association_ids = nil
365
364
  }
366
365
  end
366
+ raise ActiveRecord::Rollback unless result
367
367
  end
368
+ record
368
369
  end
369
370
  end
370
371
 
@@ -396,6 +397,7 @@ module ActiveRecord
396
397
 
397
398
  delete_records(existing_records, method) if existing_records.any?
398
399
  records.each { |record| target.delete(record) }
400
+ @association_ids = nil
399
401
 
400
402
  records.each { |record| callback(:after_remove, record) }
401
403
  end
@@ -408,9 +410,9 @@ module ActiveRecord
408
410
  end
409
411
 
410
412
  def replace_records(new_target, original_target)
411
- delete(target - new_target)
413
+ delete(difference(target, new_target))
412
414
 
413
- unless concat(new_target - target)
415
+ unless concat(difference(new_target, target))
414
416
  @target = original_target
415
417
  raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
416
418
  "new records could not be saved."
@@ -420,7 +422,7 @@ module ActiveRecord
420
422
  end
421
423
 
422
424
  def replace_common_records_in_memory(new_target, original_target)
423
- common_records = new_target & original_target
425
+ common_records = intersection(new_target, original_target)
424
426
  common_records.each do |record|
425
427
  skip_callbacks = true
426
428
  replace_on_target(record, @target.index(record), skip_callbacks)
@@ -436,13 +438,14 @@ module ActiveRecord
436
438
  unless owner.new_record?
437
439
  result &&= insert_record(record, true, raise) {
438
440
  @_was_loaded = loaded?
439
- @association_ids = nil
440
441
  }
441
442
  end
442
443
  end
443
444
  end
444
445
 
445
- result && records
446
+ raise ActiveRecord::Rollback unless result
447
+
448
+ records
446
449
  end
447
450
 
448
451
  def replace_on_target(record, index, skip_callbacks)
@@ -457,6 +460,7 @@ module ActiveRecord
457
460
  if index
458
461
  target[index] = record
459
462
  elsif @_was_loaded || !loaded?
463
+ @association_ids = nil
460
464
  target << record
461
465
  end
462
466