activerecord 7.2.4 → 8.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1013 -878
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/association.rb +69 -27
- data/lib/active_record/associations/association_scope.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +2 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +36 -8
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +7 -3
- data/lib/active_record/associations/collection_proxy.rb +26 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/foreign_association.rb +3 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/join_dependency.rb +3 -2
- data/lib/active_record/associations/preloader/association.rb +3 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +18 -5
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/base.rb +1 -2
- data/lib/active_record/callbacks.rb +1 -2
- data/lib/active_record/coders/column_serializer.rb +1 -1
- data/lib/active_record/coders/json.rb +16 -6
- data/lib/active_record/coders/yaml_column.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
- data/lib/active_record/connection_adapters/column.rb +14 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +25 -2
- data/lib/active_record/core.rb +33 -17
- data/lib/active_record/counter_cache.rb +35 -8
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +74 -9
- data/lib/active_record/database_configurations/url_config.rb +15 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/disable_joins_association_relation.rb +5 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +8 -8
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
- data/lib/active_record/encryption/encryptor.rb +28 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +33 -30
- data/lib/active_record/errors.rb +33 -9
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +76 -0
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +9 -4
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +31 -29
- data/lib/active_record/message_pack.rb +13 -0
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +56 -13
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +48 -42
- data/lib/active_record/model_schema.rb +40 -15
- data/lib/active_record/nested_attributes.rb +14 -12
- data/lib/active_record/persistence.rb +163 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +100 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +35 -30
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -38
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +54 -22
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +60 -43
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +73 -43
- data/lib/active_record/relation/merger.rb +12 -10
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +23 -8
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +200 -96
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +2 -9
- data/lib/active_record/relation.rb +127 -87
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +17 -10
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +44 -16
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +53 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +83 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +18 -4
- data/lib/active_record/test_fixtures.rb +52 -6
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +56 -32
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +13 -2
- data/lib/active_record/type/serialized.rb +16 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +85 -50
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +4 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +15 -12
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
data/README.rdoc
CHANGED
|
@@ -139,7 +139,7 @@ A short rundown of some of the major features:
|
|
|
139
139
|
|
|
140
140
|
* Database agnostic schema management with Migrations.
|
|
141
141
|
|
|
142
|
-
class AddSystemSettings < ActiveRecord::Migration[
|
|
142
|
+
class AddSystemSettings < ActiveRecord::Migration[8.1]
|
|
143
143
|
def up
|
|
144
144
|
create_table :system_settings do |t|
|
|
145
145
|
t.string :name
|
|
@@ -17,7 +17,7 @@ module ActiveRecord
|
|
|
17
17
|
|
|
18
18
|
%w(insert insert_all insert! insert_all! upsert upsert_all).each do |method|
|
|
19
19
|
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
20
|
-
def #{method}(
|
|
20
|
+
def #{method}(...)
|
|
21
21
|
if @association.reflection.through_reflection?
|
|
22
22
|
raise ArgumentError, "Bulk insert or upsert is currently not supported for has_many through association"
|
|
23
23
|
end
|
|
@@ -43,6 +43,7 @@ module ActiveRecord
|
|
|
43
43
|
def exec_queries
|
|
44
44
|
super do |record|
|
|
45
45
|
@association.set_inverse_instance_from_queries(record)
|
|
46
|
+
@association.set_strict_loading(record)
|
|
46
47
|
yield record if block_given?
|
|
47
48
|
end
|
|
48
49
|
end
|
|
@@ -34,7 +34,7 @@ module ActiveRecord
|
|
|
34
34
|
# the <tt>reflection</tt> object represents a <tt>:has_many</tt> macro.
|
|
35
35
|
class Association # :nodoc:
|
|
36
36
|
attr_accessor :owner
|
|
37
|
-
attr_reader :
|
|
37
|
+
attr_reader :reflection, :disable_joins
|
|
38
38
|
|
|
39
39
|
delegate :options, to: :reflection
|
|
40
40
|
|
|
@@ -50,6 +50,13 @@ module ActiveRecord
|
|
|
50
50
|
@skip_strict_loading = nil
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
def target
|
|
54
|
+
if @target.is_a?(Promise)
|
|
55
|
+
@target = @target.value
|
|
56
|
+
end
|
|
57
|
+
@target
|
|
58
|
+
end
|
|
59
|
+
|
|
53
60
|
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
|
|
54
61
|
def reset
|
|
55
62
|
@loaded = false
|
|
@@ -113,6 +120,14 @@ module ActiveRecord
|
|
|
113
120
|
@association_scope = nil
|
|
114
121
|
end
|
|
115
122
|
|
|
123
|
+
def set_strict_loading(record)
|
|
124
|
+
if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
|
|
125
|
+
record.strict_loading!
|
|
126
|
+
else
|
|
127
|
+
record.strict_loading!(false, mode: owner.strict_loading_mode)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
116
131
|
# Set the inverse association, if possible
|
|
117
132
|
def set_inverse_instance(record)
|
|
118
133
|
if inverse = inverse_association_for(record)
|
|
@@ -172,7 +187,7 @@ module ActiveRecord
|
|
|
172
187
|
# ActiveRecord::RecordNotFound is rescued within the method, and it is
|
|
173
188
|
# not reraised. The proxy is \reset and +nil+ is the return value.
|
|
174
189
|
def load_target
|
|
175
|
-
@target = find_target if (@stale_state && stale_target?) || find_target?
|
|
190
|
+
@target = find_target(async: false) if (@stale_state && stale_target?) || find_target?
|
|
176
191
|
|
|
177
192
|
loaded! unless loaded?
|
|
178
193
|
target
|
|
@@ -180,6 +195,13 @@ module ActiveRecord
|
|
|
180
195
|
reset
|
|
181
196
|
end
|
|
182
197
|
|
|
198
|
+
def async_load_target # :nodoc:
|
|
199
|
+
@target = find_target(async: true) if (@stale_state && stale_target?) || find_target?
|
|
200
|
+
|
|
201
|
+
loaded! unless loaded?
|
|
202
|
+
nil
|
|
203
|
+
end
|
|
204
|
+
|
|
183
205
|
# We can't dump @reflection and @through_reflection since it contains the scope proc
|
|
184
206
|
def marshal_dump
|
|
185
207
|
ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] }
|
|
@@ -210,7 +232,19 @@ module ActiveRecord
|
|
|
210
232
|
_create_record(attributes, true, &block)
|
|
211
233
|
end
|
|
212
234
|
|
|
213
|
-
|
|
235
|
+
def violates_strict_loading?
|
|
236
|
+
return unless find_target?
|
|
237
|
+
|
|
238
|
+
return if @skip_strict_loading
|
|
239
|
+
|
|
240
|
+
return unless owner.validation_context.nil?
|
|
241
|
+
|
|
242
|
+
return reflection.strict_loading? if reflection.options.key?(:strict_loading)
|
|
243
|
+
|
|
244
|
+
owner.strict_loading? && !owner.strict_loading_n_plus_one_only?
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Whether the association represents a single record
|
|
214
248
|
# or a collection of records.
|
|
215
249
|
def collection?
|
|
216
250
|
false
|
|
@@ -223,13 +257,19 @@ module ActiveRecord
|
|
|
223
257
|
klass
|
|
224
258
|
end
|
|
225
259
|
|
|
226
|
-
def find_target
|
|
260
|
+
def find_target(async: false)
|
|
227
261
|
if violates_strict_loading?
|
|
228
262
|
Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
|
|
229
263
|
end
|
|
230
264
|
|
|
231
265
|
scope = self.scope
|
|
232
|
-
|
|
266
|
+
if skip_statement_cache?(scope)
|
|
267
|
+
if async
|
|
268
|
+
return scope.load_async.then(&:to_a)
|
|
269
|
+
else
|
|
270
|
+
return scope.to_a
|
|
271
|
+
end
|
|
272
|
+
end
|
|
233
273
|
|
|
234
274
|
sc = reflection.association_scope_cache(klass, owner) do |params|
|
|
235
275
|
as = AssociationScope.create { params.bind }
|
|
@@ -238,13 +278,9 @@ module ActiveRecord
|
|
|
238
278
|
|
|
239
279
|
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
|
240
280
|
klass.with_connection do |c|
|
|
241
|
-
sc.execute(binds, c) do |record|
|
|
281
|
+
sc.execute(binds, c, async: async) do |record|
|
|
242
282
|
set_inverse_instance(record)
|
|
243
|
-
|
|
244
|
-
record.strict_loading!
|
|
245
|
-
else
|
|
246
|
-
record.strict_loading!(false, mode: owner.strict_loading_mode)
|
|
247
|
-
end
|
|
283
|
+
set_strict_loading(record)
|
|
248
284
|
end
|
|
249
285
|
end
|
|
250
286
|
end
|
|
@@ -257,16 +293,6 @@ module ActiveRecord
|
|
|
257
293
|
@skip_strict_loading = skip_strict_loading_was
|
|
258
294
|
end
|
|
259
295
|
|
|
260
|
-
def violates_strict_loading?
|
|
261
|
-
return if @skip_strict_loading
|
|
262
|
-
|
|
263
|
-
return unless owner.validation_context.nil?
|
|
264
|
-
|
|
265
|
-
return reflection.strict_loading? if reflection.options.key?(:strict_loading)
|
|
266
|
-
|
|
267
|
-
owner.strict_loading? && !owner.strict_loading_n_plus_one_only?
|
|
268
|
-
end
|
|
269
|
-
|
|
270
296
|
# The scope for this association.
|
|
271
297
|
#
|
|
272
298
|
# Note that the association_scope is merged into the target_scope only when the
|
|
@@ -385,12 +411,28 @@ module ActiveRecord
|
|
|
385
411
|
end
|
|
386
412
|
|
|
387
413
|
def matches_foreign_key?(record)
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
414
|
+
(foreign_key_for?(record) && record_foreign_key_matches_owner?(record)) ||
|
|
415
|
+
(foreign_key_for?(owner) && owner_foreign_key_matches_record?(record))
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def record_foreign_key_matches_owner?(record)
|
|
419
|
+
foreign_key_values(record) == active_record_primary_key_values(owner)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def owner_foreign_key_matches_record?(record)
|
|
423
|
+
foreign_key_values(owner) == association_primary_key_values(record)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def foreign_key_values(record)
|
|
427
|
+
Array(reflection.foreign_key).map { |key| record.read_attribute(key) }
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def active_record_primary_key_values(record)
|
|
431
|
+
Array(reflection.active_record_primary_key).map { |key| record.read_attribute(key) }
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def association_primary_key_values(record)
|
|
435
|
+
Array(reflection.association_primary_key(record.class)).map { |key| record.read_attribute(key) }
|
|
394
436
|
end
|
|
395
437
|
end
|
|
396
438
|
end
|
|
@@ -149,7 +149,7 @@ module ActiveRecord
|
|
|
149
149
|
scope.includes_values |= item.includes_values
|
|
150
150
|
end
|
|
151
151
|
|
|
152
|
-
scope.unscope!(*item.
|
|
152
|
+
scope.unscope!(*item.table_name_qualified_unscope_values)
|
|
153
153
|
scope.where_clause += item.where_clause
|
|
154
154
|
scope.order_values = item.order_values | scope.order_values
|
|
155
155
|
end
|
|
@@ -80,11 +80,11 @@ module ActiveRecord
|
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def target_changed?
|
|
83
|
-
owner.attribute_changed?(
|
|
83
|
+
Array(reflection.foreign_key).any? { |fk| owner.attribute_changed?(fk) } || (!foreign_key_present? && target&.new_record?)
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def target_previously_changed?
|
|
87
|
-
owner.attribute_previously_changed?(
|
|
87
|
+
Array(reflection.foreign_key).any? { |fk| owner.attribute_previously_changed?(fk) }
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
def saved_change_to_target?
|
|
@@ -19,7 +19,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
19
19
|
self.extensions = []
|
|
20
20
|
|
|
21
21
|
VALID_OPTIONS = [
|
|
22
|
-
:
|
|
22
|
+
:anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading, :query_constraints, :deprecated
|
|
23
23
|
].freeze # :nodoc:
|
|
24
24
|
|
|
25
25
|
def self.build(model, name, scope, options, &block)
|
|
@@ -30,10 +30,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
reflection = create_reflection(model, name, scope, options, &block)
|
|
33
|
-
define_accessors
|
|
34
|
-
define_callbacks
|
|
35
|
-
define_validations
|
|
36
|
-
define_change_tracking_methods
|
|
33
|
+
define_accessors(model, reflection)
|
|
34
|
+
define_callbacks(model, reflection)
|
|
35
|
+
define_validations(model, reflection)
|
|
36
|
+
define_change_tracking_methods(model, reflection)
|
|
37
37
|
reflection
|
|
38
38
|
end
|
|
39
39
|
|
|
@@ -71,6 +71,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def self.define_extensions(model, name)
|
|
74
|
+
# noop
|
|
74
75
|
end
|
|
75
76
|
|
|
76
77
|
def self.define_callbacks(model, reflection)
|
|
@@ -81,7 +82,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
Association.extensions.each do |extension|
|
|
84
|
-
extension.build
|
|
85
|
+
extension.build(model, reflection)
|
|
85
86
|
end
|
|
86
87
|
end
|
|
87
88
|
|
|
@@ -101,7 +102,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
101
102
|
def self.define_readers(mixin, name)
|
|
102
103
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
103
104
|
def #{name}
|
|
104
|
-
association(:#{name})
|
|
105
|
+
association = association(:#{name})
|
|
106
|
+
deprecated_associations_api_guard(association, __method__)
|
|
107
|
+
association.reader
|
|
105
108
|
end
|
|
106
109
|
CODE
|
|
107
110
|
end
|
|
@@ -109,7 +112,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
109
112
|
def self.define_writers(mixin, name)
|
|
110
113
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
111
114
|
def #{name}=(value)
|
|
112
|
-
association(:#{name})
|
|
115
|
+
association = association(:#{name})
|
|
116
|
+
deprecated_associations_api_guard(association, __method__)
|
|
117
|
+
association.writer(value)
|
|
113
118
|
end
|
|
114
119
|
CODE
|
|
115
120
|
end
|
|
@@ -131,14 +136,21 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
131
136
|
err_message = "A valid destroy_association_async_job is required to use `dependent: :destroy_async` on associations"
|
|
132
137
|
raise ActiveRecord::ConfigurationError, err_message
|
|
133
138
|
end
|
|
134
|
-
unless valid_dependent_options.include?
|
|
139
|
+
unless valid_dependent_options.include?(dependent)
|
|
135
140
|
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
|
|
136
141
|
end
|
|
137
142
|
end
|
|
138
143
|
|
|
139
144
|
def self.add_destroy_callbacks(model, reflection)
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
if reflection.deprecated?
|
|
146
|
+
# If :dependent is set, destroying the record has a side effect that
|
|
147
|
+
# would no longer happen if the association is removed.
|
|
148
|
+
model.before_destroy do
|
|
149
|
+
report_deprecated_association(reflection, context: ":dependent has a side effect here")
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
model.before_destroy(->(o) { o.association(reflection.name).handle_dependency })
|
|
142
154
|
end
|
|
143
155
|
|
|
144
156
|
def self.add_after_commit_jobs_callback(model, dependent)
|
|
@@ -8,8 +8,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
8
8
|
|
|
9
9
|
def self.valid_options(options)
|
|
10
10
|
valid = super + [:polymorphic, :counter_cache, :optional, :default]
|
|
11
|
-
valid
|
|
12
|
-
valid
|
|
11
|
+
valid << :class_name unless options[:polymorphic]
|
|
12
|
+
valid << :foreign_type if options[:polymorphic]
|
|
13
|
+
valid << :ensuring_owner_was if options[:dependent] == :destroy_async
|
|
13
14
|
valid
|
|
14
15
|
end
|
|
15
16
|
|
|
@@ -42,7 +43,16 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
def self.touch_record(o, changes, foreign_key, name, touch) # :nodoc:
|
|
45
|
-
old_foreign_id =
|
|
46
|
+
old_foreign_id =
|
|
47
|
+
if foreign_key.is_a?(Array)
|
|
48
|
+
if foreign_key.any? { |fk| changes[fk] }
|
|
49
|
+
foreign_key.map do |fk|
|
|
50
|
+
changes[fk] ? changes[fk].first : o.read_attribute(fk)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
elsif changes[foreign_key]
|
|
54
|
+
changes[foreign_key].first
|
|
55
|
+
end
|
|
46
56
|
|
|
47
57
|
if old_foreign_id
|
|
48
58
|
association = o.association(name)
|
|
@@ -55,7 +65,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
55
65
|
klass = association.klass
|
|
56
66
|
end
|
|
57
67
|
primary_key = reflection.association_primary_key(klass)
|
|
58
|
-
old_record = klass.find_by(primary_key => old_foreign_id)
|
|
68
|
+
old_record = klass.find_by(primary_key => [old_foreign_id])
|
|
59
69
|
|
|
60
70
|
if old_record
|
|
61
71
|
if touch != true
|
|
@@ -107,6 +117,14 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
107
117
|
end
|
|
108
118
|
|
|
109
119
|
def self.add_destroy_callbacks(model, reflection)
|
|
120
|
+
if reflection.deprecated?
|
|
121
|
+
# If :dependent is set, destroying the record has some side effect that
|
|
122
|
+
# would no longer happen if the association is removed.
|
|
123
|
+
model.before_destroy do
|
|
124
|
+
report_deprecated_association(reflection, context: ":dependent has a side effect here")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
110
128
|
model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency }
|
|
111
129
|
end
|
|
112
130
|
|
|
@@ -131,8 +149,14 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
131
149
|
foreign_key = reflection.foreign_key
|
|
132
150
|
foreign_type = reflection.foreign_type
|
|
133
151
|
|
|
134
|
-
|
|
135
|
-
record.attribute_changed?(
|
|
152
|
+
fk_missing_or_changed = if foreign_key.is_a?(Array)
|
|
153
|
+
foreign_key.any? { |fk| record.read_attribute(fk).nil? || record.attribute_changed?(fk) }
|
|
154
|
+
else
|
|
155
|
+
record.read_attribute(foreign_key).nil? ||
|
|
156
|
+
record.attribute_changed?(foreign_key)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
fk_missing_or_changed ||
|
|
136
160
|
(reflection.polymorphic? && (record.read_attribute(foreign_type).nil? || record.attribute_changed?(foreign_type)))
|
|
137
161
|
}
|
|
138
162
|
|
|
@@ -144,11 +168,15 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
144
168
|
def self.define_change_tracking_methods(model, reflection)
|
|
145
169
|
model.generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
146
170
|
def #{reflection.name}_changed?
|
|
147
|
-
association(:#{reflection.name})
|
|
171
|
+
association = association(:#{reflection.name})
|
|
172
|
+
deprecated_associations_api_guard(association, __method__)
|
|
173
|
+
association.target_changed?
|
|
148
174
|
end
|
|
149
175
|
|
|
150
176
|
def #{reflection.name}_previously_changed?
|
|
151
|
-
association(:#{reflection.name})
|
|
177
|
+
association = association(:#{reflection.name})
|
|
178
|
+
deprecated_associations_api_guard(association, __method__)
|
|
179
|
+
association.target_previously_changed?
|
|
152
180
|
end
|
|
153
181
|
CODE
|
|
154
182
|
end
|
|
@@ -7,7 +7,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
7
7
|
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
|
|
8
8
|
|
|
9
9
|
def self.valid_options(options)
|
|
10
|
-
super + [:before_add, :after_add, :before_remove, :after_remove, :extend]
|
|
10
|
+
super + [:class_name, :before_add, :after_add, :before_remove, :after_remove, :extend]
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def self.define_callbacks(model, reflection)
|
|
@@ -60,7 +60,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
60
60
|
|
|
61
61
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
62
62
|
def #{name.to_s.singularize}_ids
|
|
63
|
-
association(:#{name})
|
|
63
|
+
association = association(:#{name})
|
|
64
|
+
deprecated_associations_api_guard(association, __method__)
|
|
65
|
+
association.ids_reader
|
|
64
66
|
end
|
|
65
67
|
CODE
|
|
66
68
|
end
|
|
@@ -70,7 +72,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
70
72
|
|
|
71
73
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
72
74
|
def #{name.to_s.singularize}_ids=(ids)
|
|
73
|
-
association(:#{name})
|
|
75
|
+
association = association(:#{name})
|
|
76
|
+
deprecated_associations_api_guard(association, __method__)
|
|
77
|
+
association.ids_writer(ids)
|
|
74
78
|
end
|
|
75
79
|
CODE
|
|
76
80
|
end
|
|
@@ -7,7 +7,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def self.valid_options(options)
|
|
10
|
-
valid = super + [:as, :through]
|
|
10
|
+
valid = super + [:class_name, :as, :through]
|
|
11
11
|
valid += [:foreign_type] if options[:as]
|
|
12
12
|
valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
|
|
13
13
|
valid += [:source, :source_type, :disable_joins] if options[:through]
|
|
@@ -17,11 +17,15 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
17
17
|
|
|
18
18
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
19
19
|
def reload_#{name}
|
|
20
|
-
association(:#{name})
|
|
20
|
+
association = association(:#{name})
|
|
21
|
+
deprecated_associations_api_guard(association, __method__)
|
|
22
|
+
association.force_reload_reader
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def reset_#{name}
|
|
24
|
-
association(:#{name})
|
|
26
|
+
association = association(:#{name})
|
|
27
|
+
deprecated_associations_api_guard(association, __method__)
|
|
28
|
+
association.reset
|
|
25
29
|
end
|
|
26
30
|
CODE
|
|
27
31
|
end
|
|
@@ -30,19 +34,43 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
|
30
34
|
def self.define_constructors(mixin, name)
|
|
31
35
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
32
36
|
def build_#{name}(*args, &block)
|
|
33
|
-
association(:#{name})
|
|
37
|
+
association = association(:#{name})
|
|
38
|
+
deprecated_associations_api_guard(association, __method__)
|
|
39
|
+
association.build(*args, &block)
|
|
34
40
|
end
|
|
35
41
|
|
|
36
42
|
def create_#{name}(*args, &block)
|
|
37
|
-
association(:#{name})
|
|
43
|
+
association = association(:#{name})
|
|
44
|
+
deprecated_associations_api_guard(association, __method__)
|
|
45
|
+
association.create(*args, &block)
|
|
38
46
|
end
|
|
39
47
|
|
|
40
48
|
def create_#{name}!(*args, &block)
|
|
41
|
-
association(:#{name})
|
|
49
|
+
association = association(:#{name})
|
|
50
|
+
deprecated_associations_api_guard(association, __method__)
|
|
51
|
+
association.create!(*args, &block)
|
|
42
52
|
end
|
|
43
53
|
CODE
|
|
44
54
|
end
|
|
45
55
|
|
|
56
|
+
def self.define_callbacks(model, reflection)
|
|
57
|
+
super
|
|
58
|
+
|
|
59
|
+
# If the record is saved or destroyed and `:touch` is set, the parent
|
|
60
|
+
# record gets a timestamp updated. We want to know about it, because
|
|
61
|
+
# deleting the association would change that side-effect and perhaps there
|
|
62
|
+
# is code relying on it.
|
|
63
|
+
if reflection.deprecated? && reflection.options[:touch]
|
|
64
|
+
model.before_save do
|
|
65
|
+
report_deprecated_association(reflection, context: ":touch has a side effect here")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
model.before_destroy do
|
|
69
|
+
report_deprecated_association(reflection, context: ":touch has a side effect here")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
46
74
|
private_class_method :valid_options, :define_accessors, :define_constructors
|
|
47
75
|
end
|
|
48
76
|
end
|
|
@@ -61,15 +61,19 @@ module ActiveRecord
|
|
|
61
61
|
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
|
|
62
62
|
def ids_writer(ids)
|
|
63
63
|
primary_key = reflection.association_primary_key
|
|
64
|
-
pk_type = klass.type_for_attribute(primary_key)
|
|
65
64
|
ids = Array(ids).compact_blank
|
|
66
|
-
ids.map! { |id| pk_type.cast(id) }
|
|
67
65
|
|
|
68
66
|
records = if klass.composite_primary_key?
|
|
67
|
+
pk_types = primary_key.map { |column| klass.type_for_attribute(column) }
|
|
68
|
+
ids.map! { |id| pk_types.zip(id).map! { |type, value| type.cast(value) } }
|
|
69
|
+
|
|
69
70
|
klass.where(primary_key => ids).index_by do |record|
|
|
70
71
|
primary_key.map { |primary_key| record._read_attribute(primary_key) }
|
|
71
72
|
end
|
|
72
73
|
else
|
|
74
|
+
pk_type = klass.type_for_attribute(primary_key)
|
|
75
|
+
ids.map! { |id| pk_type.cast(id) }
|
|
76
|
+
|
|
73
77
|
klass.where(primary_key => ids).index_by do |record|
|
|
74
78
|
record._read_attribute(primary_key)
|
|
75
79
|
end
|
|
@@ -94,7 +98,7 @@ module ActiveRecord
|
|
|
94
98
|
def find(*args)
|
|
95
99
|
if options[:inverse_of] && loaded?
|
|
96
100
|
args_flatten = args.flatten
|
|
97
|
-
model = scope.
|
|
101
|
+
model = scope.model
|
|
98
102
|
|
|
99
103
|
if args_flatten.blank?
|
|
100
104
|
error_message = "Couldn't find #{model.name} without an ID"
|
|
@@ -110,7 +110,7 @@ module ActiveRecord
|
|
|
110
110
|
# # ]
|
|
111
111
|
|
|
112
112
|
# Finds an object in the collection responding to the +id+. Uses the same
|
|
113
|
-
# rules as ActiveRecord::FinderMethods.find.
|
|
113
|
+
# rules as ActiveRecord::FinderMethods.find. Raises ActiveRecord::RecordNotFound
|
|
114
114
|
# error if the object cannot be found.
|
|
115
115
|
#
|
|
116
116
|
# class Person < ActiveRecord::Base
|
|
@@ -726,6 +726,10 @@ module ActiveRecord
|
|
|
726
726
|
end
|
|
727
727
|
|
|
728
728
|
def pluck(*column_names)
|
|
729
|
+
if proxy_association.violates_strict_loading?
|
|
730
|
+
Base.strict_loading_violation!(owner: proxy_association.owner.class, reflection: proxy_association.reflection)
|
|
731
|
+
end
|
|
732
|
+
|
|
729
733
|
null_scope? ? scope.pluck(*column_names) : super
|
|
730
734
|
end
|
|
731
735
|
|
|
@@ -1125,14 +1129,32 @@ module ActiveRecord
|
|
|
1125
1129
|
super
|
|
1126
1130
|
end
|
|
1127
1131
|
|
|
1132
|
+
%w(insert insert_all insert! insert_all! upsert upsert_all).each do |method|
|
|
1133
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
1134
|
+
def #{method}(...)
|
|
1135
|
+
if @association&.target&.any? { |r| r.new_record? }
|
|
1136
|
+
association_name = @association.reflection.name
|
|
1137
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
|
1138
|
+
Using #{method} on association \#{association_name} with unpersisted records
|
|
1139
|
+
is deprecated and will be removed in Rails 8.2.
|
|
1140
|
+
The unpersisted records will be lost after this operation.
|
|
1141
|
+
Please either persist your records first or store them separately before
|
|
1142
|
+
calling #{method}.
|
|
1143
|
+
MSG
|
|
1144
|
+
scope.#{method}(...)
|
|
1145
|
+
else
|
|
1146
|
+
scope.#{method}(...).tap { reset }
|
|
1147
|
+
end
|
|
1148
|
+
end
|
|
1149
|
+
RUBY
|
|
1150
|
+
end
|
|
1151
|
+
|
|
1128
1152
|
delegate_methods = [
|
|
1129
1153
|
QueryMethods,
|
|
1130
1154
|
SpawnMethods,
|
|
1131
1155
|
].flat_map { |klass|
|
|
1132
1156
|
klass.public_instance_methods(false)
|
|
1133
|
-
} - self.public_instance_methods(false) - [:select] + [
|
|
1134
|
-
:scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all, :load_async
|
|
1135
|
-
]
|
|
1157
|
+
} - self.public_instance_methods(false) - [ :select ] + [ :scoping, :values, :load_async ]
|
|
1136
1158
|
|
|
1137
1159
|
delegate(*delegate_methods, to: :scope)
|
|
1138
1160
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/notifications"
|
|
4
|
+
require "active_support/core_ext/array/conversions"
|
|
5
|
+
|
|
6
|
+
module ActiveRecord::Associations::Deprecation # :nodoc:
|
|
7
|
+
EVENT = "deprecated_association.active_record"
|
|
8
|
+
private_constant :EVENT
|
|
9
|
+
|
|
10
|
+
MODES = [:warn, :raise, :notify].freeze
|
|
11
|
+
private_constant :MODES
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
attr_reader :mode, :backtrace
|
|
15
|
+
|
|
16
|
+
def mode=(value) # private setter
|
|
17
|
+
unless MODES.include?(value)
|
|
18
|
+
raise ArgumentError, "invalid deprecated associations mode #{value.inspect} (valid modes are #{MODES.map(&:inspect).to_sentence})"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
@mode = value
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def backtrace=(value)
|
|
25
|
+
@backtrace = !!value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def guard(reflection)
|
|
29
|
+
report(reflection, context: yield) if reflection.deprecated?
|
|
30
|
+
|
|
31
|
+
if reflection.through_reflection?
|
|
32
|
+
reflection.deprecated_nested_reflections.each do |deprecated_nested_reflection|
|
|
33
|
+
context = "referenced as nested association of the through #{reflection.active_record}##{reflection.name}"
|
|
34
|
+
report(deprecated_nested_reflection, context: context)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def report(reflection, context:)
|
|
40
|
+
reflection = user_facing_reflection(reflection)
|
|
41
|
+
|
|
42
|
+
message = +"The association #{reflection.active_record}##{reflection.name} is deprecated, #{context}"
|
|
43
|
+
message << " (#{backtrace_cleaner.first_clean_frame})"
|
|
44
|
+
|
|
45
|
+
case @mode
|
|
46
|
+
when :warn
|
|
47
|
+
message = [message, *clean_frames].join("\n\t") if @backtrace
|
|
48
|
+
ActiveRecord::Base.logger&.warn(message)
|
|
49
|
+
when :raise
|
|
50
|
+
error = ActiveRecord::DeprecatedAssociationError.new(message)
|
|
51
|
+
if set_backtrace_supports_array_of_locations?
|
|
52
|
+
error.set_backtrace(clean_locations)
|
|
53
|
+
else
|
|
54
|
+
error.set_backtrace(clean_frames)
|
|
55
|
+
end
|
|
56
|
+
raise error
|
|
57
|
+
else
|
|
58
|
+
payload = { reflection: reflection, message: message, location: backtrace_cleaner.first_clean_location }
|
|
59
|
+
payload[:backtrace] = clean_locations if @backtrace
|
|
60
|
+
ActiveSupport::Notifications.instrument(EVENT, payload)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
def backtrace_cleaner
|
|
66
|
+
ActiveRecord::LogSubscriber.backtrace_cleaner
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def clean_frames
|
|
70
|
+
backtrace_cleaner.clean(caller)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def clean_locations
|
|
74
|
+
backtrace_cleaner.clean_locations(caller_locations)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def set_backtrace_supports_array_of_locations?
|
|
78
|
+
@backtrace_supports_array_of_locations ||= Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def user_facing_reflection(reflection)
|
|
82
|
+
reflection.active_record.reflect_on_association(reflection.name)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
self.mode = :warn
|
|
87
|
+
self.backtrace = false
|
|
88
|
+
end
|