activerecord 7.1.5.1 → 7.2.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -2445
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +9 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +14 -7
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +6 -4
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +1 -1
  17. data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
  18. data/lib/active_record/associations/join_dependency.rb +5 -5
  19. data/lib/active_record/associations/nested_error.rb +47 -0
  20. data/lib/active_record/associations/preloader/association.rb +2 -1
  21. data/lib/active_record/associations/preloader/branch.rb +7 -1
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  23. data/lib/active_record/associations/singular_association.rb +6 -0
  24. data/lib/active_record/associations/through_association.rb +1 -1
  25. data/lib/active_record/associations.rb +33 -16
  26. data/lib/active_record/attribute_assignment.rb +1 -11
  27. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +4 -16
  31. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -10
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +60 -71
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +13 -32
  37. data/lib/active_record/base.rb +2 -3
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -65
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -1
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  59. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -13
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +56 -41
  76. data/lib/active_record/core.rb +53 -37
  77. data/lib/active_record/counter_cache.rb +18 -9
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +24 -0
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
  87. data/lib/active_record/encryption/encryptor.rb +17 -2
  88. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  89. data/lib/active_record/encryption/message_serializer.rb +4 -0
  90. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  91. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption.rb +0 -2
  93. data/lib/active_record/enum.rb +10 -1
  94. data/lib/active_record/errors.rb +16 -11
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixtures.rb +37 -31
  97. data/lib/active_record/future_result.rb +8 -4
  98. data/lib/active_record/gem_version.rb +3 -3
  99. data/lib/active_record/inheritance.rb +4 -2
  100. data/lib/active_record/insert_all.rb +18 -15
  101. data/lib/active_record/integration.rb +4 -1
  102. data/lib/active_record/internal_metadata.rb +48 -34
  103. data/lib/active_record/locking/optimistic.rb +7 -6
  104. data/lib/active_record/log_subscriber.rb +0 -21
  105. data/lib/active_record/marshalling.rb +1 -4
  106. data/lib/active_record/message_pack.rb +1 -1
  107. data/lib/active_record/migration/command_recorder.rb +2 -3
  108. data/lib/active_record/migration/compatibility.rb +5 -3
  109. data/lib/active_record/migration/default_strategy.rb +4 -5
  110. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  111. data/lib/active_record/migration.rb +85 -76
  112. data/lib/active_record/model_schema.rb +28 -68
  113. data/lib/active_record/nested_attributes.rb +13 -16
  114. data/lib/active_record/normalization.rb +3 -7
  115. data/lib/active_record/persistence.rb +30 -352
  116. data/lib/active_record/query_cache.rb +18 -6
  117. data/lib/active_record/query_logs.rb +15 -0
  118. data/lib/active_record/querying.rb +21 -9
  119. data/lib/active_record/railtie.rb +50 -62
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +41 -44
  122. data/lib/active_record/reflection.rb +90 -35
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +3 -3
  125. data/lib/active_record/relation/calculations.rb +94 -61
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +16 -2
  128. data/lib/active_record/relation/merger.rb +4 -6
  129. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  130. data/lib/active_record/relation/predicate_builder.rb +3 -3
  131. data/lib/active_record/relation/query_methods.rb +196 -57
  132. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  133. data/lib/active_record/relation/spawn_methods.rb +2 -18
  134. data/lib/active_record/relation/where_clause.rb +7 -19
  135. data/lib/active_record/relation.rb +496 -72
  136. data/lib/active_record/result.rb +31 -44
  137. data/lib/active_record/runtime_registry.rb +39 -0
  138. data/lib/active_record/sanitization.rb +24 -19
  139. data/lib/active_record/schema.rb +8 -6
  140. data/lib/active_record/schema_dumper.rb +19 -9
  141. data/lib/active_record/schema_migration.rb +30 -14
  142. data/lib/active_record/signed_id.rb +11 -1
  143. data/lib/active_record/statement_cache.rb +7 -7
  144. data/lib/active_record/table_metadata.rb +1 -10
  145. data/lib/active_record/tasks/database_tasks.rb +76 -70
  146. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  147. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  148. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  149. data/lib/active_record/test_fixtures.rb +81 -91
  150. data/lib/active_record/testing/query_assertions.rb +121 -0
  151. data/lib/active_record/timestamp.rb +1 -1
  152. data/lib/active_record/token_for.rb +22 -12
  153. data/lib/active_record/touch_later.rb +1 -1
  154. data/lib/active_record/transaction.rb +68 -0
  155. data/lib/active_record/transactions.rb +43 -14
  156. data/lib/active_record/translation.rb +0 -2
  157. data/lib/active_record/type/serialized.rb +1 -3
  158. data/lib/active_record/type_caster/connection.rb +4 -4
  159. data/lib/active_record/validations/associated.rb +9 -3
  160. data/lib/active_record/validations/uniqueness.rb +14 -10
  161. data/lib/active_record/validations.rb +4 -1
  162. data/lib/active_record.rb +149 -40
  163. data/lib/arel/alias_predication.rb +1 -1
  164. data/lib/arel/collectors/bind.rb +2 -0
  165. data/lib/arel/collectors/composite.rb +7 -0
  166. data/lib/arel/collectors/sql_string.rb +1 -1
  167. data/lib/arel/collectors/substitute_binds.rb +1 -1
  168. data/lib/arel/nodes/binary.rb +0 -6
  169. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  170. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  171. data/lib/arel/nodes/node.rb +4 -3
  172. data/lib/arel/nodes/sql_literal.rb +7 -0
  173. data/lib/arel/nodes.rb +2 -2
  174. data/lib/arel/predications.rb +1 -1
  175. data/lib/arel/select_manager.rb +1 -1
  176. data/lib/arel/tree_manager.rb +3 -2
  177. data/lib/arel/update_manager.rb +2 -1
  178. data/lib/arel/visitors/dot.rb +1 -0
  179. data/lib/arel/visitors/mysql.rb +9 -4
  180. data/lib/arel/visitors/postgresql.rb +1 -12
  181. data/lib/arel/visitors/to_sql.rb +29 -16
  182. data/lib/arel.rb +7 -3
  183. metadata +20 -15
@@ -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:
@@ -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
@@ -377,12 +397,11 @@ module ActiveRecord
377
397
  super()
378
398
  @name = name
379
399
  @scope = scope
380
- @options = options
400
+ @options = normalize_options(options)
381
401
  @active_record = active_record
382
402
  @klass = options[:anonymous_class]
383
403
  @plural_name = active_record.pluralize_table_names ?
384
404
  name.to_s.pluralize : name.to_s
385
- validate_reflection!
386
405
  end
387
406
 
388
407
  def autosave=(autosave)
@@ -409,13 +428,17 @@ module ActiveRecord
409
428
  # a new association object. Use +build_association+ or +create_association+
410
429
  # instead. This allows plugins to hook into association object creation.
411
430
  def klass
412
- @klass ||= compute_class(class_name)
431
+ @klass ||= compute_class(compute_name(class_name))
413
432
  end
414
433
 
415
434
  def compute_class(name)
416
435
  name.constantize
417
436
  end
418
437
 
438
+ def compute_name(name) # :nodoc:
439
+ active_record.name.demodulize == name ? "::#{name}" : name
440
+ end
441
+
419
442
  # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
420
443
  # and +other_aggregation+ has an options hash assigned to it.
421
444
  def ==(other_aggregation)
@@ -435,15 +458,24 @@ module ActiveRecord
435
458
  name.to_s.camelize
436
459
  end
437
460
 
438
- def validate_reflection!
439
- return unless options[:foreign_key].is_a?(Array)
461
+ def normalize_options(options)
462
+ counter_cache = options.delete(:counter_cache)
440
463
 
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
464
+ if counter_cache
465
+ active = true
466
+
467
+ case counter_cache
468
+ when String, Symbol
469
+ column = -counter_cache.to_s
470
+ when Hash
471
+ active = counter_cache.fetch(:active, true)
472
+ column = counter_cache[:column]&.to_s
473
+ end
474
+
475
+ options[:counter_cache] = { active: active, column: column }
476
+ end
477
+
478
+ options
447
479
  end
448
480
  end
449
481
 
@@ -494,6 +526,17 @@ module ActiveRecord
494
526
  @foreign_key = nil
495
527
  @association_foreign_key = nil
496
528
  @association_primary_key = nil
529
+ if options[:query_constraints]
530
+ ActiveRecord.deprecator.warn <<~MSG.squish
531
+ Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is deprecated.
532
+ To maintain current behavior, use the `foreign_key` option instead.
533
+ MSG
534
+ end
535
+
536
+ # If the foreign key is an array, set query constraints options and don't use the foreign key
537
+ if options[:foreign_key].is_a?(Array)
538
+ options[:query_constraints] = options.delete(:foreign_key)
539
+ end
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]
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]
515
566
  options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
516
- elsif options[:foreign_key]
517
- options[:foreign_key].to_s
518
567
  else
519
568
  derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
520
569
 
@@ -711,6 +760,10 @@ module ActiveRecord
711
760
 
712
761
  begin
713
762
  reflection = klass._reflect_on_association(inverse_name)
763
+ if !reflection && active_record.automatically_invert_plural_associations
764
+ plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
765
+ reflection = klass._reflect_on_association(plural_inverse_name)
766
+ end
714
767
  rescue NameError => error
715
768
  raise unless error.name.to_s == class_name
716
769
 
@@ -720,7 +773,7 @@ module ActiveRecord
720
773
  end
721
774
 
722
775
  if valid_inverse_reflection?(reflection)
723
- inverse_name
776
+ reflection.name
724
777
  end
725
778
  end
726
779
  end
@@ -933,7 +986,7 @@ module ActiveRecord
933
986
  end
934
987
 
935
988
  def klass
936
- @klass ||= delegate_reflection.compute_class(class_name)
989
+ @klass ||= delegate_reflection.compute_class(compute_name(class_name))
937
990
  end
938
991
 
939
992
  # Returns the source of the through reflection. It checks both a singularized
@@ -954,6 +1007,8 @@ module ActiveRecord
954
1007
  # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
955
1008
  #
956
1009
  def source_reflection
1010
+ return unless source_reflection_name
1011
+
957
1012
  through_reflection.klass._reflect_on_association(source_reflection_name)
958
1013
  end
959
1014
 
@@ -1111,7 +1166,7 @@ module ActiveRecord
1111
1166
  end
1112
1167
 
1113
1168
  if parent_reflection.nil?
1114
- reflections = active_record.reflections.keys.map(&:to_sym)
1169
+ reflections = active_record.normalized_reflections.keys
1115
1170
 
1116
1171
  if reflections.index(through_reflection.name) > reflections.index(name)
1117
1172
  raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
@@ -77,13 +77,26 @@ module ActiveRecord
77
77
  end
78
78
  end
79
79
 
80
- # Destroys records in batches.
80
+ # Touches records in batches. Returns the total number of rows affected.
81
+ #
82
+ # Person.in_batches.touch_all
83
+ #
84
+ # See Relation#touch_all for details of how each batch is touched.
85
+ def touch_all(...)
86
+ sum do |relation|
87
+ relation.touch_all(...)
88
+ end
89
+ end
90
+
91
+ # Destroys records in batches. Returns the total number of rows affected.
81
92
  #
82
93
  # Person.where("age < 10").in_batches.destroy_all
83
94
  #
84
95
  # See Relation#destroy_all for details of how each batch is destroyed.
85
96
  def destroy_all
86
- each(&:destroy_all)
97
+ sum do |relation|
98
+ relation.destroy_all.count(&:destroyed?)
99
+ end
87
100
  end
88
101
 
89
102
  # Yields an ActiveRecord::Relation object for each batch of records.
@@ -345,15 +345,15 @@ module ActiveRecord
345
345
  end
346
346
  end
347
347
 
348
- records = records.sort_by { |record| record.id }
348
+ records.sort_by!(&:id)
349
349
 
350
350
  if order == :desc
351
351
  records.reverse!
352
352
  end
353
353
 
354
- (0...records.size).step(batch_limit).each do |start|
354
+ records.each_slice(batch_limit) do |subrecords|
355
355
  subrelation = relation.spawn
356
- subrelation.load_records(records[start, batch_limit])
356
+ subrelation.load_records(subrecords)
357
357
 
358
358
  yield subrelation
359
359
  end
@@ -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(klass.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,10 @@ 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, people.id FROM comments INNER JOIN people on comments.person_id = people.id
280
+ # # => [[1, 2], [2, 2]]
281
+ #
278
282
  # Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
279
283
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
280
284
  # # => ['0', '27761', '173']
@@ -302,7 +306,7 @@ module ActiveRecord
302
306
  relation = apply_join_dependency
303
307
  relation.pluck(*column_names)
304
308
  else
305
- klass.disallow_raw_sql!(column_names.flatten)
309
+ klass.disallow_raw_sql!(flattened_args(column_names))
306
310
  columns = arel_columns(column_names)
307
311
  relation = spawn
308
312
  relation.select_values = columns
@@ -310,7 +314,9 @@ module ActiveRecord
310
314
  if where_clause.contradiction?
311
315
  ActiveRecord::Result.empty(async: @async)
312
316
  else
313
- klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
317
+ klass.with_connection do |c|
318
+ c.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
319
+ end
314
320
  end
315
321
  end
316
322
  result.then do |result|
@@ -357,7 +363,7 @@ module ActiveRecord
357
363
  # Returns the base model's ID's for the relation using the table's primary key
358
364
  #
359
365
  # 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
366
+ # Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
361
367
  def ids
362
368
  primary_key_array = Array(primary_key)
363
369
 
@@ -385,7 +391,9 @@ module ActiveRecord
385
391
  ActiveRecord::Result.empty
386
392
  else
387
393
  skip_query_cache_if_necessary do
388
- klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
394
+ klass.with_connection do |c|
395
+ c.select_all(relation, "#{klass.name} Ids", async: @async)
396
+ end
389
397
  end
390
398
  end
391
399
 
@@ -442,7 +450,7 @@ module ActiveRecord
442
450
  return column_name if Arel::Expressions === column_name
443
451
 
444
452
  arel_column(column_name.to_s) do |name|
445
- Arel.sql(column_name == :all ? "*" : name)
453
+ column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
446
454
  end
447
455
  end
448
456
 
@@ -451,7 +459,7 @@ module ActiveRecord
451
459
  end
452
460
 
453
461
  def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
454
- if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
462
+ if build_count_subquery?(operation, column_name, distinct)
455
463
  # Shortcut when limit is zero.
456
464
  return 0 if limit_value == 0
457
465
 
@@ -474,7 +482,9 @@ module ActiveRecord
474
482
  ActiveRecord::Result.empty
475
483
  else
476
484
  skip_query_cache_if_necessary do
477
- @klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
485
+ @klass.with_connection do |c|
486
+ c.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
487
+ end
478
488
  end
479
489
  end
480
490
 
@@ -500,68 +510,73 @@ module ActiveRecord
500
510
  end
501
511
  group_fields = arel_columns(group_fields)
502
512
 
503
- column_alias_tracker = ColumnAliasTracker.new(connection)
513
+ @klass.with_connection do |connection|
514
+ column_alias_tracker = ColumnAliasTracker.new(connection)
504
515
 
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)
516
+ group_aliases = group_fields.map { |field|
517
+ field = connection.visitor.compile(field) if Arel.arel_node?(field)
518
+ column_alias_tracker.alias_for(field.to_s.downcase)
519
+ }
520
+ group_columns = group_aliases.zip(group_fields)
510
521
 
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))
522
+ column = aggregate_column(column_name)
523
+ column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
524
+ select_value = operation_over_aggregate_column(column, operation, distinct)
525
+ select_value.as(adapter_class.quote_column_name(column_alias))
515
526
 
516
- select_values = [select_value]
517
- select_values += self.select_values unless having_clause.empty?
527
+ select_values = [select_value]
528
+ select_values += self.select_values unless having_clause.empty?
518
529
 
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
- }
527
-
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
530
+ select_values.concat group_columns.map { |aliaz, field|
531
+ aliaz = adapter_class.quote_column_name(aliaz)
532
+ if field.respond_to?(:as)
533
+ field.as(aliaz)
534
+ else
535
+ "#{field} AS #{aliaz}"
536
+ end
537
+ }
539
538
 
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
539
+ relation = except(:group).distinct!(false)
540
+ relation.group_values = group_fields
541
+ relation.select_values = select_values
542
+
543
+ result = skip_query_cache_if_necessary do
544
+ connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async)
545
545
  end
546
546
 
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]
547
+ result.then do |calculated_data|
548
+ if association
549
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
550
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
551
+ key_records = key_records.index_by(&:id)
550
552
  end
551
- end
552
553
 
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
554
+ key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
555
+ types[aliaz] = col_name.try(:type_caster) ||
556
+ type_for(col_name) do
557
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
558
+ end
559
+ end
560
+
561
+ hash_rows = calculated_data.cast_values(key_types).map! do |row|
562
+ calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
563
+ hash[col_name] = row[i]
564
+ end
565
+ end
566
+
567
+ if operation != "count"
568
+ type = column.try(:type_caster) ||
569
+ lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
570
+ type = type.subtype if Enum::EnumType === type
571
+ end
558
572
 
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
573
+ hash_rows.each_with_object({}) do |row, result|
574
+ key = group_aliases.map { |aliaz| row[aliaz] }
575
+ key = key.first if key.size == 1
576
+ key = key_records[key] if associated
563
577
 
564
- result[key] = type_cast_calculated_value(row[column_alias], operation, type)
578
+ result[key] = type_cast_calculated_value(row[column_alias], operation, type)
579
+ end
565
580
  end
566
581
  end
567
582
  end
@@ -617,12 +632,30 @@ module ActiveRecord
617
632
  def select_for_count
618
633
  if select_values.present?
619
634
  return select_values.first if select_values.one?
620
- select_values.join(", ")
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(", ")
621
647
  else
622
648
  :all
623
649
  end
624
650
  end
625
651
 
652
+ def build_count_subquery?(operation, column_name, distinct)
653
+ # SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or
654
+ # multiple columns, so we need to use subquery for this.
655
+ operation == "count" &&
656
+ (((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?)
657
+ end
658
+
626
659
  def build_count_subquery(relation, column_name, distinct)
627
660
  if column_name == :all
628
661
  column_alias = Arel.star
@@ -632,7 +665,7 @@ module ActiveRecord
632
665
  relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
633
666
  end
634
667
 
635
- subquery_alias = Arel.sql("subquery_for_count")
668
+ subquery_alias = Arel.sql("subquery_for_count", retryable: true)
636
669
  select_value = operation_over_aggregate_column(column_alias, "count", false)
637
670
 
638
671
  relation.build_subquery(subquery_alias, select_value)
@@ -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
@@ -67,23 +66,22 @@ module ActiveRecord
67
66
  end
68
67
 
69
68
  class GeneratedRelationMethods < Module # :nodoc:
70
- include Mutex_m
69
+ MUTEX = Mutex.new
71
70
 
72
71
  def generate_method(method)
73
- synchronize do
72
+ MUTEX.synchronize do
74
73
  return if method_defined?(method)
75
74
 
76
- if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !DELEGATION_RESERVED_METHOD_NAMES.include?(method.to_s)
75
+ if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
77
76
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
78
77
  def #{method}(...)
79
78
  scoping { klass.#{method}(...) }
80
79
  end
81
80
  RUBY
82
81
  else
83
- define_method(method) do |*args, &block|
84
- scoping { klass.public_send(method, *args, &block) }
82
+ define_method(method) do |*args, **kwargs, &block|
83
+ scoping { klass.public_send(method, *args, **kwargs, &block) }
85
84
  end
86
- ruby2_keywords(method)
87
85
  end
88
86
  end
89
87
  end
@@ -102,7 +100,7 @@ module ActiveRecord
102
100
  :to_sentence, :to_fs, :to_formatted_s, :as_json,
103
101
  :shuffle, :split, :slice, :index, :rindex, to: :records
104
102
 
105
- delegate :primary_key, :connection, :transaction, to: :klass
103
+ delegate :primary_key, :lease_connection, :connection, :with_connection, :transaction, to: :klass
106
104
 
107
105
  module ClassSpecificRelation # :nodoc:
108
106
  extend ActiveSupport::Concern
@@ -114,17 +112,16 @@ module ActiveRecord
114
112
  end
115
113
 
116
114
  private
117
- def method_missing(method, *args, &block)
115
+ def method_missing(method, ...)
118
116
  if @klass.respond_to?(method)
119
117
  unless Delegation.uncacheable_methods.include?(method)
120
118
  @klass.generate_relation_method(method)
121
119
  end
122
- scoping { @klass.public_send(method, *args, &block) }
120
+ scoping { @klass.public_send(method, ...) }
123
121
  else
124
122
  super
125
123
  end
126
124
  end
127
- ruby2_keywords(:method_missing)
128
125
  end
129
126
 
130
127
  module ClassMethods # :nodoc: