activerecord 7.1.5.2 → 8.1.2.1

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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -11,6 +11,7 @@ module ActiveRecord
11
11
  class_attribute :_reflections, instance_writer: false, default: {}
12
12
  class_attribute :aggregate_reflections, instance_writer: false, default: {}
13
13
  class_attribute :automatic_scope_inversing, instance_writer: false, default: false
14
+ class_attribute :automatically_invert_plural_associations, instance_writer: false, default: false
14
15
  end
15
16
 
16
17
  class << self
@@ -21,12 +22,12 @@ module ActiveRecord
21
22
 
22
23
  def add_reflection(ar, name, reflection)
23
24
  ar.clear_reflections_cache
24
- name = -name.to_s
25
+ name = name.to_sym
25
26
  ar._reflections = ar._reflections.except(name).merge!(name => reflection)
26
27
  end
27
28
 
28
29
  def add_aggregate_reflection(ar, name, reflection)
29
- ar.aggregate_reflections = ar.aggregate_reflections.merge(-name.to_s => reflection)
30
+ ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_sym => reflection)
30
31
  end
31
32
 
32
33
  private
@@ -67,7 +68,7 @@ module ActiveRecord
67
68
  # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
68
69
  #
69
70
  def reflect_on_aggregation(aggregation)
70
- aggregate_reflections[aggregation.to_s]
71
+ aggregate_reflections[aggregation.to_sym]
71
72
  end
72
73
 
73
74
  # Returns a Hash of name of the reflection as the key and an AssociationReflection as the value.
@@ -75,6 +76,10 @@ module ActiveRecord
75
76
  # Account.reflections # => {"balance" => AggregateReflection}
76
77
  #
77
78
  def reflections
79
+ normalized_reflections.stringify_keys
80
+ end
81
+
82
+ def normalized_reflections # :nodoc:
78
83
  @__reflections ||= begin
79
84
  ref = {}
80
85
 
@@ -83,13 +88,13 @@ module ActiveRecord
83
88
 
84
89
  if parent_reflection
85
90
  parent_name = parent_reflection.name
86
- ref[parent_name.to_s] = parent_reflection
91
+ ref[parent_name] = parent_reflection
87
92
  else
88
93
  ref[name] = reflection
89
94
  end
90
95
  end
91
96
 
92
- ref
97
+ ref.freeze
93
98
  end
94
99
  end
95
100
 
@@ -104,7 +109,7 @@ module ActiveRecord
104
109
  # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
105
110
  #
106
111
  def reflect_on_all_associations(macro = nil)
107
- association_reflections = reflections.values
112
+ association_reflections = normalized_reflections.values
108
113
  association_reflections.select! { |reflection| reflection.macro == macro } if macro
109
114
  association_reflections
110
115
  end
@@ -115,16 +120,18 @@ module ActiveRecord
115
120
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
116
121
  #
117
122
  def reflect_on_association(association)
118
- reflections[association.to_s]
123
+ normalized_reflections[association.to_sym]
119
124
  end
120
125
 
121
126
  def _reflect_on_association(association) # :nodoc:
122
- _reflections[association.to_s]
127
+ _reflections[association.to_sym]
123
128
  end
124
129
 
125
130
  # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
126
131
  def reflect_on_all_autosave_associations
127
- reflections.values.select { |reflection| reflection.options[:autosave] }
132
+ reflections = normalized_reflections.values
133
+ reflections.select! { |reflection| reflection.options[:autosave] }
134
+ reflections
128
135
  end
129
136
 
130
137
  def clear_reflections_cache # :nodoc:
@@ -191,7 +198,7 @@ module ActiveRecord
191
198
  end
192
199
 
193
200
  def join_scope(table, foreign_table, foreign_klass)
194
- predicate_builder = predicate_builder(table)
201
+ predicate_builder = klass.predicate_builder.with(TableMetadata.new(klass, table))
195
202
  scope_chain_items = join_scopes(table, predicate_builder)
196
203
  klass_scope = klass_join_scope(table, predicate_builder)
197
204
 
@@ -217,7 +224,7 @@ module ActiveRecord
217
224
  klass_scope
218
225
  end
219
226
 
220
- def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
227
+ def join_scopes(table, predicate_builder = nil, klass = self.klass, record = nil) # :nodoc:
221
228
  if scope
222
229
  [scope_for(build_scope(table, predicate_builder, klass), record)]
223
230
  else
@@ -225,7 +232,7 @@ module ActiveRecord
225
232
  end
226
233
  end
227
234
 
228
- def klass_join_scope(table, predicate_builder) # :nodoc:
235
+ def klass_join_scope(table, predicate_builder = nil) # :nodoc:
229
236
  relation = build_scope(table, predicate_builder)
230
237
  klass.scope_for_association(relation)
231
238
  end
@@ -235,14 +242,16 @@ module ActiveRecord
235
242
  end
236
243
 
237
244
  def counter_cache_column
238
- @counter_cache_column ||= if belongs_to?
239
- if options[:counter_cache] == true
240
- -"#{active_record.name.demodulize.underscore.pluralize}_count"
241
- elsif options[:counter_cache]
242
- -options[:counter_cache].to_s
245
+ @counter_cache_column ||= begin
246
+ counter_cache = options[:counter_cache]
247
+
248
+ if belongs_to?
249
+ if counter_cache
250
+ counter_cache[:column] || -"#{active_record.name.demodulize.underscore.pluralize}_count"
251
+ end
252
+ else
253
+ -((counter_cache && -counter_cache[:column]) || "#{name}_count")
243
254
  end
244
- else
245
- -(options[:counter_cache]&.to_s || "#{name}_count")
246
255
  end
247
256
  end
248
257
 
@@ -291,7 +300,7 @@ module ActiveRecord
291
300
  inverse_of && inverse_which_updates_counter_cache == inverse_of
292
301
  end
293
302
 
294
- # Returns whether a counter cache should be used for this association.
303
+ # Returns whether this association has a counter cache.
295
304
  #
296
305
  # The counter_cache option must be given on either the owner or inverse
297
306
  # association, and the column must be present on the owner.
@@ -301,6 +310,17 @@ module ActiveRecord
301
310
  active_record.has_attribute?(counter_cache_column)
302
311
  end
303
312
 
313
+ # Returns whether this association has a counter cache and its column values were backfilled
314
+ # (and so it is used internally by methods like +size+/+any?+/etc).
315
+ def has_active_cached_counter?
316
+ return false unless has_cached_counter?
317
+
318
+ counter_cache = options[:counter_cache] ||
319
+ (inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache])
320
+
321
+ counter_cache[:active] != false
322
+ end
323
+
304
324
  def counter_must_be_updated_by_has_many?
305
325
  !inverse_updates_counter_in_memory? && has_cached_counter?
306
326
  end
@@ -313,12 +333,8 @@ module ActiveRecord
313
333
  collect_join_chain
314
334
  end
315
335
 
316
- def build_scope(table, predicate_builder = predicate_builder(table), klass = self.klass)
317
- Relation.create(
318
- klass,
319
- table: table,
320
- predicate_builder: predicate_builder
321
- )
336
+ def build_scope(table, predicate_builder = nil, klass = self.klass)
337
+ Relation.create(klass, table:, predicate_builder:)
322
338
  end
323
339
 
324
340
  def strict_loading?
@@ -337,10 +353,6 @@ module ActiveRecord
337
353
  end
338
354
 
339
355
  private
340
- def predicate_builder(table)
341
- PredicateBuilder.new(TableMetadata.new(klass, table))
342
- end
343
-
344
356
  def primary_key(klass)
345
357
  klass.primary_key || raise(UnknownPrimaryKey.new(klass))
346
358
  end
@@ -377,12 +389,11 @@ module ActiveRecord
377
389
  super()
378
390
  @name = name
379
391
  @scope = scope
380
- @options = options
392
+ @options = normalize_options(options)
381
393
  @active_record = active_record
382
394
  @klass = options[:anonymous_class]
383
395
  @plural_name = active_record.pluralize_table_names ?
384
396
  name.to_s.pluralize : name.to_s
385
- validate_reflection!
386
397
  end
387
398
 
388
399
  def autosave=(autosave)
@@ -409,7 +420,15 @@ module ActiveRecord
409
420
  # a new association object. Use +build_association+ or +create_association+
410
421
  # instead. This allows plugins to hook into association object creation.
411
422
  def klass
412
- @klass ||= compute_class(class_name)
423
+ @klass ||= _klass(class_name)
424
+ end
425
+
426
+ def _klass(class_name) # :nodoc:
427
+ if active_record.name.demodulize == class_name
428
+ return compute_class("::#{class_name}") rescue NameError
429
+ end
430
+
431
+ compute_class(class_name)
413
432
  end
414
433
 
415
434
  def compute_class(name)
@@ -435,15 +454,24 @@ module ActiveRecord
435
454
  name.to_s.camelize
436
455
  end
437
456
 
438
- def validate_reflection!
439
- return unless options[:foreign_key].is_a?(Array)
457
+ def normalize_options(options)
458
+ counter_cache = options.delete(:counter_cache)
440
459
 
441
- message = <<~MSG.squish
442
- Passing #{options[:foreign_key]} array to :foreign_key option
443
- on the #{active_record}##{name} association is not supported.
444
- Use the query_constraints: #{options[:foreign_key]} option instead to represent a composite foreign key.
445
- MSG
446
- raise ArgumentError, message
460
+ if counter_cache
461
+ active = true
462
+
463
+ case counter_cache
464
+ when String, Symbol
465
+ column = -counter_cache.to_s
466
+ when Hash
467
+ active = counter_cache.fetch(:active, true)
468
+ column = counter_cache[:column]&.to_s
469
+ end
470
+
471
+ options[:counter_cache] = { active: active, column: column }
472
+ end
473
+
474
+ options
447
475
  end
448
476
  end
449
477
 
@@ -488,12 +516,27 @@ module ActiveRecord
488
516
 
489
517
  def initialize(name, scope, options, active_record)
490
518
  super
519
+
520
+ @validated = false
491
521
  @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
492
522
  @foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
493
523
  @join_table = nil
494
524
  @foreign_key = nil
495
525
  @association_foreign_key = nil
496
526
  @association_primary_key = nil
527
+ if options[:query_constraints]
528
+ raise ConfigurationError, <<~MSG.squish
529
+ Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is not allowed.
530
+ To get the same behavior, use the `foreign_key` option instead.
531
+ MSG
532
+ end
533
+
534
+ # If the foreign key is an array, set query constraints options and don't use the foreign key
535
+ if options[:foreign_key].is_a?(Array)
536
+ options[:query_constraints] = options.delete(:foreign_key)
537
+ end
538
+
539
+ @deprecated = !!options[:deprecated]
497
540
 
498
541
  ensure_option_not_given_as_class!(:class_name)
499
542
  end
@@ -503,7 +546,9 @@ module ActiveRecord
503
546
  if polymorphic?
504
547
  key = [key, owner._read_attribute(@foreign_type)]
505
548
  end
506
- klass.cached_find_by_statement(key, &block)
549
+ klass.with_connection do |connection|
550
+ klass.cached_find_by_statement(connection, key, &block)
551
+ end
507
552
  end
508
553
 
509
554
  def join_table
@@ -511,10 +556,14 @@ module ActiveRecord
511
556
  end
512
557
 
513
558
  def foreign_key(infer_from_inverse_of: true)
514
- @foreign_key ||= if options[:query_constraints]
515
- options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
516
- elsif options[:foreign_key]
517
- options[:foreign_key].to_s
559
+ @foreign_key ||= if options[:foreign_key]
560
+ if options[:foreign_key].is_a?(Array)
561
+ options[:foreign_key].map { |fk| -fk.to_s.freeze }.freeze
562
+ else
563
+ options[:foreign_key].to_s.freeze
564
+ end
565
+ elsif options[:query_constraints]
566
+ options[:query_constraints].map { |fk| -fk.to_s.freeze }.freeze
518
567
  else
519
568
  derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
520
569
 
@@ -522,7 +571,12 @@ module ActiveRecord
522
571
  derived_fk = derive_fk_query_constraints(derived_fk)
523
572
  end
524
573
 
525
- derived_fk
574
+ if derived_fk.is_a?(Array)
575
+ derived_fk.map! { |fk| -fk.freeze }
576
+ derived_fk.freeze
577
+ else
578
+ -derived_fk.freeze
579
+ end
526
580
  end
527
581
  end
528
582
 
@@ -566,6 +620,8 @@ module ActiveRecord
566
620
  end
567
621
 
568
622
  def check_validity!
623
+ return if @validated
624
+
569
625
  check_validity_of_inverse!
570
626
 
571
627
  if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
@@ -575,6 +631,8 @@ module ActiveRecord
575
631
  raise CompositePrimaryKeyMismatchError.new(self)
576
632
  end
577
633
  end
634
+
635
+ @validated = true
578
636
  end
579
637
 
580
638
  def check_eager_loadable!
@@ -692,6 +750,10 @@ module ActiveRecord
692
750
  Array(options[:extend])
693
751
  end
694
752
 
753
+ def deprecated?
754
+ @deprecated
755
+ end
756
+
695
757
  private
696
758
  # Attempts to find the inverse association name automatically.
697
759
  # If it cannot find a suitable inverse association name, it returns
@@ -711,6 +773,10 @@ module ActiveRecord
711
773
 
712
774
  begin
713
775
  reflection = klass._reflect_on_association(inverse_name)
776
+ if !reflection && active_record.automatically_invert_plural_associations
777
+ plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
778
+ reflection = klass._reflect_on_association(plural_inverse_name)
779
+ end
714
780
  rescue NameError => error
715
781
  raise unless error.name.to_s == class_name
716
782
 
@@ -720,7 +786,7 @@ module ActiveRecord
720
786
  end
721
787
 
722
788
  if valid_inverse_reflection?(reflection)
723
- inverse_name
789
+ reflection.name
724
790
  end
725
791
  end
726
792
  end
@@ -921,6 +987,8 @@ module ActiveRecord
921
987
 
922
988
  def initialize(delegate_reflection)
923
989
  super()
990
+
991
+ @validated = false
924
992
  @delegate_reflection = delegate_reflection
925
993
  @klass = delegate_reflection.options[:anonymous_class]
926
994
  @source_reflection_name = delegate_reflection.options[:source]
@@ -933,7 +1001,7 @@ module ActiveRecord
933
1001
  end
934
1002
 
935
1003
  def klass
936
- @klass ||= delegate_reflection.compute_class(class_name)
1004
+ @klass ||= delegate_reflection._klass(class_name)
937
1005
  end
938
1006
 
939
1007
  # Returns the source of the through reflection. It checks both a singularized
@@ -954,6 +1022,8 @@ module ActiveRecord
954
1022
  # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
955
1023
  #
956
1024
  def source_reflection
1025
+ return unless source_reflection_name
1026
+
957
1027
  through_reflection.klass._reflect_on_association(source_reflection_name)
958
1028
  end
959
1029
 
@@ -1006,7 +1076,7 @@ module ActiveRecord
1006
1076
  source_reflection.scopes + super
1007
1077
  end
1008
1078
 
1009
- def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
1079
+ def join_scopes(table, predicate_builder = nil, klass = self.klass, record = nil) # :nodoc:
1010
1080
  source_reflection.join_scopes(table, predicate_builder, klass, record) + super
1011
1081
  end
1012
1082
 
@@ -1082,6 +1152,8 @@ module ActiveRecord
1082
1152
  end
1083
1153
 
1084
1154
  def check_validity!
1155
+ return if @validated
1156
+
1085
1157
  if through_reflection.nil?
1086
1158
  raise HasManyThroughAssociationNotFoundError.new(active_record, self)
1087
1159
  end
@@ -1111,7 +1183,7 @@ module ActiveRecord
1111
1183
  end
1112
1184
 
1113
1185
  if parent_reflection.nil?
1114
- reflections = active_record.reflections.keys.map(&:to_sym)
1186
+ reflections = active_record.normalized_reflections.keys
1115
1187
 
1116
1188
  if reflections.index(through_reflection.name) > reflections.index(name)
1117
1189
  raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
@@ -1119,6 +1191,8 @@ module ActiveRecord
1119
1191
  end
1120
1192
 
1121
1193
  check_validity_of_inverse!
1194
+
1195
+ @validated = true
1122
1196
  end
1123
1197
 
1124
1198
  def constraints
@@ -1139,6 +1213,10 @@ module ActiveRecord
1139
1213
  collect_join_reflections(seed + [self])
1140
1214
  end
1141
1215
 
1216
+ def deprecated_nested_reflections
1217
+ @deprecated_nested_reflections ||= collect_deprecated_nested_reflections
1218
+ end
1219
+
1142
1220
  protected
1143
1221
  def actual_source_reflection # FIXME: this is a horrible name
1144
1222
  source_reflection.actual_source_reflection
@@ -1163,6 +1241,19 @@ module ActiveRecord
1163
1241
  options[:source_type] || source_reflection.class_name
1164
1242
  end
1165
1243
 
1244
+ def collect_deprecated_nested_reflections
1245
+ result = []
1246
+ [through_reflection, source_reflection].each do |reflection|
1247
+ result << reflection if reflection.deprecated?
1248
+ # Both the through and the source reflections could be through
1249
+ # themselves. Nesting can go an arbitrary number of levels down.
1250
+ if reflection.through_reflection?
1251
+ result.concat(reflection.deprecated_nested_reflections)
1252
+ end
1253
+ end
1254
+ result
1255
+ end
1256
+
1166
1257
  delegate_methods = AssociationReflection.public_instance_methods -
1167
1258
  public_instance_methods
1168
1259
 
@@ -1179,8 +1270,11 @@ module ActiveRecord
1179
1270
  @previous_reflection = previous_reflection
1180
1271
  end
1181
1272
 
1182
- def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
1183
- scopes = @previous_reflection.join_scopes(table, predicate_builder, klass, record) + super
1273
+ def join_scopes(table, predicate_builder = nil, klass = self.klass, record = nil) # :nodoc:
1274
+ scopes = super
1275
+ unless @previous_reflection.through_reflection?
1276
+ scopes += @previous_reflection.join_scopes(table, predicate_builder, klass, record)
1277
+ end
1184
1278
  scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
1185
1279
  end
1186
1280
 
@@ -5,11 +5,12 @@ module ActiveRecord
5
5
  class BatchEnumerator
6
6
  include Enumerable
7
7
 
8
- def initialize(of: 1000, start: nil, finish: nil, relation:, order: :asc, use_ranges: nil) # :nodoc:
8
+ def initialize(of: 1000, start: nil, finish: nil, relation:, cursor:, order: :asc, use_ranges: nil) # :nodoc:
9
9
  @of = of
10
10
  @relation = relation
11
11
  @start = start
12
12
  @finish = finish
13
+ @cursor = cursor
13
14
  @order = order
14
15
  @use_ranges = use_ranges
15
16
  end
@@ -52,7 +53,7 @@ module ActiveRecord
52
53
  def each_record(&block)
53
54
  return to_enum(:each_record) unless block_given?
54
55
 
55
- @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true, order: @order).each do |relation|
56
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true, cursor: @cursor, order: @order).each do |relation|
56
57
  relation.records.each(&block)
57
58
  end
58
59
  end
@@ -77,13 +78,26 @@ module ActiveRecord
77
78
  end
78
79
  end
79
80
 
80
- # Destroys records in batches.
81
+ # Touches records in batches. Returns the total number of rows affected.
82
+ #
83
+ # Person.in_batches.touch_all
84
+ #
85
+ # See Relation#touch_all for details of how each batch is touched.
86
+ def touch_all(...)
87
+ sum do |relation|
88
+ relation.touch_all(...)
89
+ end
90
+ end
91
+
92
+ # Destroys records in batches. Returns the total number of rows affected.
81
93
  #
82
94
  # Person.where("age < 10").in_batches.destroy_all
83
95
  #
84
96
  # See Relation#destroy_all for details of how each batch is destroyed.
85
97
  def destroy_all
86
- each(&:destroy_all)
98
+ sum do |relation|
99
+ relation.destroy_all.count(&:destroyed?)
100
+ end
87
101
  end
88
102
 
89
103
  # Yields an ActiveRecord::Relation object for each batch of records.
@@ -92,7 +106,7 @@ module ActiveRecord
92
106
  # relation.update_all(awesome: true)
93
107
  # end
94
108
  def each(&block)
95
- enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false, order: @order, use_ranges: @use_ranges)
109
+ enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false, cursor: @cursor, order: @order, use_ranges: @use_ranges)
96
110
  return enum.each(&block) if block_given?
97
111
  enum
98
112
  end