activerecord 7.1.5.1 → 8.0.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 +369 -2484
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +2 -1
  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 +21 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +7 -6
  11. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  13. data/lib/active_record/associations/builder/has_many.rb +3 -4
  14. data/lib/active_record/associations/builder/has_one.rb +3 -4
  15. data/lib/active_record/associations/collection_association.rb +17 -9
  16. data/lib/active_record/associations/collection_proxy.rb +14 -1
  17. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  18. data/lib/active_record/associations/errors.rb +265 -0
  19. data/lib/active_record/associations/has_many_association.rb +1 -1
  20. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  21. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  22. data/lib/active_record/associations/nested_error.rb +47 -0
  23. data/lib/active_record/associations/preloader/association.rb +4 -3
  24. data/lib/active_record/associations/preloader/branch.rb +7 -1
  25. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  26. data/lib/active_record/associations/singular_association.rb +14 -3
  27. data/lib/active_record/associations/through_association.rb +1 -1
  28. data/lib/active_record/associations.rb +92 -295
  29. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  30. data/lib/active_record/attribute_assignment.rb +0 -2
  31. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  32. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  33. data/lib/active_record/attribute_methods/read.rb +1 -13
  34. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
  36. data/lib/active_record/attribute_methods.rb +71 -75
  37. data/lib/active_record/attributes.rb +63 -49
  38. data/lib/active_record/autosave_association.rb +92 -57
  39. data/lib/active_record/base.rb +2 -3
  40. data/lib/active_record/callbacks.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
  42. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
  44. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
  45. data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
  46. data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
  47. data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
  48. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  49. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
  50. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
  51. data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
  52. data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
  53. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  56. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
  58. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
  60. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  66. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  67. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  68. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  69. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
  70. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
  72. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
  73. data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
  74. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  75. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
  76. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  77. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  78. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  79. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
  80. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
  81. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
  82. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  83. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
  84. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  85. data/lib/active_record/connection_adapters.rb +65 -0
  86. data/lib/active_record/connection_handling.rb +74 -37
  87. data/lib/active_record/core.rb +132 -51
  88. data/lib/active_record/counter_cache.rb +19 -10
  89. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  90. data/lib/active_record/database_configurations/database_config.rb +23 -4
  91. data/lib/active_record/database_configurations/hash_config.rb +46 -34
  92. data/lib/active_record/database_configurations/url_config.rb +20 -1
  93. data/lib/active_record/database_configurations.rb +1 -1
  94. data/lib/active_record/delegated_type.rb +41 -17
  95. data/lib/active_record/dynamic_matchers.rb +2 -2
  96. data/lib/active_record/encryption/config.rb +3 -1
  97. data/lib/active_record/encryption/encryptable_record.rb +7 -7
  98. data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
  99. data/lib/active_record/encryption/encryptor.rb +28 -6
  100. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  101. data/lib/active_record/encryption/key_provider.rb +1 -1
  102. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  103. data/lib/active_record/encryption/message_serializer.rb +4 -0
  104. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  105. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  106. data/lib/active_record/encryption/scheme.rb +8 -1
  107. data/lib/active_record/enum.rb +20 -16
  108. data/lib/active_record/errors.rb +54 -20
  109. data/lib/active_record/explain.rb +13 -24
  110. data/lib/active_record/fixtures.rb +37 -33
  111. data/lib/active_record/future_result.rb +21 -13
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +4 -2
  114. data/lib/active_record/insert_all.rb +19 -16
  115. data/lib/active_record/integration.rb +4 -1
  116. data/lib/active_record/internal_metadata.rb +48 -34
  117. data/lib/active_record/locking/optimistic.rb +8 -7
  118. data/lib/active_record/log_subscriber.rb +5 -32
  119. data/lib/active_record/message_pack.rb +1 -1
  120. data/lib/active_record/migration/command_recorder.rb +33 -14
  121. data/lib/active_record/migration/compatibility.rb +8 -3
  122. data/lib/active_record/migration/default_strategy.rb +4 -5
  123. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  124. data/lib/active_record/migration.rb +104 -98
  125. data/lib/active_record/model_schema.rb +32 -70
  126. data/lib/active_record/nested_attributes.rb +15 -9
  127. data/lib/active_record/normalization.rb +3 -7
  128. data/lib/active_record/persistence.rb +127 -451
  129. data/lib/active_record/query_cache.rb +19 -8
  130. data/lib/active_record/query_logs.rb +104 -37
  131. data/lib/active_record/query_logs_formatter.rb +17 -28
  132. data/lib/active_record/querying.rb +24 -12
  133. data/lib/active_record/railtie.rb +26 -68
  134. data/lib/active_record/railties/controller_runtime.rb +13 -4
  135. data/lib/active_record/railties/databases.rake +43 -61
  136. data/lib/active_record/reflection.rb +112 -53
  137. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  138. data/lib/active_record/relation/batches.rb +138 -72
  139. data/lib/active_record/relation/calculations.rb +122 -82
  140. data/lib/active_record/relation/delegation.rb +30 -22
  141. data/lib/active_record/relation/finder_methods.rb +32 -18
  142. data/lib/active_record/relation/merger.rb +12 -14
  143. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  144. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  145. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  146. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  147. data/lib/active_record/relation/predicate_builder.rb +16 -3
  148. data/lib/active_record/relation/query_attribute.rb +1 -1
  149. data/lib/active_record/relation/query_methods.rb +317 -101
  150. data/lib/active_record/relation/spawn_methods.rb +3 -19
  151. data/lib/active_record/relation/where_clause.rb +7 -19
  152. data/lib/active_record/relation.rb +561 -119
  153. data/lib/active_record/result.rb +95 -46
  154. data/lib/active_record/runtime_registry.rb +39 -0
  155. data/lib/active_record/sanitization.rb +31 -25
  156. data/lib/active_record/schema.rb +8 -6
  157. data/lib/active_record/schema_dumper.rb +53 -20
  158. data/lib/active_record/schema_migration.rb +31 -14
  159. data/lib/active_record/scoping/named.rb +6 -2
  160. data/lib/active_record/signed_id.rb +24 -4
  161. data/lib/active_record/statement_cache.rb +19 -19
  162. data/lib/active_record/store.rb +7 -3
  163. data/lib/active_record/table_metadata.rb +2 -13
  164. data/lib/active_record/tasks/database_tasks.rb +87 -58
  165. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
  166. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  167. data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
  168. data/lib/active_record/test_fixtures.rb +98 -89
  169. data/lib/active_record/testing/query_assertions.rb +121 -0
  170. data/lib/active_record/timestamp.rb +2 -2
  171. data/lib/active_record/token_for.rb +22 -12
  172. data/lib/active_record/touch_later.rb +1 -1
  173. data/lib/active_record/transaction.rb +132 -0
  174. data/lib/active_record/transactions.rb +72 -17
  175. data/lib/active_record/translation.rb +0 -2
  176. data/lib/active_record/type/serialized.rb +1 -3
  177. data/lib/active_record/type_caster/connection.rb +4 -4
  178. data/lib/active_record/validations/associated.rb +9 -3
  179. data/lib/active_record/validations/uniqueness.rb +23 -18
  180. data/lib/active_record/validations.rb +4 -1
  181. data/lib/active_record.rb +138 -57
  182. data/lib/arel/alias_predication.rb +1 -1
  183. data/lib/arel/collectors/bind.rb +4 -2
  184. data/lib/arel/collectors/composite.rb +7 -0
  185. data/lib/arel/collectors/sql_string.rb +2 -2
  186. data/lib/arel/collectors/substitute_binds.rb +3 -3
  187. data/lib/arel/nodes/binary.rb +1 -7
  188. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  189. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  190. data/lib/arel/nodes/node.rb +5 -4
  191. data/lib/arel/nodes/sql_literal.rb +8 -1
  192. data/lib/arel/nodes.rb +2 -2
  193. data/lib/arel/predications.rb +1 -1
  194. data/lib/arel/select_manager.rb +1 -1
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/tree_manager.rb +3 -2
  197. data/lib/arel/update_manager.rb +2 -1
  198. data/lib/arel/visitors/dot.rb +1 -0
  199. data/lib/arel/visitors/mysql.rb +9 -4
  200. data/lib/arel/visitors/postgresql.rb +1 -12
  201. data/lib/arel/visitors/sqlite.rb +25 -0
  202. data/lib/arel/visitors/to_sql.rb +29 -16
  203. data/lib/arel.rb +7 -3
  204. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  205. metadata +18 -16
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -234,7 +234,7 @@ module ActiveRecord
234
234
  if operation == "count"
235
235
  unless distinct_value || distinct_select?(column_name || select_for_count)
236
236
  relation.distinct!
237
- relation.select_values = [ klass.primary_key || table[Arel.star] ]
237
+ relation.select_values = Array(model.primary_key || table[Arel.star])
238
238
  end
239
239
  # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
240
240
  relation.order_values = [] if group_values.empty?
@@ -275,6 +275,14 @@ module ActiveRecord
275
275
  # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
276
276
  # # => [2, 3]
277
277
  #
278
+ # Comment.joins(:person).pluck(:id, person: :id)
279
+ # # SELECT comments.id, person.id FROM comments INNER JOIN people person ON person.id = comments.person_id
280
+ # # => [[1, 2], [2, 2]]
281
+ #
282
+ # Comment.joins(:person).pluck(:id, person: [:id, :name])
283
+ # # SELECT comments.id, person.id, person.name FROM comments INNER JOIN people person ON person.id = comments.person_id
284
+ # # => [[1, 2, 'David'], [2, 2, 'David']]
285
+ #
278
286
  # Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
279
287
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
280
288
  # # => ['0', '27761', '173']
@@ -302,15 +310,17 @@ module ActiveRecord
302
310
  relation = apply_join_dependency
303
311
  relation.pluck(*column_names)
304
312
  else
305
- klass.disallow_raw_sql!(column_names.flatten)
306
- columns = arel_columns(column_names)
313
+ model.disallow_raw_sql!(flattened_args(column_names))
307
314
  relation = spawn
315
+ columns = relation.arel_columns(column_names)
308
316
  relation.select_values = columns
309
317
  result = skip_query_cache_if_necessary do
310
318
  if where_clause.contradiction?
311
319
  ActiveRecord::Result.empty(async: @async)
312
320
  else
313
- klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
321
+ model.with_connection do |c|
322
+ c.select_all(relation.arel, "#{model.name} Pluck", async: @async)
323
+ end
314
324
  end
315
325
  end
316
326
  result.then do |result|
@@ -357,7 +367,7 @@ module ActiveRecord
357
367
  # Returns the base model's ID's for the relation using the table's primary key
358
368
  #
359
369
  # Person.ids # SELECT people.id FROM people
360
- # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
370
+ # Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
361
371
  def ids
362
372
  primary_key_array = Array(primary_key)
363
373
 
@@ -385,7 +395,9 @@ module ActiveRecord
385
395
  ActiveRecord::Result.empty
386
396
  else
387
397
  skip_query_cache_if_necessary do
388
- klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
398
+ model.with_connection do |c|
399
+ c.select_all(relation, "#{model.name} Ids", async: @async)
400
+ end
389
401
  end
390
402
  end
391
403
 
@@ -398,9 +410,21 @@ module ActiveRecord
398
410
  async.ids
399
411
  end
400
412
 
413
+ protected
414
+ def aggregate_column(column_name)
415
+ case column_name
416
+ when Arel::Expressions
417
+ column_name
418
+ when :all
419
+ Arel.star
420
+ else
421
+ arel_column(column_name)
422
+ end
423
+ end
424
+
401
425
  private
402
426
  def all_attributes?(column_names)
403
- (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
427
+ (column_names.map(&:to_s) - model.attribute_names - model.attribute_aliases.keys).empty?
404
428
  end
405
429
 
406
430
  def has_include?(column_name)
@@ -438,20 +462,12 @@ module ActiveRecord
438
462
  column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
439
463
  end
440
464
 
441
- def aggregate_column(column_name)
442
- return column_name if Arel::Expressions === column_name
443
-
444
- arel_column(column_name.to_s) do |name|
445
- Arel.sql(column_name == :all ? "*" : name)
446
- end
447
- end
448
-
449
465
  def operation_over_aggregate_column(column, operation, distinct)
450
466
  operation == "count" ? column.count(distinct) : column.public_send(operation)
451
467
  end
452
468
 
453
469
  def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
454
- if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
470
+ if build_count_subquery?(operation, column_name, distinct)
455
471
  # Shortcut when limit is zero.
456
472
  return 0 if limit_value == 0
457
473
 
@@ -461,7 +477,7 @@ module ActiveRecord
461
477
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
462
478
  relation = unscope(:order).distinct!(false)
463
479
 
464
- column = aggregate_column(column_name)
480
+ column = relation.aggregate_column(column_name)
465
481
  select_value = operation_over_aggregate_column(column, operation, distinct)
466
482
  select_value.distinct = true if operation == "sum" && distinct
467
483
 
@@ -471,10 +487,16 @@ module ActiveRecord
471
487
  end
472
488
 
473
489
  query_result = if relation.where_clause.contradiction?
474
- ActiveRecord::Result.empty
490
+ if @async
491
+ FutureResult.wrap(ActiveRecord::Result.empty)
492
+ else
493
+ ActiveRecord::Result.empty
494
+ end
475
495
  else
476
496
  skip_query_cache_if_necessary do
477
- @klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
497
+ model.with_connection do |c|
498
+ c.select_all(query_builder, "#{model.name} #{operation.capitalize}", async: @async)
499
+ end
478
500
  end
479
501
  end
480
502
 
@@ -494,81 +516,87 @@ module ActiveRecord
494
516
  group_fields = group_fields.uniq if group_fields.size > 1
495
517
 
496
518
  if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
497
- association = klass._reflect_on_association(group_fields.first)
519
+ association = model._reflect_on_association(group_fields.first)
498
520
  associated = association && association.belongs_to? # only count belongs_to associations
499
521
  group_fields = Array(association.foreign_key) if associated
500
522
  end
501
- group_fields = arel_columns(group_fields)
502
523
 
503
- column_alias_tracker = ColumnAliasTracker.new(connection)
524
+ relation = except(:group).distinct!(false)
525
+ group_fields = relation.arel_columns(group_fields)
504
526
 
505
- group_aliases = group_fields.map { |field|
506
- field = connection.visitor.compile(field) if Arel.arel_node?(field)
507
- column_alias_tracker.alias_for(field.to_s.downcase)
508
- }
509
- group_columns = group_aliases.zip(group_fields)
527
+ model.with_connection do |connection|
528
+ column_alias_tracker = ColumnAliasTracker.new(connection)
510
529
 
511
- column = aggregate_column(column_name)
512
- column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
513
- select_value = operation_over_aggregate_column(column, operation, distinct)
514
- select_value.as(connection.quote_column_name(column_alias))
530
+ group_aliases = group_fields.map { |field|
531
+ field = connection.visitor.compile(field) if Arel.arel_node?(field)
532
+ column_alias_tracker.alias_for(field.to_s.downcase)
533
+ }
534
+ group_columns = group_aliases.zip(group_fields)
515
535
 
516
- select_values = [select_value]
517
- select_values += self.select_values unless having_clause.empty?
536
+ column = relation.aggregate_column(column_name)
537
+ column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
538
+ select_value = operation_over_aggregate_column(column, operation, distinct)
539
+ select_value.as(model.adapter_class.quote_column_name(column_alias))
518
540
 
519
- select_values.concat group_columns.map { |aliaz, field|
520
- aliaz = connection.quote_column_name(aliaz)
521
- if field.respond_to?(:as)
522
- field.as(aliaz)
523
- else
524
- "#{field} AS #{aliaz}"
525
- end
526
- }
541
+ select_values = [select_value]
542
+ select_values += self.select_values unless having_clause.empty?
527
543
 
528
- relation = except(:group).distinct!(false)
529
- relation.group_values = group_fields
530
- relation.select_values = select_values
531
-
532
- result = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async) }
533
- result.then do |calculated_data|
534
- if association
535
- key_ids = calculated_data.collect { |row| row[group_aliases.first] }
536
- key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
537
- key_records = key_records.index_by(&:id)
538
- end
544
+ select_values.concat group_columns.map { |aliaz, field|
545
+ aliaz = model.adapter_class.quote_column_name(aliaz)
546
+ if field.respond_to?(:as)
547
+ field.as(aliaz)
548
+ else
549
+ "#{field} AS #{aliaz}"
550
+ end
551
+ }
539
552
 
540
- key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
541
- types[aliaz] = col_name.try(:type_caster) ||
542
- type_for(col_name) do
543
- calculated_data.column_types.fetch(aliaz, Type.default_value)
544
- end
553
+ relation.group_values = group_fields
554
+ relation.select_values = select_values
555
+
556
+ result = skip_query_cache_if_necessary do
557
+ connection.select_all(relation.arel, "#{model.name} #{operation.capitalize}", async: @async)
545
558
  end
546
559
 
547
- hash_rows = calculated_data.cast_values(key_types).map! do |row|
548
- calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
549
- hash[col_name] = row[i]
560
+ result.then do |calculated_data|
561
+ if association
562
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
563
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
564
+ key_records = key_records.index_by(&:id)
550
565
  end
551
- end
552
566
 
553
- if operation != "count"
554
- type = column.try(:type_caster) ||
555
- lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
556
- type = type.subtype if Enum::EnumType === type
557
- end
567
+ key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
568
+ types[aliaz] = col_name.try(:type_caster) ||
569
+ type_for(col_name) do
570
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
571
+ end
572
+ end
558
573
 
559
- hash_rows.each_with_object({}) do |row, result|
560
- key = group_aliases.map { |aliaz| row[aliaz] }
561
- key = key.first if key.size == 1
562
- key = key_records[key] if associated
574
+ hash_rows = calculated_data.cast_values(key_types).map! do |row|
575
+ calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
576
+ hash[col_name] = row[i]
577
+ end
578
+ end
563
579
 
564
- result[key] = type_cast_calculated_value(row[column_alias], operation, type)
580
+ if operation != "count"
581
+ type = column.try(:type_caster) ||
582
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
583
+ type = type.subtype if Enum::EnumType === type
584
+ end
585
+
586
+ hash_rows.each_with_object({}) do |row, result|
587
+ key = group_aliases.map { |aliaz| row[aliaz] }
588
+ key = key.first if key.size == 1
589
+ key = key_records[key] if associated
590
+
591
+ result[key] = type_cast_calculated_value(row[column_alias], operation, type)
592
+ end
565
593
  end
566
594
  end
567
595
  end
568
596
 
569
597
  def type_for(field, &block)
570
598
  field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
571
- @klass.type_for_attribute(field_name, &block)
599
+ model.type_for_attribute(field_name, &block)
572
600
  end
573
601
 
574
602
  def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
@@ -581,15 +609,15 @@ module ActiveRecord
581
609
 
582
610
  def type_cast_pluck_values(result, columns)
583
611
  cast_types = if result.columns.size != columns.size
584
- klass.attribute_types
612
+ model.attribute_types
585
613
  else
586
614
  join_dependencies = nil
587
615
  columns.map.with_index do |column, i|
588
616
  column.try(:type_caster) ||
589
- klass.attribute_types.fetch(name = result.columns[i]) do
617
+ model.attribute_types.fetch(name = result.columns[i]) do
590
618
  join_dependencies ||= build_join_dependencies
591
619
  lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
592
- result.column_types[name] || Type.default_value
620
+ result.column_types[i] || Type.default_value
593
621
  end
594
622
  end
595
623
  end
@@ -615,27 +643,39 @@ module ActiveRecord
615
643
  end
616
644
 
617
645
  def select_for_count
618
- if select_values.present?
619
- return select_values.first if select_values.one?
620
- select_values.join(", ")
621
- else
646
+ if select_values.empty?
622
647
  :all
648
+ else
649
+ with_connection do |conn|
650
+ arel_columns(select_values).map { |column| conn.visitor.compile(column) }.join(", ")
651
+ end
623
652
  end
624
653
  end
625
654
 
655
+ def build_count_subquery?(operation, column_name, distinct)
656
+ # SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or
657
+ # multiple columns, so we need to use subquery for this.
658
+ operation == "count" &&
659
+ (((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?)
660
+ end
661
+
626
662
  def build_count_subquery(relation, column_name, distinct)
627
663
  if column_name == :all
628
664
  column_alias = Arel.star
629
665
  relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
630
666
  else
631
667
  column_alias = Arel.sql("count_column")
632
- relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
668
+ relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
633
669
  end
634
670
 
635
- subquery_alias = Arel.sql("subquery_for_count")
671
+ subquery_alias = Arel.sql("subquery_for_count", retryable: true)
636
672
  select_value = operation_over_aggregate_column(column_alias, "count", false)
637
673
 
638
- relation.build_subquery(subquery_alias, select_value)
674
+ if column_name == :all
675
+ relation.unscope(:order).build_subquery(subquery_alias, select_value)
676
+ else
677
+ relation.build_subquery(subquery_alias, select_value)
678
+ end
639
679
  end
640
680
  end
641
681
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mutex_m"
4
3
  require "active_support/core_ext/module/delegation"
5
4
 
6
5
  module ActiveRecord
@@ -23,6 +22,9 @@ module ActiveRecord
23
22
  end
24
23
 
25
24
  module DelegateCache # :nodoc:
25
+ @delegate_base_methods = true
26
+ singleton_class.attr_accessor :delegate_base_methods
27
+
26
28
  def relation_delegate_class(klass)
27
29
  @relation_delegate_cache[klass]
28
30
  end
@@ -67,23 +69,22 @@ module ActiveRecord
67
69
  end
68
70
 
69
71
  class GeneratedRelationMethods < Module # :nodoc:
70
- include Mutex_m
72
+ MUTEX = Mutex.new
71
73
 
72
74
  def generate_method(method)
73
- synchronize do
75
+ MUTEX.synchronize do
74
76
  return if method_defined?(method)
75
77
 
76
- if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !DELEGATION_RESERVED_METHOD_NAMES.include?(method.to_s)
78
+ if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
77
79
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
78
80
  def #{method}(...)
79
- scoping { klass.#{method}(...) }
81
+ scoping { model.#{method}(...) }
80
82
  end
81
83
  RUBY
82
84
  else
83
- define_method(method) do |*args, &block|
84
- scoping { klass.public_send(method, *args, &block) }
85
+ define_method(method) do |*args, **kwargs, &block|
86
+ scoping { model.public_send(method, *args, **kwargs, &block) }
85
87
  end
86
- ruby2_keywords(method)
87
88
  end
88
89
  end
89
90
  end
@@ -94,15 +95,15 @@ module ActiveRecord
94
95
 
95
96
  # This module creates compiled delegation methods dynamically at runtime, which makes
96
97
  # subsequent calls to that method faster by avoiding method_missing. The delegations
97
- # may vary depending on the klass of a relation, so we create a subclass of Relation
98
- # for each different klass, and the delegations are compiled into that subclass only.
98
+ # may vary depending on the model of a relation, so we create a subclass of Relation
99
+ # for each different model, and the delegations are compiled into that subclass only.
99
100
 
100
101
  delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
101
102
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
102
103
  :to_sentence, :to_fs, :to_formatted_s, :as_json,
103
104
  :shuffle, :split, :slice, :index, :rindex, to: :records
104
105
 
105
- delegate :primary_key, :connection, :transaction, to: :klass
106
+ delegate :primary_key, :with_connection, :connection, :table_name, :transaction, :sanitize_sql_like, :unscoped, :name, to: :model
106
107
 
107
108
  module ClassSpecificRelation # :nodoc:
108
109
  extend ActiveSupport::Concern
@@ -114,33 +115,40 @@ module ActiveRecord
114
115
  end
115
116
 
116
117
  private
117
- def method_missing(method, *args, &block)
118
- if @klass.respond_to?(method)
119
- unless Delegation.uncacheable_methods.include?(method)
120
- @klass.generate_relation_method(method)
118
+ def method_missing(method, ...)
119
+ if model.respond_to?(method)
120
+ if !DelegateCache.delegate_base_methods && Base.respond_to?(method)
121
+ # A common mistake in Active Record's own code is to call `ActiveRecord::Base`
122
+ # class methods on Association. It works because it's automatically delegated, but
123
+ # can introduce subtle bugs because it sets the global scope.
124
+ # We can't deprecate this behavior because gems might depend on it, however we
125
+ # can ban it from Active Record's own test suite to avoid regressions.
126
+ raise NotImplementedError, "Active Record code shouldn't rely on association delegation into ActiveRecord::Base methods"
127
+ elsif !Delegation.uncacheable_methods.include?(method)
128
+ model.generate_relation_method(method)
121
129
  end
122
- scoping { @klass.public_send(method, *args, &block) }
130
+
131
+ scoping { model.public_send(method, ...) }
123
132
  else
124
133
  super
125
134
  end
126
135
  end
127
- ruby2_keywords(:method_missing)
128
136
  end
129
137
 
130
138
  module ClassMethods # :nodoc:
131
- def create(klass, *args, **kwargs)
132
- relation_class_for(klass).new(klass, *args, **kwargs)
139
+ def create(model, ...)
140
+ relation_class_for(model).new(model, ...)
133
141
  end
134
142
 
135
143
  private
136
- def relation_class_for(klass)
137
- klass.relation_delegate_class(self)
144
+ def relation_class_for(model)
145
+ model.relation_delegate_class(self)
138
146
  end
139
147
  end
140
148
 
141
149
  private
142
150
  def respond_to_missing?(method, _)
143
- super || @klass.respond_to?(method)
151
+ super || model.respond_to?(method)
144
152
  end
145
153
  end
146
154
  end
@@ -87,6 +87,14 @@ module ActiveRecord
87
87
  #
88
88
  # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
89
89
  # # returns an Array of the required fields.
90
+ #
91
+ # ==== Edge Cases
92
+ #
93
+ # Person.find(37) # raises ActiveRecord::RecordNotFound exception if the record with the given ID does not exist.
94
+ # Person.find([37]) # raises ActiveRecord::RecordNotFound exception if the record with the given ID in the input array does not exist.
95
+ # Person.find(nil) # raises ActiveRecord::RecordNotFound exception if the argument is nil.
96
+ # Person.find([]) # returns an empty array if the argument is an empty array.
97
+ # Person.find # raises ActiveRecord::RecordNotFound exception if the argument is not provided.
90
98
  def find(*args)
91
99
  return super if block_given?
92
100
  find_with_ids(*args)
@@ -137,10 +145,10 @@ module ActiveRecord
137
145
 
138
146
  if found.nil?
139
147
  raise_record_not_found_exception!
140
- elsif undesired.present?
141
- raise ActiveRecord::SoleRecordExceeded.new(self)
142
- else
148
+ elsif undesired.nil?
143
149
  found
150
+ else
151
+ raise ActiveRecord::SoleRecordExceeded.new(model)
144
152
  end
145
153
  end
146
154
 
@@ -366,7 +374,11 @@ module ActiveRecord
366
374
  relation = construct_relation_for_exists(conditions)
367
375
  return false if relation.where_clause.contradiction?
368
376
 
369
- skip_query_cache_if_necessary { connection.select_rows(relation.arel, "#{name} Exists?").size == 1 }
377
+ skip_query_cache_if_necessary do
378
+ with_connection do |c|
379
+ c.select_rows(relation.arel, "#{model.name} Exists?").size == 1
380
+ end
381
+ end
370
382
  end
371
383
 
372
384
  # Returns true if the relation contains the given record or false otherwise.
@@ -377,7 +389,7 @@ module ActiveRecord
377
389
  def include?(record)
378
390
  # The existing implementation relies on receiving an Active Record instance as the input parameter named record.
379
391
  # Any non-Active Record object passed to this implementation is guaranteed to return `false`.
380
- return false unless record.is_a?(klass)
392
+ return false unless record.is_a?(model)
381
393
 
382
394
  if loaded? || offset_value || limit_value || having_clause.any?
383
395
  records.include?(record)
@@ -403,9 +415,9 @@ module ActiveRecord
403
415
  # the expected number of results should be provided in the +expected_size+
404
416
  # argument.
405
417
  def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
406
- conditions = " [#{arel.where_sql(klass)}]" unless where_clause.empty?
418
+ conditions = " [#{arel.where_sql(model)}]" unless where_clause.empty?
407
419
 
408
- name = @klass.name
420
+ name = model.name
409
421
 
410
422
  if ids.nil?
411
423
  error = +"Couldn't find #{name}"
@@ -459,7 +471,9 @@ module ActiveRecord
459
471
  )
460
472
  )
461
473
  relation = skip_query_cache_if_necessary do
462
- klass.connection.distinct_relation_for_primary_key(relation)
474
+ model.with_connection do |c|
475
+ c.distinct_relation_for_primary_key(relation)
476
+ end
463
477
  end
464
478
  end
465
479
 
@@ -475,9 +489,9 @@ module ActiveRecord
475
489
  end
476
490
 
477
491
  def find_with_ids(*ids)
478
- raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
492
+ raise UnknownPrimaryKey.new(model) if primary_key.nil?
479
493
 
480
- expects_array = if klass.composite_primary_key?
494
+ expects_array = if model.composite_primary_key?
481
495
  ids.first.first.is_a?(Array)
482
496
  else
483
497
  ids.first.is_a?(Array)
@@ -489,7 +503,7 @@ module ActiveRecord
489
503
 
490
504
  ids = ids.compact.uniq
491
505
 
492
- model_name = @klass.name
506
+ model_name = model.name
493
507
 
494
508
  case ids.size
495
509
  when 0
@@ -511,7 +525,7 @@ module ActiveRecord
511
525
  MSG
512
526
  end
513
527
 
514
- relation = if klass.composite_primary_key?
528
+ relation = if model.composite_primary_key?
515
529
  where(primary_key.zip(id).to_h)
516
530
  else
517
531
  where(primary_key => id)
@@ -559,7 +573,7 @@ module ActiveRecord
559
573
  result = relation.records
560
574
 
561
575
  if result.size == ids.size
562
- result.in_order_of(:id, ids.map { |id| @klass.type_for_attribute(primary_key).cast(id) })
576
+ result.in_order_of(:id, ids.map { |id| model.type_for_attribute(primary_key).cast(id) })
563
577
  else
564
578
  raise_record_not_found_exception!(ids, result.size, ids.size)
565
579
  end
@@ -624,7 +638,7 @@ module ActiveRecord
624
638
  end
625
639
 
626
640
  def ordered_relation
627
- if order_values.empty? && (implicit_order_column || !query_constraints_list.nil? || primary_key)
641
+ if order_values.empty? && (model.implicit_order_column || !model.query_constraints_list.nil? || primary_key)
628
642
  order(_order_columns.map { |column| table[column].asc })
629
643
  else
630
644
  self
@@ -634,11 +648,11 @@ module ActiveRecord
634
648
  def _order_columns
635
649
  oc = []
636
650
 
637
- oc << implicit_order_column if implicit_order_column
638
- oc << query_constraints_list if query_constraints_list
651
+ oc << model.implicit_order_column if model.implicit_order_column
652
+ oc << model.query_constraints_list if model.query_constraints_list
639
653
 
640
- if primary_key && query_constraints_list.nil?
641
- oc << primary_key
654
+ if model.primary_key && model.query_constraints_list.nil?
655
+ oc << model.primary_key
642
656
  end
643
657
 
644
658
  oc.flatten.uniq.compact