activerecord 7.2.2.1 → 8.1.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +564 -753
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -11
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +10 -8
  14. data/lib/active_record/associations/collection_proxy.rb +22 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  20. data/lib/active_record/associations/join_dependency.rb +4 -2
  21. data/lib/active_record/associations/preloader/association.rb +2 -2
  22. data/lib/active_record/associations/preloader/batch.rb +7 -1
  23. data/lib/active_record/associations/preloader/branch.rb +1 -0
  24. data/lib/active_record/associations/singular_association.rb +8 -3
  25. data/lib/active_record/associations.rb +192 -24
  26. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  27. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  28. data/lib/active_record/attribute_methods/query.rb +34 -0
  29. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  31. data/lib/active_record/attribute_methods.rb +24 -19
  32. data/lib/active_record/attributes.rb +40 -26
  33. data/lib/active_record/autosave_association.rb +91 -39
  34. data/lib/active_record/base.rb +3 -4
  35. data/lib/active_record/coders/json.rb +14 -5
  36. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  37. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  38. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  39. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
  40. data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
  41. data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
  42. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  43. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  45. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
  47. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  48. data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
  49. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
  50. data/lib/active_record/connection_adapters/column.rb +17 -4
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  53. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  54. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  59. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  60. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  62. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  65. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  66. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  67. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
  68. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  69. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
  70. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
  71. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  72. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  73. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  74. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  75. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  76. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
  78. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  79. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  80. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
  81. data/lib/active_record/connection_adapters.rb +1 -56
  82. data/lib/active_record/connection_handling.rb +37 -10
  83. data/lib/active_record/core.rb +61 -25
  84. data/lib/active_record/counter_cache.rb +34 -9
  85. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  86. data/lib/active_record/database_configurations/database_config.rb +9 -1
  87. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  88. data/lib/active_record/database_configurations/url_config.rb +13 -3
  89. data/lib/active_record/database_configurations.rb +7 -3
  90. data/lib/active_record/delegated_type.rb +19 -19
  91. data/lib/active_record/dynamic_matchers.rb +54 -69
  92. data/lib/active_record/encryption/config.rb +3 -1
  93. data/lib/active_record/encryption/encryptable_record.rb +9 -9
  94. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  95. data/lib/active_record/encryption/encryptor.rb +49 -28
  96. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  97. data/lib/active_record/encryption/scheme.rb +9 -2
  98. data/lib/active_record/enum.rb +46 -42
  99. data/lib/active_record/errors.rb +36 -12
  100. data/lib/active_record/explain.rb +1 -1
  101. data/lib/active_record/explain_registry.rb +51 -2
  102. data/lib/active_record/filter_attribute_handler.rb +73 -0
  103. data/lib/active_record/fixture_set/table_row.rb +19 -2
  104. data/lib/active_record/fixtures.rb +2 -4
  105. data/lib/active_record/future_result.rb +13 -9
  106. data/lib/active_record/gem_version.rb +3 -3
  107. data/lib/active_record/inheritance.rb +1 -1
  108. data/lib/active_record/insert_all.rb +12 -7
  109. data/lib/active_record/locking/optimistic.rb +8 -1
  110. data/lib/active_record/locking/pessimistic.rb +5 -0
  111. data/lib/active_record/log_subscriber.rb +3 -13
  112. data/lib/active_record/middleware/shard_selector.rb +34 -17
  113. data/lib/active_record/migration/command_recorder.rb +44 -11
  114. data/lib/active_record/migration/compatibility.rb +37 -24
  115. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  116. data/lib/active_record/migration.rb +50 -43
  117. data/lib/active_record/model_schema.rb +38 -13
  118. data/lib/active_record/nested_attributes.rb +6 -6
  119. data/lib/active_record/persistence.rb +162 -133
  120. data/lib/active_record/query_cache.rb +22 -15
  121. data/lib/active_record/query_logs.rb +104 -52
  122. data/lib/active_record/query_logs_formatter.rb +17 -28
  123. data/lib/active_record/querying.rb +12 -12
  124. data/lib/active_record/railtie.rb +37 -32
  125. data/lib/active_record/railties/controller_runtime.rb +11 -6
  126. data/lib/active_record/railties/databases.rake +26 -37
  127. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  128. data/lib/active_record/railties/job_runtime.rb +10 -11
  129. data/lib/active_record/reflection.rb +53 -21
  130. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  131. data/lib/active_record/relation/batches.rb +147 -73
  132. data/lib/active_record/relation/calculations.rb +80 -63
  133. data/lib/active_record/relation/delegation.rb +25 -15
  134. data/lib/active_record/relation/finder_methods.rb +54 -37
  135. data/lib/active_record/relation/merger.rb +8 -8
  136. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  137. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  138. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  139. data/lib/active_record/relation/predicate_builder.rb +22 -7
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +156 -95
  142. data/lib/active_record/relation/spawn_methods.rb +7 -7
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +122 -80
  145. data/lib/active_record/result.rb +109 -24
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +9 -6
  148. data/lib/active_record/schema_dumper.rb +47 -22
  149. data/lib/active_record/schema_migration.rb +2 -1
  150. data/lib/active_record/scoping/named.rb +5 -2
  151. data/lib/active_record/scoping.rb +0 -1
  152. data/lib/active_record/secure_token.rb +3 -3
  153. data/lib/active_record/signed_id.rb +47 -18
  154. data/lib/active_record/statement_cache.rb +24 -20
  155. data/lib/active_record/store.rb +51 -22
  156. data/lib/active_record/structured_event_subscriber.rb +85 -0
  157. data/lib/active_record/table_metadata.rb +6 -23
  158. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  159. data/lib/active_record/tasks/database_tasks.rb +85 -85
  160. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  161. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  162. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  163. data/lib/active_record/test_databases.rb +14 -4
  164. data/lib/active_record/test_fixtures.rb +39 -2
  165. data/lib/active_record/testing/query_assertions.rb +8 -2
  166. data/lib/active_record/timestamp.rb +4 -2
  167. data/lib/active_record/token_for.rb +1 -1
  168. data/lib/active_record/transaction.rb +2 -5
  169. data/lib/active_record/transactions.rb +39 -16
  170. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  171. data/lib/active_record/type/internal/timezone.rb +7 -0
  172. data/lib/active_record/type/json.rb +15 -2
  173. data/lib/active_record/type/serialized.rb +11 -4
  174. data/lib/active_record/type/type_map.rb +1 -1
  175. data/lib/active_record/type_caster/connection.rb +2 -1
  176. data/lib/active_record/validations/associated.rb +1 -1
  177. data/lib/active_record/validations/uniqueness.rb +8 -8
  178. data/lib/active_record.rb +85 -50
  179. data/lib/arel/alias_predication.rb +2 -0
  180. data/lib/arel/collectors/bind.rb +2 -2
  181. data/lib/arel/collectors/sql_string.rb +1 -1
  182. data/lib/arel/collectors/substitute_binds.rb +2 -2
  183. data/lib/arel/crud.rb +8 -11
  184. data/lib/arel/delete_manager.rb +5 -0
  185. data/lib/arel/nodes/binary.rb +1 -1
  186. data/lib/arel/nodes/count.rb +2 -2
  187. data/lib/arel/nodes/delete_statement.rb +4 -2
  188. data/lib/arel/nodes/function.rb +4 -10
  189. data/lib/arel/nodes/named_function.rb +2 -2
  190. data/lib/arel/nodes/node.rb +2 -2
  191. data/lib/arel/nodes/sql_literal.rb +1 -1
  192. data/lib/arel/nodes/update_statement.rb +4 -2
  193. data/lib/arel/nodes.rb +0 -2
  194. data/lib/arel/select_manager.rb +13 -4
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/update_manager.rb +5 -0
  197. data/lib/arel/visitors/dot.rb +2 -3
  198. data/lib/arel/visitors/postgresql.rb +55 -0
  199. data/lib/arel/visitors/sqlite.rb +55 -8
  200. data/lib/arel/visitors/to_sql.rb +6 -22
  201. data/lib/arel.rb +3 -1
  202. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  203. metadata +17 -17
  204. data/lib/active_record/explain_subscriber.rb +0 -34
  205. data/lib/active_record/normalization.rb +0 -163
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -60,37 +60,37 @@ module ActiveRecord
60
60
  # Person.distinct.count(:age)
61
61
  # # => counts the number of different age values
62
62
  #
63
- # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
63
+ # If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group],
64
64
  # it returns a Hash whose keys represent the aggregated column,
65
65
  # and the values are the respective amounts:
66
66
  #
67
67
  # Person.group(:city).count
68
68
  # # => { 'Rome' => 5, 'Paris' => 3 }
69
69
  #
70
- # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
70
+ # If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
71
71
  # keys are an array containing the individual values of each column and the value
72
- # of each key would be the #count.
72
+ # of each key would be the count.
73
73
  #
74
74
  # Article.group(:status, :category).count
75
75
  # # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
76
76
  #
77
- # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
77
+ # If +count+ is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
78
78
  #
79
79
  # Person.select(:age).count
80
80
  # # => counts the number of different age values
81
81
  #
82
- # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
82
+ # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid +count+ expressions. The specifics differ
83
83
  # between databases. In invalid cases, an error from the database is thrown.
84
84
  #
85
- # When given a block, loads all records in the relation, if the relation
86
- # hasn't been loaded yet. Calls the block with each record in the relation.
87
- # Returns the number of records for which the block returns a truthy value.
85
+ # When given a block, calls the block with each record in the relation and
86
+ # returns the number of records for which the block returns a truthy value.
88
87
  #
89
88
  # Person.count { |person| person.age > 21 }
90
89
  # # => counts the number of people older that 21
91
90
  #
92
- # Note: If there are a lot of records in the relation, loading all records
93
- # could result in performance issues.
91
+ # If the relation hasn't been loaded yet, calling +count+ with a block will
92
+ # load all records in the relation. If there are a lot of records in the
93
+ # relation, loading all records could result in performance issues.
94
94
  def count(column_name = nil)
95
95
  if block_given?
96
96
  unless column_name.nil?
@@ -159,16 +159,15 @@ module ActiveRecord
159
159
  #
160
160
  # Person.sum(:age) # => 4562
161
161
  #
162
- # When given a block, loads all records in the relation, if the relation
163
- # hasn't been loaded yet. Calls the block with each record in the relation.
164
- # Returns the sum of +initial_value_or_column+ and the block return
165
- # values:
162
+ # When given a block, calls the block with each record in the relation and
163
+ # returns the sum of +initial_value_or_column+ plus the block return values:
166
164
  #
167
165
  # Person.sum { |person| person.age } # => 4562
168
166
  # Person.sum(1000) { |person| person.age } # => 5562
169
167
  #
170
- # Note: If there are a lot of records in the relation, loading all records
171
- # could result in performance issues.
168
+ # If the relation hasn't been loaded yet, calling +sum+ with a block will
169
+ # load all records in the relation. If there are a lot of records in the
170
+ # relation, loading all records could result in performance issues.
172
171
  def sum(initial_value_or_column = 0, &block)
173
172
  if block_given?
174
173
  map(&block).sum(initial_value_or_column)
@@ -234,7 +233,7 @@ module ActiveRecord
234
233
  if operation == "count"
235
234
  unless distinct_value || distinct_select?(column_name || select_for_count)
236
235
  relation.distinct!
237
- relation.select_values = Array(klass.primary_key || table[Arel.star])
236
+ relation.select_values = Array(model.primary_key || table[Arel.star])
238
237
  end
239
238
  # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
240
239
  relation.order_values = [] if group_values.empty?
@@ -275,14 +274,23 @@ module ActiveRecord
275
274
  # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
276
275
  # # => [2, 3]
277
276
  #
278
- # Comment.joins(:person).pluck(:id, person: [:id])
279
- # # SELECT comments.id, people.id FROM comments INNER JOIN people on comments.person_id = people.id
277
+ # Comment.joins(:person).pluck(:id, person: :id)
278
+ # # SELECT comments.id, person.id FROM comments INNER JOIN people person ON person.id = comments.person_id
280
279
  # # => [[1, 2], [2, 2]]
281
280
  #
281
+ # Comment.joins(:person).pluck(:id, person: [:id, :name])
282
+ # # SELECT comments.id, person.id, person.name FROM comments INNER JOIN people person ON person.id = comments.person_id
283
+ # # => [[1, 2, 'David'], [2, 2, 'David']]
284
+ #
282
285
  # Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
283
286
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
284
287
  # # => ['0', '27761', '173']
285
288
  #
289
+ # Be aware that #pluck ignores any previous select clauses
290
+ #
291
+ # Person.select(:name).pluck(:id)
292
+ # # SELECT people.id FROM people
293
+ #
286
294
  # See also #ids.
287
295
  def pluck(*column_names)
288
296
  if @none
@@ -306,16 +314,16 @@ module ActiveRecord
306
314
  relation = apply_join_dependency
307
315
  relation.pluck(*column_names)
308
316
  else
309
- klass.disallow_raw_sql!(flattened_args(column_names))
317
+ model.disallow_raw_sql!(flattened_args(column_names))
310
318
  relation = spawn
311
319
  columns = relation.arel_columns(column_names)
312
320
  relation.select_values = columns
313
321
  result = skip_query_cache_if_necessary do
314
- if where_clause.contradiction?
322
+ if where_clause.contradiction? && !possible_aggregation?(column_names)
315
323
  ActiveRecord::Result.empty(async: @async)
316
324
  else
317
- klass.with_connection do |c|
318
- c.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
325
+ model.with_connection do |c|
326
+ c.select_all(relation.arel, "#{model.name} Pluck", async: @async)
319
327
  end
320
328
  end
321
329
  end
@@ -391,8 +399,8 @@ module ActiveRecord
391
399
  ActiveRecord::Result.empty
392
400
  else
393
401
  skip_query_cache_if_necessary do
394
- klass.with_connection do |c|
395
- c.select_all(relation, "#{klass.name} Ids", async: @async)
402
+ model.with_connection do |c|
403
+ c.select_all(relation, "#{model.name} Ids", async: @async)
396
404
  end
397
405
  end
398
406
  end
@@ -406,9 +414,21 @@ module ActiveRecord
406
414
  async.ids
407
415
  end
408
416
 
417
+ protected
418
+ def aggregate_column(column_name)
419
+ case column_name
420
+ when Arel::Expressions
421
+ column_name
422
+ when :all
423
+ Arel.star
424
+ else
425
+ arel_column(column_name.to_s)
426
+ end
427
+ end
428
+
409
429
  private
410
430
  def all_attributes?(column_names)
411
- (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
431
+ (column_names.map(&:to_s) - model.attribute_names - model.attribute_aliases.keys).empty?
412
432
  end
413
433
 
414
434
  def has_include?(column_name)
@@ -446,11 +466,13 @@ module ActiveRecord
446
466
  column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
447
467
  end
448
468
 
449
- def aggregate_column(column_name)
450
- return column_name if Arel::Expressions === column_name
451
-
452
- arel_column(column_name.to_s) do |name|
453
- column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
469
+ def possible_aggregation?(column_names)
470
+ column_names.all? do |column_name|
471
+ if column_name.is_a?(String)
472
+ column_name.include?("(")
473
+ else
474
+ Arel.arel_node?(column_name)
475
+ end
454
476
  end
455
477
  end
456
478
 
@@ -469,7 +491,7 @@ module ActiveRecord
469
491
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
470
492
  relation = unscope(:order).distinct!(false)
471
493
 
472
- column = aggregate_column(column_name)
494
+ column = relation.aggregate_column(column_name)
473
495
  select_value = operation_over_aggregate_column(column, operation, distinct)
474
496
  select_value.distinct = true if operation == "sum" && distinct
475
497
 
@@ -479,11 +501,15 @@ module ActiveRecord
479
501
  end
480
502
 
481
503
  query_result = if relation.where_clause.contradiction?
482
- ActiveRecord::Result.empty
504
+ if @async
505
+ FutureResult.wrap(ActiveRecord::Result.empty)
506
+ else
507
+ ActiveRecord::Result.empty
508
+ end
483
509
  else
484
510
  skip_query_cache_if_necessary do
485
- @klass.with_connection do |c|
486
- c.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
511
+ model.with_connection do |c|
512
+ c.select_all(query_builder, "#{model.name} #{operation.capitalize}", async: @async)
487
513
  end
488
514
  end
489
515
  end
@@ -501,16 +527,17 @@ module ActiveRecord
501
527
 
502
528
  def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
503
529
  group_fields = group_values
504
- group_fields = group_fields.uniq if group_fields.size > 1
505
530
 
506
531
  if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
507
- association = klass._reflect_on_association(group_fields.first)
532
+ association = model._reflect_on_association(group_fields.first)
508
533
  associated = association && association.belongs_to? # only count belongs_to associations
509
534
  group_fields = Array(association.foreign_key) if associated
510
535
  end
511
- group_fields = arel_columns(group_fields)
512
536
 
513
- @klass.with_connection do |connection|
537
+ relation = except(:group).distinct!(false)
538
+ group_fields = relation.arel_columns(group_fields)
539
+
540
+ model.with_connection do |connection|
514
541
  column_alias_tracker = ColumnAliasTracker.new(connection)
515
542
 
516
543
  group_aliases = group_fields.map { |field|
@@ -519,16 +546,16 @@ module ActiveRecord
519
546
  }
520
547
  group_columns = group_aliases.zip(group_fields)
521
548
 
522
- column = aggregate_column(column_name)
549
+ column = relation.aggregate_column(column_name)
523
550
  column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
524
551
  select_value = operation_over_aggregate_column(column, operation, distinct)
525
- select_value.as(adapter_class.quote_column_name(column_alias))
552
+ select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))
526
553
 
527
554
  select_values = [select_value]
528
555
  select_values += self.select_values unless having_clause.empty?
529
556
 
530
557
  select_values.concat group_columns.map { |aliaz, field|
531
- aliaz = adapter_class.quote_column_name(aliaz)
558
+ aliaz = model.adapter_class.quote_column_name(aliaz)
532
559
  if field.respond_to?(:as)
533
560
  field.as(aliaz)
534
561
  else
@@ -536,12 +563,11 @@ module ActiveRecord
536
563
  end
537
564
  }
538
565
 
539
- relation = except(:group).distinct!(false)
540
566
  relation.group_values = group_fields
541
567
  relation.select_values = select_values
542
568
 
543
569
  result = skip_query_cache_if_necessary do
544
- connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async)
570
+ connection.select_all(relation.arel, "#{model.name} #{operation.capitalize}", async: @async)
545
571
  end
546
572
 
547
573
  result.then do |calculated_data|
@@ -583,7 +609,7 @@ module ActiveRecord
583
609
 
584
610
  def type_for(field, &block)
585
611
  field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
586
- @klass.type_for_attribute(field_name, &block)
612
+ model.type_for_attribute(field_name, &block)
587
613
  end
588
614
 
589
615
  def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
@@ -596,12 +622,12 @@ module ActiveRecord
596
622
 
597
623
  def type_cast_pluck_values(result, columns)
598
624
  cast_types = if result.columns.size != columns.size
599
- klass.attribute_types
625
+ model.attribute_types
600
626
  else
601
627
  join_dependencies = nil
602
628
  columns.map.with_index do |column, i|
603
629
  column.try(:type_caster) ||
604
- klass.attribute_types.fetch(name = result.columns[i]) do
630
+ model.attribute_types.fetch(name = result.columns[i]) do
605
631
  join_dependencies ||= build_join_dependencies
606
632
  lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
607
633
  result.column_types[i] || Type.default_value
@@ -630,22 +656,12 @@ module ActiveRecord
630
656
  end
631
657
 
632
658
  def select_for_count
633
- if select_values.present?
634
- return select_values.first if select_values.one?
635
-
636
- select_values.map do |field|
637
- column = arel_column(field.to_s) do |attr_name|
638
- Arel.sql(attr_name)
639
- end
640
-
641
- if column.is_a?(Arel::Nodes::SqlLiteral)
642
- column
643
- else
644
- "#{adapter_class.quote_table_name(column.relation.name)}.#{adapter_class.quote_column_name(column.name)}"
645
- end
646
- end.join(", ")
647
- else
659
+ if select_values.empty?
648
660
  :all
661
+ else
662
+ with_connection do |conn|
663
+ arel_columns(select_values).map { |column| conn.visitor.compile(column) }.join(", ")
664
+ end
649
665
  end
650
666
  end
651
667
 
@@ -660,9 +676,10 @@ module ActiveRecord
660
676
  if column_name == :all
661
677
  column_alias = Arel.star
662
678
  relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
679
+ relation.unscope!(:order)
663
680
  else
664
681
  column_alias = Arel.sql("count_column")
665
- relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
682
+ relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
666
683
  end
667
684
 
668
685
  subquery_alias = Arel.sql("subquery_for_count", retryable: true)
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/module/delegation"
4
3
 
5
4
  module ActiveRecord
6
5
  module Delegation # :nodoc:
@@ -22,6 +21,9 @@ module ActiveRecord
22
21
  end
23
22
 
24
23
  module DelegateCache # :nodoc:
24
+ @delegate_base_methods = true
25
+ singleton_class.attr_accessor :delegate_base_methods
26
+
25
27
  def relation_delegate_class(klass)
26
28
  @relation_delegate_cache[klass]
27
29
  end
@@ -75,12 +77,12 @@ module ActiveRecord
75
77
  if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
76
78
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
77
79
  def #{method}(...)
78
- scoping { klass.#{method}(...) }
80
+ scoping { model.#{method}(...) }
79
81
  end
80
82
  RUBY
81
83
  else
82
84
  define_method(method) do |*args, **kwargs, &block|
83
- scoping { klass.public_send(method, *args, **kwargs, &block) }
85
+ scoping { model.public_send(method, *args, **kwargs, &block) }
84
86
  end
85
87
  end
86
88
  end
@@ -92,15 +94,15 @@ module ActiveRecord
92
94
 
93
95
  # This module creates compiled delegation methods dynamically at runtime, which makes
94
96
  # subsequent calls to that method faster by avoiding method_missing. The delegations
95
- # may vary depending on the klass of a relation, so we create a subclass of Relation
96
- # for each different klass, and the delegations are compiled into that subclass only.
97
+ # may vary depending on the model of a relation, so we create a subclass of Relation
98
+ # for each different model, and the delegations are compiled into that subclass only.
97
99
 
98
100
  delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
99
101
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
100
102
  :to_sentence, :to_fs, :to_formatted_s, :as_json,
101
103
  :shuffle, :split, :slice, :index, :rindex, to: :records
102
104
 
103
- delegate :primary_key, :lease_connection, :connection, :with_connection, :transaction, to: :klass
105
+ delegate :primary_key, :with_connection, :connection, :table_name, :transaction, :sanitize_sql_like, :unscoped, :name, to: :model
104
106
 
105
107
  module ClassSpecificRelation # :nodoc:
106
108
  extend ActiveSupport::Concern
@@ -113,11 +115,19 @@ module ActiveRecord
113
115
 
114
116
  private
115
117
  def method_missing(method, ...)
116
- if @klass.respond_to?(method)
117
- unless Delegation.uncacheable_methods.include?(method)
118
- @klass.generate_relation_method(method)
118
+ if model.respond_to?(method)
119
+ if !DelegateCache.delegate_base_methods && Base.respond_to?(method)
120
+ # A common mistake in Active Record's own code is to call `ActiveRecord::Base`
121
+ # class methods on Association. It works because it's automatically delegated, but
122
+ # can introduce subtle bugs because it sets the global scope.
123
+ # We can't deprecate this behavior because gems might depend on it, however we
124
+ # can ban it from Active Record's own test suite to avoid regressions.
125
+ raise NotImplementedError, "Active Record code shouldn't rely on association delegation into ActiveRecord::Base methods"
126
+ elsif !Delegation.uncacheable_methods.include?(method)
127
+ model.generate_relation_method(method)
119
128
  end
120
- scoping { @klass.public_send(method, ...) }
129
+
130
+ scoping { model.public_send(method, ...) }
121
131
  else
122
132
  super
123
133
  end
@@ -125,19 +135,19 @@ module ActiveRecord
125
135
  end
126
136
 
127
137
  module ClassMethods # :nodoc:
128
- def create(klass, *args, **kwargs)
129
- relation_class_for(klass).new(klass, *args, **kwargs)
138
+ def create(model, ...)
139
+ relation_class_for(model).new(model, ...)
130
140
  end
131
141
 
132
142
  private
133
- def relation_class_for(klass)
134
- klass.relation_delegate_class(self)
143
+ def relation_class_for(model)
144
+ model.relation_delegate_class(self)
135
145
  end
136
146
  end
137
147
 
138
148
  private
139
149
  def respond_to_missing?(method, _)
140
- super || @klass.respond_to?(method)
150
+ super || model.respond_to?(method)
141
151
  end
142
152
  end
143
153
  end
@@ -24,22 +24,22 @@ module ActiveRecord
24
24
  # TravelRoute.primary_key = [:origin, :destination]
25
25
  #
26
26
  # TravelRoute.find(["Ottawa", "London"])
27
- # => #<TravelRoute origin: "Ottawa", destination: "London">
27
+ # # => #<TravelRoute origin: "Ottawa", destination: "London">
28
28
  #
29
29
  # TravelRoute.find([["Paris", "Montreal"]])
30
- # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
30
+ # # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
31
31
  #
32
32
  # TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
33
- # => [
34
- # #<TravelRoute origin: "New York", destination: "Las Vegas">,
35
- # #<TravelRoute origin: "New York", destination: "Portland">
36
- # ]
33
+ # # => [
34
+ # # #<TravelRoute origin: "New York", destination: "Las Vegas">,
35
+ # # #<TravelRoute origin: "New York", destination: "Portland">
36
+ # # ]
37
37
  #
38
38
  # TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
39
- # => [
40
- # #<TravelRoute origin: "Berlin", destination: "London">,
41
- # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
42
- # ]
39
+ # # => [
40
+ # # #<TravelRoute origin: "Berlin", destination: "London">,
41
+ # # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
42
+ # # ]
43
43
  #
44
44
  # NOTE: The returned records are in the same order as the ids you provide.
45
45
  # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
@@ -141,14 +141,14 @@ module ActiveRecord
141
141
  #
142
142
  # Product.where(["price = %?", price]).sole
143
143
  def sole
144
- found, undesired = first(2)
144
+ found, undesired = take(2)
145
145
 
146
146
  if found.nil?
147
147
  raise_record_not_found_exception!
148
- elsif undesired.present?
149
- raise ActiveRecord::SoleRecordExceeded.new(self)
150
- else
148
+ elsif undesired.nil?
151
149
  found
150
+ else
151
+ raise ActiveRecord::SoleRecordExceeded.new(self)
152
152
  end
153
153
  end
154
154
 
@@ -376,7 +376,7 @@ module ActiveRecord
376
376
 
377
377
  skip_query_cache_if_necessary do
378
378
  with_connection do |c|
379
- c.select_rows(relation.arel, "#{name} Exists?").size == 1
379
+ c.select_rows(relation.arel, "#{model.name} Exists?").size == 1
380
380
  end
381
381
  end
382
382
  end
@@ -389,7 +389,7 @@ module ActiveRecord
389
389
  def include?(record)
390
390
  # The existing implementation relies on receiving an Active Record instance as the input parameter named record.
391
391
  # Any non-Active Record object passed to this implementation is guaranteed to return `false`.
392
- return false unless record.is_a?(klass)
392
+ return false unless record.is_a?(model)
393
393
 
394
394
  if loaded? || offset_value || limit_value || having_clause.any?
395
395
  records.include?(record)
@@ -415,21 +415,22 @@ module ActiveRecord
415
415
  # the expected number of results should be provided in the +expected_size+
416
416
  # argument.
417
417
  def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
418
- conditions = " [#{arel.where_sql(klass)}]" unless where_clause.empty?
418
+ conditions = " [#{arel.where_sql(model)}]" unless where_clause.empty?
419
419
 
420
- name = @klass.name
420
+ name = model.name
421
421
 
422
422
  if ids.nil?
423
423
  error = +"Couldn't find #{name}"
424
424
  error << " with#{conditions}" if conditions
425
425
  raise RecordNotFound.new(error, name, key)
426
426
  elsif Array.wrap(ids).size == 1
427
- error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
427
+ id = Array.wrap(ids)[0]
428
+ error = "Couldn't find #{name} with '#{key}'=#{id.inspect}#{conditions}"
428
429
  raise RecordNotFound.new(error, name, key, ids)
429
430
  else
430
431
  error = +"Couldn't find all #{name.pluralize} with '#{key}': "
431
- error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
432
- error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
432
+ error << "(#{ids.map(&:inspect).join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
433
+ error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.map(&:inspect).join(', ')}." if not_found_ids
433
434
  raise RecordNotFound.new(error, name, key, ids)
434
435
  end
435
436
  end
@@ -441,7 +442,7 @@ module ActiveRecord
441
442
  if distinct_value && offset_value
442
443
  relation = except(:order).limit!(1)
443
444
  else
444
- relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
445
+ relation = except(:select, :distinct, :order)._select!(Arel.sql(ONE_AS_ONE, retryable: true)).limit!(1)
445
446
  end
446
447
 
447
448
  case conditions
@@ -471,7 +472,7 @@ module ActiveRecord
471
472
  )
472
473
  )
473
474
  relation = skip_query_cache_if_necessary do
474
- klass.with_connection do |c|
475
+ model.with_connection do |c|
475
476
  c.distinct_relation_for_primary_key(relation)
476
477
  end
477
478
  end
@@ -489,9 +490,9 @@ module ActiveRecord
489
490
  end
490
491
 
491
492
  def find_with_ids(*ids)
492
- raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
493
+ raise UnknownPrimaryKey.new(model) if primary_key.nil?
493
494
 
494
- expects_array = if klass.composite_primary_key?
495
+ expects_array = if model.composite_primary_key?
495
496
  ids.first.first.is_a?(Array)
496
497
  else
497
498
  ids.first.is_a?(Array)
@@ -503,7 +504,7 @@ module ActiveRecord
503
504
 
504
505
  ids = ids.compact.uniq
505
506
 
506
- model_name = @klass.name
507
+ model_name = model.name
507
508
 
508
509
  case ids.size
509
510
  when 0
@@ -525,7 +526,7 @@ module ActiveRecord
525
526
  MSG
526
527
  end
527
528
 
528
- relation = if klass.composite_primary_key?
529
+ relation = if model.composite_primary_key?
529
530
  where(primary_key.zip(id).to_h)
530
531
  else
531
532
  where(primary_key => id)
@@ -573,7 +574,7 @@ module ActiveRecord
573
574
  result = relation.records
574
575
 
575
576
  if result.size == ids.size
576
- result.in_order_of(:id, ids.map { |id| @klass.type_for_attribute(primary_key).cast(id) })
577
+ result.in_order_of(:id, ids.map { |id| model.type_for_attribute(primary_key).cast(id) })
577
578
  else
578
579
  raise_record_not_found_exception!(ids, result.size, ids.size)
579
580
  end
@@ -638,24 +639,40 @@ module ActiveRecord
638
639
  end
639
640
 
640
641
  def ordered_relation
641
- if order_values.empty? && (implicit_order_column || !query_constraints_list.nil? || primary_key)
642
- order(_order_columns.map { |column| table[column].asc })
642
+ if order_values.empty?
643
+ if !_order_columns.empty?
644
+ return order(_order_columns.map { |column| table[column].asc })
645
+ end
646
+
647
+ if ActiveRecord.raise_on_missing_required_finder_order_columns
648
+ raise MissingRequiredOrderError, <<~MSG.squish
649
+ Relation has no order values, and #{model} has no order columns to use as a default.
650
+ Set at least one of `implicit_order_column`, `query_constraints` or `primary_key` on
651
+ the model when no `order `is specified on the relation.
652
+ MSG
653
+ else
654
+ ActiveRecord.deprecator.warn(<<~MSG)
655
+ Calling order dependent finder methods (e.g. `#first`, `#second`) without `order` values on the relation,
656
+ and on a model (#{model}) that does not have any order columns (`implicit_order_column`, `query_constraints`,
657
+ or `primary_key`) to fall back on is deprecated and will raise `ActiveRecord::MissingRequiredOrderError`
658
+ in Rails 8.2.
659
+ MSG
660
+
661
+ self
662
+ end
643
663
  else
644
664
  self
645
665
  end
646
666
  end
647
667
 
648
668
  def _order_columns
649
- oc = []
669
+ columns = Array(model.implicit_order_column)
650
670
 
651
- oc << implicit_order_column if implicit_order_column
652
- oc << query_constraints_list if query_constraints_list
671
+ return columns.compact if columns.length.positive? && columns.last.nil?
653
672
 
654
- if primary_key && query_constraints_list.nil?
655
- oc << primary_key
656
- end
673
+ columns += Array(model.query_constraints_list || model.primary_key)
657
674
 
658
- oc.flatten.uniq.compact
675
+ columns.uniq.compact
659
676
  end
660
677
  end
661
678
  end