activerecord 8.0.2.1 → 8.1.0.beta1

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.
Files changed (159) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +459 -421
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/belongs_to_association.rb +9 -1
  7. data/lib/active_record/associations/builder/association.rb +16 -5
  8. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  9. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  10. data/lib/active_record/associations/builder/has_one.rb +1 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  12. data/lib/active_record/associations/collection_association.rb +3 -3
  13. data/lib/active_record/associations/collection_proxy.rb +22 -4
  14. data/lib/active_record/associations/deprecation.rb +88 -0
  15. data/lib/active_record/associations/errors.rb +3 -0
  16. data/lib/active_record/associations/join_dependency.rb +2 -0
  17. data/lib/active_record/associations/preloader/branch.rb +1 -0
  18. data/lib/active_record/associations.rb +159 -21
  19. data/lib/active_record/attribute_methods/query.rb +34 -0
  20. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  21. data/lib/active_record/attributes.rb +38 -24
  22. data/lib/active_record/base.rb +0 -1
  23. data/lib/active_record/coders/json.rb +14 -5
  24. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +2 -4
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
  26. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  27. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +384 -49
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -1
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  31. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  32. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  33. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
  35. data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -13
  37. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
  38. data/lib/active_record/connection_adapters/column.rb +17 -4
  39. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  45. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -16
  46. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  47. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  48. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  49. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
  50. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  51. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
  52. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
  53. data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -7
  54. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  55. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -27
  56. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  57. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  58. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
  59. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  60. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  61. data/lib/active_record/connection_adapters.rb +1 -0
  62. data/lib/active_record/connection_handling.rb +1 -1
  63. data/lib/active_record/core.rb +12 -9
  64. data/lib/active_record/counter_cache.rb +33 -8
  65. data/lib/active_record/database_configurations/database_config.rb +5 -1
  66. data/lib/active_record/database_configurations/hash_config.rb +56 -9
  67. data/lib/active_record/database_configurations/url_config.rb +13 -3
  68. data/lib/active_record/database_configurations.rb +7 -3
  69. data/lib/active_record/delegated_type.rb +2 -2
  70. data/lib/active_record/dynamic_matchers.rb +54 -69
  71. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  72. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  73. data/lib/active_record/encryption/encryptor.rb +27 -25
  74. data/lib/active_record/encryption/scheme.rb +1 -1
  75. data/lib/active_record/enum.rb +37 -20
  76. data/lib/active_record/errors.rb +20 -4
  77. data/lib/active_record/explain_registry.rb +0 -1
  78. data/lib/active_record/filter_attribute_handler.rb +73 -0
  79. data/lib/active_record/fixture_set/table_row.rb +19 -2
  80. data/lib/active_record/fixtures.rb +2 -2
  81. data/lib/active_record/gem_version.rb +3 -3
  82. data/lib/active_record/inheritance.rb +1 -1
  83. data/lib/active_record/insert_all.rb +12 -7
  84. data/lib/active_record/locking/optimistic.rb +7 -0
  85. data/lib/active_record/locking/pessimistic.rb +5 -0
  86. data/lib/active_record/log_subscriber.rb +1 -5
  87. data/lib/active_record/middleware/shard_selector.rb +34 -17
  88. data/lib/active_record/migration/command_recorder.rb +14 -1
  89. data/lib/active_record/migration/compatibility.rb +34 -24
  90. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  91. data/lib/active_record/migration.rb +31 -21
  92. data/lib/active_record/model_schema.rb +10 -7
  93. data/lib/active_record/nested_attributes.rb +2 -0
  94. data/lib/active_record/persistence.rb +34 -3
  95. data/lib/active_record/query_cache.rb +22 -15
  96. data/lib/active_record/query_logs.rb +7 -7
  97. data/lib/active_record/querying.rb +4 -4
  98. data/lib/active_record/railtie.rb +34 -5
  99. data/lib/active_record/railties/databases.rake +23 -19
  100. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  101. data/lib/active_record/railties/job_runtime.rb +10 -11
  102. data/lib/active_record/reflection.rb +42 -3
  103. data/lib/active_record/relation/batches.rb +26 -12
  104. data/lib/active_record/relation/calculations.rb +35 -25
  105. data/lib/active_record/relation/delegation.rb +0 -1
  106. data/lib/active_record/relation/finder_methods.rb +37 -21
  107. data/lib/active_record/relation/merger.rb +2 -2
  108. data/lib/active_record/relation/predicate_builder.rb +2 -2
  109. data/lib/active_record/relation/query_attribute.rb +3 -1
  110. data/lib/active_record/relation/query_methods.rb +43 -33
  111. data/lib/active_record/relation/spawn_methods.rb +6 -6
  112. data/lib/active_record/relation/where_clause.rb +7 -10
  113. data/lib/active_record/relation.rb +37 -15
  114. data/lib/active_record/result.rb +44 -21
  115. data/lib/active_record/sanitization.rb +2 -0
  116. data/lib/active_record/schema_dumper.rb +12 -10
  117. data/lib/active_record/scoping.rb +0 -1
  118. data/lib/active_record/secure_token.rb +3 -3
  119. data/lib/active_record/signed_id.rb +46 -18
  120. data/lib/active_record/statement_cache.rb +13 -9
  121. data/lib/active_record/store.rb +44 -19
  122. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  123. data/lib/active_record/tasks/database_tasks.rb +24 -35
  124. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  125. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  126. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  127. data/lib/active_record/test_databases.rb +11 -3
  128. data/lib/active_record/test_fixtures.rb +27 -2
  129. data/lib/active_record/testing/query_assertions.rb +8 -2
  130. data/lib/active_record/timestamp.rb +4 -2
  131. data/lib/active_record/transaction.rb +2 -5
  132. data/lib/active_record/transactions.rb +34 -10
  133. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  134. data/lib/active_record/type/internal/timezone.rb +7 -0
  135. data/lib/active_record/type/json.rb +15 -2
  136. data/lib/active_record/type/serialized.rb +11 -4
  137. data/lib/active_record/type/type_map.rb +1 -1
  138. data/lib/active_record/type_caster/connection.rb +2 -1
  139. data/lib/active_record/validations/associated.rb +1 -1
  140. data/lib/active_record.rb +68 -5
  141. data/lib/arel/alias_predication.rb +2 -0
  142. data/lib/arel/crud.rb +8 -11
  143. data/lib/arel/delete_manager.rb +5 -0
  144. data/lib/arel/nodes/count.rb +2 -2
  145. data/lib/arel/nodes/delete_statement.rb +4 -2
  146. data/lib/arel/nodes/function.rb +4 -10
  147. data/lib/arel/nodes/named_function.rb +2 -2
  148. data/lib/arel/nodes/node.rb +1 -1
  149. data/lib/arel/nodes/update_statement.rb +4 -2
  150. data/lib/arel/nodes.rb +0 -2
  151. data/lib/arel/select_manager.rb +13 -4
  152. data/lib/arel/update_manager.rb +5 -0
  153. data/lib/arel/visitors/dot.rb +2 -3
  154. data/lib/arel/visitors/postgresql.rb +55 -0
  155. data/lib/arel/visitors/sqlite.rb +55 -8
  156. data/lib/arel/visitors/to_sql.rb +5 -21
  157. data/lib/arel.rb +3 -1
  158. metadata +13 -9
  159. data/lib/active_record/normalization.rb +0 -163
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[8.0]
142
+ class AddSystemSettings < ActiveRecord::Migration[8.1]
143
143
  def up
144
144
  create_table :system_settings do |t|
145
145
  t.string :name
@@ -214,6 +214,6 @@ Bug reports for the Ruby on \Rails project can be filed here:
214
214
 
215
215
  * https://github.com/rails/rails/issues
216
216
 
217
- Feature requests should be discussed on the rails-core mailing list here:
217
+ Feature requests should be discussed on the rubyonrails-core forum here:
218
218
 
219
219
  * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -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}(attributes, **kwargs)
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
@@ -232,7 +232,7 @@ module ActiveRecord
232
232
  _create_record(attributes, true, &block)
233
233
  end
234
234
 
235
- # Whether the association represent a single record
235
+ # Whether the association represents a single record
236
236
  # or a collection of records.
237
237
  def collection?
238
238
  false
@@ -162,7 +162,15 @@ module ActiveRecord
162
162
  end
163
163
 
164
164
  def stale_state
165
- owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
165
+ foreign_key = reflection.foreign_key
166
+ if foreign_key.is_a?(Array)
167
+ attributes = foreign_key.map do |fk|
168
+ owner._read_attribute(fk) { |n| owner.send(:missing_attribute, n, caller) }
169
+ end
170
+ attributes if attributes.any?
171
+ else
172
+ owner._read_attribute(foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
173
+ end
166
174
  end
167
175
  end
168
176
  end
@@ -19,7 +19,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
19
19
  self.extensions = []
20
20
 
21
21
  VALID_OPTIONS = [
22
- :class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading, :query_constraints
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)
@@ -102,7 +102,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
102
102
  def self.define_readers(mixin, name)
103
103
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
104
104
  def #{name}
105
- association(:#{name}).reader
105
+ association = association(:#{name})
106
+ deprecated_associations_api_guard(association, __method__)
107
+ association.reader
106
108
  end
107
109
  CODE
108
110
  end
@@ -110,7 +112,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
110
112
  def self.define_writers(mixin, name)
111
113
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
112
114
  def #{name}=(value)
113
- association(:#{name}).writer(value)
115
+ association = association(:#{name})
116
+ deprecated_associations_api_guard(association, __method__)
117
+ association.writer(value)
114
118
  end
115
119
  CODE
116
120
  end
@@ -138,8 +142,15 @@ module ActiveRecord::Associations::Builder # :nodoc:
138
142
  end
139
143
 
140
144
  def self.add_destroy_callbacks(model, reflection)
141
- name = reflection.name
142
- model.before_destroy(->(o) { o.association(name).handle_dependency })
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 })
143
154
  end
144
155
 
145
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 += [:foreign_type] if options[:polymorphic]
12
- valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
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
 
@@ -107,6 +108,14 @@ module ActiveRecord::Associations::Builder # :nodoc:
107
108
  end
108
109
 
109
110
  def self.add_destroy_callbacks(model, reflection)
111
+ if reflection.deprecated?
112
+ # If :dependent is set, destroying the record has some side effect that
113
+ # would no longer happen if the association is removed.
114
+ model.before_destroy do
115
+ report_deprecated_association(reflection, context: ":dependent has a side effect here")
116
+ end
117
+ end
118
+
110
119
  model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency }
111
120
  end
112
121
 
@@ -144,11 +153,15 @@ module ActiveRecord::Associations::Builder # :nodoc:
144
153
  def self.define_change_tracking_methods(model, reflection)
145
154
  model.generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
146
155
  def #{reflection.name}_changed?
147
- association(:#{reflection.name}).target_changed?
156
+ association = association(:#{reflection.name})
157
+ deprecated_associations_api_guard(association, __method__)
158
+ association.target_changed?
148
159
  end
149
160
 
150
161
  def #{reflection.name}_previously_changed?
151
- association(:#{reflection.name}).target_previously_changed?
162
+ association = association(:#{reflection.name})
163
+ deprecated_associations_api_guard(association, __method__)
164
+ association.target_previously_changed?
152
165
  end
153
166
  CODE
154
167
  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}).ids_reader
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}).ids_writer(ids)
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}).force_reload_reader
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}).reset
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}).build(*args, &block)
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}).create(*args, &block)
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}).create!(*args, &block)
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
@@ -259,10 +259,10 @@ module ActiveRecord
259
259
  klass = reflection.klass
260
260
  return false unless record.is_a?(klass)
261
261
 
262
- if record.new_record?
263
- include_in_memory?(record)
264
- elsif loaded?
262
+ if loaded?
265
263
  target.include?(record)
264
+ elsif record.new_record?
265
+ include_in_memory?(record)
266
266
  else
267
267
  record_id = klass.composite_primary_key? ? klass.primary_key.zip(record.id).to_h : record.id
268
268
  scope.exists?(record_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. Returns ActiveRecord::RecordNotFound
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
@@ -1125,14 +1125,32 @@ module ActiveRecord
1125
1125
  super
1126
1126
  end
1127
1127
 
1128
+ %w(insert insert_all insert! insert_all! upsert upsert_all).each do |method|
1129
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
1130
+ def #{method}(...)
1131
+ if @association&.target&.any? { |r| r.new_record? }
1132
+ association_name = @association.reflection.name
1133
+ ActiveRecord.deprecator.warn(<<~MSG)
1134
+ Using #{method} on association \#{association_name} with unpersisted records
1135
+ is deprecated and will be removed in Rails 8.2.
1136
+ The unpersisted records will be lost after this operation.
1137
+ Please either persist your records first or store them separately before
1138
+ calling #{method}.
1139
+ MSG
1140
+ scope.#{method}(...)
1141
+ else
1142
+ scope.#{method}(...).tap { reset }
1143
+ end
1144
+ end
1145
+ RUBY
1146
+ end
1147
+
1128
1148
  delegate_methods = [
1129
1149
  QueryMethods,
1130
1150
  SpawnMethods,
1131
1151
  ].flat_map { |klass|
1132
1152
  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
- ]
1153
+ } - self.public_instance_methods(false) - [ :select ] + [ :scoping, :values, :load_async ]
1136
1154
 
1137
1155
  delegate(*delegate_methods, to: :scope)
1138
1156
 
@@ -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
@@ -262,4 +262,7 @@ module ActiveRecord
262
262
  end
263
263
  end
264
264
  end
265
+
266
+ class DeprecatedAssociationError < ActiveRecordError
267
+ end
265
268
  end
@@ -235,6 +235,8 @@ module ActiveRecord
235
235
  raise EagerLoadPolymorphicError.new(reflection)
236
236
  end
237
237
 
238
+ Deprecation.guard(reflection) { "referenced in query to join its table" }
239
+
238
240
  JoinAssociation.new(reflection, build(right, reflection.klass))
239
241
  end
240
242
  end
@@ -118,6 +118,7 @@ module ActiveRecord
118
118
  def loaders
119
119
  @loaders ||=
120
120
  grouped_records.flat_map do |reflection, reflection_records|
121
+ Deprecation.guard(reflection) { "referenced in query to preload records" }
121
122
  preloaders_for_reflection(reflection, reflection_records)
122
123
  end
123
124
  end