activerecord 6.1.0 → 6.1.4

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +305 -17
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/aggregations.rb +4 -4
  5. data/lib/active_record/association_relation.rb +10 -0
  6. data/lib/active_record/associations/association.rb +13 -7
  7. data/lib/active_record/associations/association_scope.rb +7 -5
  8. data/lib/active_record/associations/belongs_to_association.rb +8 -5
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -2
  11. data/lib/active_record/associations/builder/belongs_to.rb +2 -2
  12. data/lib/active_record/associations/has_many_association.rb +1 -1
  13. data/lib/active_record/associations/join_dependency/join_association.rb +8 -7
  14. data/lib/active_record/associations/join_dependency.rb +1 -1
  15. data/lib/active_record/associations.rb +6 -2
  16. data/lib/active_record/attributes.rb +1 -1
  17. data/lib/active_record/coders/yaml_column.rb +11 -1
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +4 -4
  19. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  20. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
  23. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +7 -1
  24. data/lib/active_record/connection_adapters/abstract/transaction.rb +14 -3
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +10 -11
  26. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +6 -2
  27. data/lib/active_record/connection_adapters/mysql/quoting.rb +17 -2
  28. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +4 -1
  29. data/lib/active_record/connection_adapters/pool_config.rb +13 -3
  30. data/lib/active_record/connection_adapters/pool_manager.rb +5 -1
  31. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  32. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  33. data/lib/active_record/connection_adapters/postgresql_adapter.rb +2 -8
  34. data/lib/active_record/connection_adapters/schema_cache.rb +9 -1
  35. data/lib/active_record/connection_adapters/sql_type_metadata.rb +0 -2
  36. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
  37. data/lib/active_record/connection_adapters.rb +2 -0
  38. data/lib/active_record/connection_handling.rb +20 -12
  39. data/lib/active_record/core.rb +58 -29
  40. data/lib/active_record/database_configurations/url_config.rb +1 -1
  41. data/lib/active_record/enum.rb +52 -34
  42. data/lib/active_record/fixtures.rb +5 -2
  43. data/lib/active_record/gem_version.rb +1 -1
  44. data/lib/active_record/insert_all.rb +5 -1
  45. data/lib/active_record/locking/optimistic.rb +14 -4
  46. data/lib/active_record/log_subscriber.rb +3 -2
  47. data/lib/active_record/migration/compatibility.rb +2 -1
  48. data/lib/active_record/migration.rb +1 -1
  49. data/lib/active_record/model_schema.rb +4 -4
  50. data/lib/active_record/railties/console_sandbox.rb +2 -4
  51. data/lib/active_record/railties/databases.rake +16 -9
  52. data/lib/active_record/reflection.rb +1 -1
  53. data/lib/active_record/relation/calculations.rb +6 -2
  54. data/lib/active_record/relation/finder_methods.rb +1 -1
  55. data/lib/active_record/relation/predicate_builder/association_query_value.rb +3 -3
  56. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +9 -5
  57. data/lib/active_record/relation/predicate_builder.rb +5 -6
  58. data/lib/active_record/relation/query_methods.rb +9 -6
  59. data/lib/active_record/relation/where_clause.rb +17 -14
  60. data/lib/active_record/relation.rb +10 -17
  61. data/lib/active_record/scoping/default.rb +1 -3
  62. data/lib/active_record/signed_id.rb +1 -1
  63. data/lib/active_record/statement_cache.rb +2 -2
  64. data/lib/active_record/table_metadata.rb +6 -3
  65. data/lib/active_record/tasks/database_tasks.rb +1 -0
  66. data/lib/active_record/test_fixtures.rb +42 -1
  67. data/lib/active_record/transactions.rb +4 -2
  68. data/lib/active_record/validations/numericality.rb +1 -1
  69. data/lib/arel/collectors/bind.rb +2 -2
  70. data/lib/arel/collectors/composite.rb +3 -3
  71. data/lib/arel/collectors/sql_string.rb +1 -1
  72. data/lib/arel/collectors/substitute_binds.rb +1 -1
  73. data/lib/arel/nodes/homogeneous_in.rb +4 -0
  74. data/lib/arel/predications.rb +2 -2
  75. data/lib/arel/visitors/to_sql.rb +1 -1
  76. metadata +10 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 418812373f3ff5fd16133fe0928b94b80b78b8a366abfc23a205fcb2ccc5a3e3
4
- data.tar.gz: 70db1902a8fc82b1c46f8a7731ffb358357bc8cac29564a57881a732dbd2a65a
3
+ metadata.gz: 8533ac44f34eb20fbdc7d86aeef22c966a16ebe23e157db652a5bc1d6551429a
4
+ data.tar.gz: 2fe7a2768e779b4b5cd02f9da33231392199313c9acd4a2600a7b10f253ac7bd
5
5
  SHA512:
6
- metadata.gz: 31ae64f629cb62f63bc37c74a888d557f9f0b102d401f2f9cf62c7d31655d99e5161ae9bc7f8be874c7a9cf27ec879dde3c04075df5cb2b5868891c0db144a50
7
- data.tar.gz: cfc9585f3a6f53187707a9d96503269e3d4b1c5f1fe8c5f4db3bdefe8ce636042b6cf1fe9fb233491d1e8197f52293a7018a2f9925f1e34644ad1e30b134d59f
6
+ metadata.gz: e65080023ad359f9782e8c9f40437e0fb961b285cbe9e9ecb8efb8155defd814470a774ef086b5b9911de1c60cb7aa04e4f0006cdaa361344dfb41627f2a3fd3
7
+ data.tar.gz: 3c7e9ac5eea68d2e2ed2d4c6bc6a49f59b026682da56a695426454e20b6f4d25bb1d1dec4ecf651fbf42bce7231cf97a2cbf45751632471ffb7ffe3a6382a460
data/CHANGELOG.md CHANGED
@@ -1,3 +1,291 @@
1
+ ## Rails 6.1.4 (June 24, 2021) ##
2
+
3
+ * Do not try to rollback transactions that failed due to a `ActiveRecord::TransactionRollbackError`.
4
+
5
+ *Jamie McCarthy*
6
+
7
+ * Raise an error if `pool_config` is `nil` in `set_pool_config`.
8
+
9
+ *Eileen M. Uchitelle*
10
+
11
+ * Fix compatibility with `psych >= 4`.
12
+
13
+ Starting in Psych 4.0.0 `YAML.load` behaves like `YAML.safe_load`. To preserve compatibility
14
+ Active Record's schema cache loader and `YAMLColumn` now uses `YAML.unsafe_load` if available.
15
+
16
+ *Jean Boussier*
17
+
18
+ * Support using replicas when using `rails dbconsole`.
19
+
20
+ *Christopher Thornton*
21
+
22
+ * Restore connection pools after transactional tests.
23
+
24
+ *Eugene Kenny*
25
+
26
+ * Change `upsert_all` to fails cleanly for MySQL when `:unique_by` is used.
27
+
28
+ *Bastian Bartmann*
29
+
30
+ * Fix user-defined `self.default_scope` to respect table alias.
31
+
32
+ *Ryuta Kamizono*
33
+
34
+ * Clear `@cache_keys` cache after `update_all`, `delete_all`, `destroy_all`.
35
+
36
+ *Ryuta Kamizono*
37
+
38
+ * Changed Arel predications `contains` and `overlaps` to use
39
+ `quoted_node` so that PostgreSQL arrays are quoted properly.
40
+
41
+ *Bradley Priest*
42
+
43
+ * Fix `merge` when the `where` clauses have string contents.
44
+
45
+ *Ryuta Kamizono*
46
+
47
+ * Fix rollback of parent destruction with nested `dependent: :destroy`.
48
+
49
+ *Jacopo Beschi*
50
+
51
+ * Fix binds logging for `"WHERE ... IN ..."` statements.
52
+
53
+ *Ricardo Díaz*
54
+
55
+ * Handle `false` in relation strict loading checks.
56
+
57
+ Previously when a model had strict loading set to true and then had a
58
+ relation set `strict_loading` to false the false wasn't considered when
59
+ deciding whether to raise/warn about strict loading.
60
+
61
+ ```
62
+ class Dog < ActiveRecord::Base
63
+ self.strict_loading_by_default = true
64
+
65
+ has_many :treats, strict_loading: false
66
+ end
67
+ ```
68
+
69
+ In the example, `dog.treats` would still raise even though
70
+ `strict_loading` was set to false. This is a bug effecting more than
71
+ Active Storage which is why I made this PR superceeding #41461. We need
72
+ to fix this for all applications since the behavior is a little
73
+ surprising. I took the test from ##41461 and the code suggestion from #41453
74
+ with some additions.
75
+
76
+ *Eileen M. Uchitelle*, *Radamés Roriz*
77
+
78
+ * Fix numericality validator without precision.
79
+
80
+ *Ryuta Kamizono*
81
+
82
+ * Fix aggregate attribute on Enum types.
83
+
84
+ *Ryuta Kamizono*
85
+
86
+ * Fix `CREATE INDEX` statement generation for PostgreSQL.
87
+
88
+ *eltongo*
89
+
90
+ * Fix where clause on enum attribute when providing array of strings.
91
+
92
+ *Ryuta Kamizono*
93
+
94
+ * Fix `unprepared_statement` to work it when nesting.
95
+
96
+ *Ryuta Kamizono*
97
+
98
+
99
+ ## Rails 6.1.3.2 (May 05, 2021) ##
100
+
101
+ * No changes.
102
+
103
+
104
+ ## Rails 6.1.3.1 (March 26, 2021) ##
105
+
106
+ * No changes.
107
+
108
+
109
+ ## Rails 6.1.3 (February 17, 2021) ##
110
+
111
+ * Fix the MySQL adapter to always set the right collation and charset
112
+ to the connection session.
113
+
114
+ *Rafael Mendonça França*
115
+
116
+ * Fix MySQL adapter handling of time objects when prepared statements
117
+ are enabled.
118
+
119
+ *Rafael Mendonça França*
120
+
121
+ * Fix scoping in enum fields using conditions that would generate
122
+ an `IN` clause.
123
+
124
+ *Ryuta Kamizono*
125
+
126
+ * Skip optimised #exist? query when #include? is called on a relation
127
+ with a having clause
128
+
129
+ Relations that have aliased select values AND a having clause that
130
+ references an aliased select value would generate an error when
131
+ #include? was called, due to an optimisation that would generate
132
+ call #exists? on the relation instead, which effectively alters
133
+ the select values of the query (and thus removes the aliased select
134
+ values), but leaves the having clause intact. Because the having
135
+ clause is then referencing an aliased column that is no longer
136
+ present in the simplified query, an ActiveRecord::InvalidStatement
137
+ error was raised.
138
+
139
+ An sample query affected by this problem:
140
+
141
+ ```ruby
142
+ Author.select('COUNT(*) as total_posts', 'authors.*')
143
+ .joins(:posts)
144
+ .group(:id)
145
+ .having('total_posts > 2')
146
+ .include?(Author.first)
147
+ ```
148
+
149
+ This change adds an addition check to the condition that skips the
150
+ simplified #exists? query, which simply checks for the presence of
151
+ a having clause.
152
+
153
+ Fixes #41417
154
+
155
+ *Michael Smart*
156
+
157
+ * Increment postgres prepared statement counter before making a prepared statement, so if the statement is aborted
158
+ without Rails knowledge (e.g., if app gets kill -9d during long-running query or due to Rack::Timeout), app won't end
159
+ up in perpetual crash state for being inconsistent with Postgres.
160
+
161
+ *wbharding*, *Martin Tepper*
162
+
163
+
164
+ ## Rails 6.1.2.1 (February 10, 2021) ##
165
+
166
+ * Fix possible DoS vector in PostgreSQL money type
167
+
168
+ Carefully crafted input can cause a DoS via the regular expressions used
169
+ for validating the money format in the PostgreSQL adapter. This patch
170
+ fixes the regexp.
171
+
172
+ Thanks to @dee-see from Hackerone for this patch!
173
+
174
+ [CVE-2021-22880]
175
+
176
+ *Aaron Patterson*
177
+
178
+
179
+ ## Rails 6.1.2 (February 09, 2021) ##
180
+
181
+ * Fix timestamp type for sqlite3.
182
+
183
+ *Eileen M. Uchitelle*
184
+
185
+ * Make destroy async transactional.
186
+
187
+ An active record rollback could occur while enqueuing a job. In this
188
+ case the job would enqueue even though the database deletion
189
+ rolledback putting things in a funky state.
190
+
191
+ Now the jobs are only enqueued until after the db transaction has been committed.
192
+
193
+ *Cory Gwin*
194
+
195
+ * Fix malformed packet error in MySQL statement for connection configuration.
196
+
197
+ *robinroestenburg*
198
+
199
+ * Connection specification now passes the "url" key as a configuration for the
200
+ adapter if the "url" protocol is "jdbc", "http", or "https". Previously only
201
+ urls with the "jdbc" prefix were passed to the Active Record Adapter, others
202
+ are assumed to be adapter specification urls.
203
+
204
+ Fixes #41137.
205
+
206
+ *Jonathan Bracy*
207
+
208
+ * Fix granular connection swapping when there are multiple abstract classes.
209
+
210
+ *Eileen M. Uchitelle*
211
+
212
+ * Fix `find_by` with custom primary key for belongs_to association.
213
+
214
+ *Ryuta Kamizono*
215
+
216
+ * Add support for `rails console --sandbox` for multiple database applications.
217
+
218
+ *alpaca-tc*
219
+
220
+ * Fix `where` on polymorphic association with empty array.
221
+
222
+ *Ryuta Kamizono*
223
+
224
+ * Fix preventing writes for `ApplicationRecord`.
225
+
226
+ *Eileen M. Uchitelle*
227
+
228
+
229
+ ## Rails 6.1.1 (January 07, 2021) ##
230
+
231
+ * Fix fixtures loading when strict loading is enabled for the association.
232
+
233
+ *Alex Ghiculescu*
234
+
235
+ * Fix `where` with custom primary key for belongs_to association.
236
+
237
+ *Ryuta Kamizono*
238
+
239
+ * Fix `where` with aliased associations.
240
+
241
+ *Ryuta Kamizono*
242
+
243
+ * Fix `composed_of` with symbol mapping.
244
+
245
+ *Ryuta Kamizono*
246
+
247
+ * Don't skip money's type cast for pluck and calculations.
248
+
249
+ *Ryuta Kamizono*
250
+
251
+ * Fix `where` on polymorphic association with non Active Record object.
252
+
253
+ *Ryuta Kamizono*
254
+
255
+ * Make sure `db:prepare` works even the schema file doesn't exist.
256
+
257
+ *Rafael Mendonça França*
258
+
259
+ * Fix complicated `has_many :through` with nested where condition.
260
+
261
+ *Ryuta Kamizono*
262
+
263
+ * Handle STI models for `has_many dependent: :destroy_async`.
264
+
265
+ *Muhammad Usman*
266
+
267
+ * Restore possibility of passing `false` to :polymorphic option of `belongs_to`.
268
+
269
+ Previously, passing `false` would trigger the option validation logic
270
+ to throw an error saying :polymorphic would not be a valid option.
271
+
272
+ *glaszig*
273
+
274
+ * Allow adding nonnamed expression indexes to be revertible.
275
+
276
+ Fixes #40732.
277
+
278
+ Previously, the following code would raise an error, when executed while rolling back,
279
+ and the index name should be specified explicitly. Now, the index name is inferred
280
+ automatically.
281
+
282
+ ```ruby
283
+ add_index(:items, "to_tsvector('english', description)")
284
+ ```
285
+
286
+ *fatkodima*
287
+
288
+
1
289
  ## Rails 6.1.0 (December 09, 2020) ##
2
290
 
3
291
  * Only warn about negative enums if a positive form that would cause conflicts exists.
@@ -38,21 +326,21 @@
38
326
 
39
327
  Before:
40
328
 
41
- AnimalsRecord.connected_to(role: :reading) do
42
- MealsRecord.connected_to(role: :reading) do
43
- Dog.first # read from animals replica
44
- Dinner.first # read from meals replica
45
- Person.first # read from primary writer
329
+ AnimalsRecord.connected_to(role: :reading) do
330
+ MealsRecord.connected_to(role: :reading) do
331
+ Dog.first # read from animals replica
332
+ Dinner.first # read from meals replica
333
+ Person.first # read from primary writer
334
+ end
46
335
  end
47
- end
48
336
 
49
337
  After:
50
338
 
51
- ActiveRecord::Base.connected_to_many([AnimalsRecord, MealsRecord], role: :reading) do
52
- Dog.first # read from animals replica
53
- Dinner.first # read from meals replica
54
- Person.first # read from primary writer
55
- end
339
+ ActiveRecord::Base.connected_to_many([AnimalsRecord, MealsRecord], role: :reading) do
340
+ Dog.first # read from animals replica
341
+ Dinner.first # read from meals replica
342
+ Person.first # read from primary writer
343
+ end
56
344
 
57
345
  *Eileen M. Uchitelle*, *John Crepezzi*
58
346
 
@@ -170,13 +458,13 @@
170
458
 
171
459
  Before:
172
460
 
173
- User.where.not(name: "Jon", role: "admin")
174
- # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
461
+ User.where.not(name: "Jon", role: "admin")
462
+ # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
175
463
 
176
464
  After:
177
465
 
178
- User.where.not(name: "Jon", role: "admin")
179
- # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
466
+ User.where.not(name: "Jon", role: "admin")
467
+ # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
180
468
 
181
469
  *Rafael Mendonça França*
182
470
 
@@ -863,14 +1151,14 @@
863
1151
  Deprecated behavior:
864
1152
 
865
1153
  ```ruby
866
- db_config = ActiveRecord::Base.configs_for(env_name: "development", spec_name: "primary")
1154
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: "development", spec_name: "primary")
867
1155
  db_config.spec_name
868
1156
  ```
869
1157
 
870
1158
  New behavior:
871
1159
 
872
1160
  ```ruby
873
- db_config = ActiveRecord::Base.configs_for(env_name: "development", name: "primary")
1161
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: "development", name: "primary")
874
1162
  db_config.name
875
1163
  ```
876
1164
 
data/README.rdoc CHANGED
@@ -194,7 +194,7 @@ The latest version of Active Record can be installed with RubyGems:
194
194
 
195
195
  Source code can be downloaded as part of the Rails project on GitHub:
196
196
 
197
- * https://github.com/rails/rails/tree/master/activerecord
197
+ * https://github.com/rails/rails/tree/main/activerecord
198
198
 
199
199
 
200
200
  == License
@@ -244,8 +244,8 @@ module ActiveRecord
244
244
  private
245
245
  def reader_method(name, class_name, mapping, allow_nil, constructor)
246
246
  define_method(name) do
247
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? { |key, _| !_read_attribute(key).nil? })
248
- attrs = mapping.collect { |key, _| _read_attribute(key) }
247
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? { |key, _| !read_attribute(key).nil? })
248
+ attrs = mapping.collect { |key, _| read_attribute(key) }
249
249
  object = constructor.respond_to?(:call) ?
250
250
  constructor.call(*attrs) :
251
251
  class_name.constantize.send(constructor, *attrs)
@@ -271,10 +271,10 @@ module ActiveRecord
271
271
  end
272
272
 
273
273
  if part.nil? && allow_nil
274
- mapping.each { |key, _| self[key] = nil }
274
+ mapping.each { |key, _| write_attribute(key, nil) }
275
275
  @aggregation_cache[name] = nil
276
276
  else
277
- mapping.each { |key, value| self[key] = part.send(value) }
277
+ mapping.each { |key, value| write_attribute(key, part.send(value)) }
278
278
  @aggregation_cache[name] = part.freeze
279
279
  end
280
280
  end
@@ -27,6 +27,16 @@ module ActiveRecord
27
27
  RUBY
28
28
  end
29
29
 
30
+ def build(attributes = nil, &block)
31
+ if attributes.is_a?(Array)
32
+ attributes.collect { |attr| build(attr, &block) }
33
+ else
34
+ block = current_scope_restoring_block(&block)
35
+ scoping { _new(attributes, &block) }
36
+ end
37
+ end
38
+ alias new build
39
+
30
40
  private
31
41
  def _new(attributes, &block)
32
42
  @association.build(attributes, &block)
@@ -211,12 +211,8 @@ module ActiveRecord
211
211
 
212
212
  private
213
213
  def find_target
214
- if owner.strict_loading? && owner.validation_context.nil?
215
- Base.strict_loading_violation!(owner: owner.class, association: klass)
216
- end
217
-
218
- if reflection.strict_loading? && owner.validation_context.nil?
219
- Base.strict_loading_violation!(owner: owner.class, association: reflection.name)
214
+ if strict_loading? && owner.validation_context.nil?
215
+ Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
220
216
  end
221
217
 
222
218
  scope = self.scope
@@ -231,6 +227,12 @@ module ActiveRecord
231
227
  sc.execute(binds, klass.connection) { |record| set_inverse_instance(record) }
232
228
  end
233
229
 
230
+ def strict_loading?
231
+ return reflection.strict_loading? if reflection.options.key?(:strict_loading)
232
+
233
+ owner.strict_loading?
234
+ end
235
+
234
236
  # The scope for this association.
235
237
  #
236
238
  # Note that the association_scope is merged into the target_scope only when the
@@ -331,7 +333,11 @@ module ActiveRecord
331
333
  end
332
334
 
333
335
  def enqueue_destroy_association(options)
334
- owner.class.destroy_association_async_job&.perform_later(**options)
336
+ job_class = owner.class.destroy_association_async_job
337
+
338
+ if job_class
339
+ owner._after_commit_jobs.push([job_class, options])
340
+ end
335
341
  end
336
342
 
337
343
  def inversable?(record)
@@ -131,11 +131,13 @@ module ActiveRecord
131
131
  if scope_chain_item == chain_head.scope
132
132
  scope.merge! item.except(:where, :includes, :unscope, :order)
133
133
  elsif !item.references_values.empty?
134
- join_dependency = item.construct_join_dependency(
135
- item.eager_load_values | item.includes_values, Arel::Nodes::OuterJoin
136
- )
137
- scope.joins!(*item.joins_values, join_dependency)
138
- scope.left_outer_joins!(*item.left_outer_joins_values)
134
+ scope.merge! item.only(:joins, :left_outer_joins)
135
+
136
+ associations = item.eager_load_values | item.includes_values
137
+
138
+ unless associations.empty?
139
+ scope.joins! item.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
140
+ end
139
141
  end
140
142
 
141
143
  reflection.all_includes do
@@ -9,8 +9,7 @@ module ActiveRecord
9
9
 
10
10
  case options[:dependent]
11
11
  when :destroy
12
- target.destroy
13
- raise ActiveRecord::Rollback unless target.destroyed?
12
+ raise ActiveRecord::Rollback unless target.destroy
14
13
  when :destroy_async
15
14
  id = owner.public_send(reflection.foreign_key.to_sym)
16
15
  primary_key_column = reflection.active_record_primary_key.to_sym
@@ -80,7 +79,7 @@ module ActiveRecord
80
79
  @updated = true
81
80
  end
82
81
 
83
- replace_keys(record)
82
+ replace_keys(record, force: true)
84
83
 
85
84
  self.target = record
86
85
  end
@@ -108,8 +107,12 @@ module ActiveRecord
108
107
  reflection.counter_cache_column && owner.persisted?
109
108
  end
110
109
 
111
- def replace_keys(record)
112
- owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record.class)) : nil
110
+ def replace_keys(record, force: false)
111
+ target_key = record ? record._read_attribute(primary_key(record.class)) : nil
112
+
113
+ if force || owner[reflection.foreign_key] != target_key
114
+ owner[reflection.foreign_key] = target_key
115
+ end
113
116
  end
114
117
 
115
118
  def primary_key(klass)
@@ -14,9 +14,14 @@ module ActiveRecord
14
14
  end
15
15
 
16
16
  private
17
- def replace_keys(record)
17
+ def replace_keys(record, force: false)
18
18
  super
19
- owner[reflection.foreign_type] = record ? record.class.polymorphic_name : nil
19
+
20
+ target_type = record ? record.class.polymorphic_name : nil
21
+
22
+ if force || owner[reflection.foreign_type] != target_type
23
+ owner[reflection.foreign_type] = target_type
24
+ end
20
25
  end
21
26
 
22
27
  def inverse_reflection_for(record)
@@ -76,6 +76,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
76
76
  if dependent = reflection.options[:dependent]
77
77
  check_dependent_options(dependent, model)
78
78
  add_destroy_callbacks(model, reflection)
79
+ add_after_commit_jobs_callback(model, dependent)
79
80
  end
80
81
 
81
82
  Association.extensions.each do |extension|
@@ -132,11 +133,31 @@ module ActiveRecord::Associations::Builder # :nodoc:
132
133
 
133
134
  def self.add_destroy_callbacks(model, reflection)
134
135
  name = reflection.name
135
- model.before_destroy lambda { |o| o.association(name).handle_dependency }
136
+ model.before_destroy(->(o) { o.association(name).handle_dependency })
137
+ end
138
+
139
+ def self.add_after_commit_jobs_callback(model, dependent)
140
+ if dependent == :destroy_async
141
+ mixin = model.generated_association_methods
142
+
143
+ unless mixin.method_defined?(:_after_commit_jobs)
144
+ model.after_commit(-> do
145
+ _after_commit_jobs.each do |job_class, job_arguments|
146
+ job_class.perform_later(**job_arguments)
147
+ end
148
+ end)
149
+
150
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
151
+ def _after_commit_jobs
152
+ @_after_commit_jobs ||= []
153
+ end
154
+ CODE
155
+ end
156
+ end
136
157
  end
137
158
 
138
159
  private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions,
139
160
  :define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations,
140
- :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks
161
+ :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks, :add_after_commit_jobs_callback
141
162
  end
142
163
  end
@@ -7,8 +7,8 @@ module ActiveRecord::Associations::Builder # :nodoc:
7
7
  end
8
8
 
9
9
  def self.valid_options(options)
10
- valid = super + [:counter_cache, :optional, :default]
11
- valid += [:polymorphic, :foreign_type] if options[:polymorphic]
10
+ valid = super + [:polymorphic, :counter_cache, :optional, :default]
11
+ valid += [:foreign_type] if options[:polymorphic]
12
12
  valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
13
13
  valid
14
14
  end
@@ -42,7 +42,7 @@ module ActiveRecord
42
42
  enqueue_destroy_association(
43
43
  owner_model_name: owner.class.to_s,
44
44
  owner_id: owner.id,
45
- association_class: association_class.to_s,
45
+ association_class: reflection.klass.to_s,
46
46
  association_ids: ids,
47
47
  association_primary_key_column: primary_key_column,
48
48
  ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
@@ -42,16 +42,17 @@ module ActiveRecord
42
42
  chain.reverse_each do |reflection, table|
43
43
  klass = reflection.klass
44
44
 
45
- join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
45
+ scope = reflection.join_scope(table, foreign_table, foreign_klass)
46
46
 
47
- unless join_scope.references_values.empty?
48
- join_dependency = join_scope.construct_join_dependency(
49
- join_scope.eager_load_values | join_scope.includes_values, Arel::Nodes::OuterJoin
50
- )
51
- join_scope.joins!(join_dependency)
47
+ unless scope.references_values.empty?
48
+ associations = scope.eager_load_values | scope.includes_values
49
+
50
+ unless associations.empty?
51
+ scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
52
+ end
52
53
  end
53
54
 
54
- arel = join_scope.arel(alias_tracker.aliases)
55
+ arel = scope.arel(alias_tracker.aliases)
55
56
  nodes = arel.constraints.first
56
57
 
57
58
  if nodes.is_a?(Arel::Nodes::And)
@@ -195,7 +195,7 @@ module ActiveRecord
195
195
  next table, true
196
196
  end
197
197
 
198
- table_name = @references[reflection.name.to_sym]
198
+ table_name = @references[reflection.name.to_sym]&.to_s
199
199
 
200
200
  table = alias_tracker.aliased_table_for(reflection.klass.arel_table, table_name) do
201
201
  name = reflection.alias_candidate(parent.table_name)
@@ -1371,7 +1371,9 @@ module ActiveRecord
1371
1371
  #
1372
1372
  # * <tt>nil</tt> do nothing (default).
1373
1373
  # * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
1374
- # * <tt>:destroy_async</tt> destroys all the associated objects in a background job.
1374
+ # * <tt>:destroy_async</tt> destroys all the associated objects in a background job. <b>WARNING:</b> Do not use
1375
+ # this option if the association is backed by foreign key constraints in your database. The foreign key
1376
+ # constraint actions will occur inside the same transaction that deletes its owner.
1375
1377
  # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
1376
1378
  # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Polymorphic type will also be nullified
1377
1379
  # on polymorphic associations. Callbacks are not executed.
@@ -1523,7 +1525,9 @@ module ActiveRecord
1523
1525
  #
1524
1526
  # * <tt>nil</tt> do nothing (default).
1525
1527
  # * <tt>:destroy</tt> causes the associated object to also be destroyed
1526
- # * <tt>:destroy_async</tt> causes all the associated object to be destroyed in a background job.
1528
+ # * <tt>:destroy_async</tt> causes the associated object to be destroyed in a background job. <b>WARNING:</b> Do not use
1529
+ # this option if the association is backed by foreign key constraints in your database. The foreign key
1530
+ # constraint actions will occur inside the same transaction that deletes its owner.
1527
1531
  # * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
1528
1532
  # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Polymorphic type column is also nullified
1529
1533
  # on polymorphic associations. Callbacks are not executed.
@@ -173,7 +173,7 @@ module ActiveRecord
173
173
  # class Money < Struct.new(:amount, :currency)
174
174
  # end
175
175
  #
176
- # class MoneyType < Type::Value
176
+ # class MoneyType < ActiveRecord::Type::Value
177
177
  # def initialize(currency_converter:)
178
178
  # @currency_converter = currency_converter
179
179
  # end