activerecord 7.1.3.4 → 7.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +507 -2133
  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 +18 -11
  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 +4 -2
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +3 -3
  17. data/lib/active_record/associations/has_one_association.rb +2 -2
  18. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  19. data/lib/active_record/associations/join_dependency.rb +5 -7
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +34 -11
  27. data/lib/active_record/attribute_assignment.rb +1 -11
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  30. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  31. data/lib/active_record/attribute_methods/read.rb +1 -13
  32. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  34. data/lib/active_record/attribute_methods.rb +87 -58
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +14 -30
  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 -58
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +160 -75
  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 +22 -9
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -61
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
  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 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  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 +6 -0
  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 +16 -12
  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 +109 -77
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  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 +59 -38
  77. data/lib/active_record/counter_cache.rb +23 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  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 +30 -6
  84. data/lib/active_record/destroy_association_async_job.rb +1 -1
  85. data/lib/active_record/dynamic_matchers.rb +2 -2
  86. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  87. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  88. data/lib/active_record/encryption/encryptor.rb +17 -2
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/encryption/scheme.rb +8 -4
  94. data/lib/active_record/enum.rb +11 -2
  95. data/lib/active_record/errors.rb +16 -11
  96. data/lib/active_record/explain.rb +13 -24
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +17 -4
  99. data/lib/active_record/gem_version.rb +3 -3
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +8 -7
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/marshalling.rb +1 -1
  107. data/lib/active_record/message_pack.rb +2 -2
  108. data/lib/active_record/migration/command_recorder.rb +2 -3
  109. data/lib/active_record/migration/compatibility.rb +11 -3
  110. data/lib/active_record/migration/default_strategy.rb +4 -5
  111. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  112. data/lib/active_record/migration.rb +85 -76
  113. data/lib/active_record/model_schema.rb +34 -69
  114. data/lib/active_record/nested_attributes.rb +11 -3
  115. data/lib/active_record/normalization.rb +3 -7
  116. data/lib/active_record/persistence.rb +32 -354
  117. data/lib/active_record/query_cache.rb +18 -6
  118. data/lib/active_record/query_logs.rb +15 -0
  119. data/lib/active_record/query_logs_formatter.rb +1 -1
  120. data/lib/active_record/querying.rb +21 -9
  121. data/lib/active_record/railtie.rb +52 -64
  122. data/lib/active_record/railties/controller_runtime.rb +13 -4
  123. data/lib/active_record/railties/databases.rake +41 -44
  124. data/lib/active_record/reflection.rb +98 -37
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  126. data/lib/active_record/relation/batches.rb +3 -3
  127. data/lib/active_record/relation/calculations.rb +94 -61
  128. data/lib/active_record/relation/delegation.rb +8 -11
  129. data/lib/active_record/relation/finder_methods.rb +16 -2
  130. data/lib/active_record/relation/merger.rb +4 -6
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  132. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  133. data/lib/active_record/relation/predicate_builder.rb +3 -3
  134. data/lib/active_record/relation/query_methods.rb +196 -43
  135. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  136. data/lib/active_record/relation/spawn_methods.rb +2 -18
  137. data/lib/active_record/relation/where_clause.rb +7 -19
  138. data/lib/active_record/relation.rb +500 -66
  139. data/lib/active_record/result.rb +32 -45
  140. data/lib/active_record/runtime_registry.rb +39 -0
  141. data/lib/active_record/sanitization.rb +24 -19
  142. data/lib/active_record/schema.rb +8 -6
  143. data/lib/active_record/schema_dumper.rb +19 -9
  144. data/lib/active_record/schema_migration.rb +30 -14
  145. data/lib/active_record/signed_id.rb +11 -1
  146. data/lib/active_record/statement_cache.rb +7 -7
  147. data/lib/active_record/table_metadata.rb +1 -10
  148. data/lib/active_record/tasks/database_tasks.rb +70 -42
  149. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  152. data/lib/active_record/test_fixtures.rb +82 -91
  153. data/lib/active_record/testing/query_assertions.rb +121 -0
  154. data/lib/active_record/timestamp.rb +4 -2
  155. data/lib/active_record/token_for.rb +22 -12
  156. data/lib/active_record/touch_later.rb +1 -1
  157. data/lib/active_record/transaction.rb +68 -0
  158. data/lib/active_record/transactions.rb +43 -14
  159. data/lib/active_record/translation.rb +0 -2
  160. data/lib/active_record/type/serialized.rb +1 -3
  161. data/lib/active_record/type_caster/connection.rb +4 -4
  162. data/lib/active_record/validations/associated.rb +9 -3
  163. data/lib/active_record/validations/uniqueness.rb +14 -10
  164. data/lib/active_record/validations.rb +4 -1
  165. data/lib/active_record.rb +149 -40
  166. data/lib/arel/alias_predication.rb +1 -1
  167. data/lib/arel/collectors/bind.rb +2 -0
  168. data/lib/arel/collectors/composite.rb +7 -0
  169. data/lib/arel/collectors/sql_string.rb +1 -1
  170. data/lib/arel/collectors/substitute_binds.rb +1 -1
  171. data/lib/arel/nodes/binary.rb +0 -6
  172. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  173. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  174. data/lib/arel/nodes/node.rb +4 -3
  175. data/lib/arel/nodes/sql_literal.rb +7 -0
  176. data/lib/arel/nodes.rb +2 -2
  177. data/lib/arel/predications.rb +1 -1
  178. data/lib/arel/select_manager.rb +1 -1
  179. data/lib/arel/tree_manager.rb +8 -3
  180. data/lib/arel/update_manager.rb +2 -1
  181. data/lib/arel/visitors/dot.rb +1 -0
  182. data/lib/arel/visitors/mysql.rb +9 -4
  183. data/lib/arel/visitors/postgresql.rb +1 -12
  184. data/lib/arel/visitors/to_sql.rb +31 -17
  185. data/lib/arel.rb +7 -3
  186. metadata +17 -12
@@ -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
@@ -786,7 +839,7 @@ module ActiveRecord
786
839
  primary_query_constraints = active_record.query_constraints_list
787
840
  owner_pk = active_record.primary_key
788
841
 
789
- if primary_query_constraints.size != 2
842
+ if primary_query_constraints.size > 2
790
843
  raise ArgumentError, <<~MSG.squish
791
844
  The query constraints list on the `#{active_record}` model has more than 2
792
845
  attributes. Active Record is unable to derive the query constraints
@@ -804,6 +857,8 @@ module ActiveRecord
804
857
  MSG
805
858
  end
806
859
 
860
+ return foreign_key if primary_query_constraints.include?(foreign_key)
861
+
807
862
  first_key, last_key = primary_query_constraints
808
863
 
809
864
  if first_key == owner_pk
@@ -869,7 +924,11 @@ module ActiveRecord
869
924
  # klass option is necessary to support loading polymorphic associations
870
925
  def association_primary_key(klass = nil)
871
926
  if primary_key = options[:primary_key]
872
- @association_primary_key ||= -primary_key.to_s
927
+ @association_primary_key ||= if primary_key.is_a?(Array)
928
+ primary_key.map { |pk| pk.to_s.freeze }.freeze
929
+ else
930
+ -primary_key.to_s
931
+ end
873
932
  elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
874
933
  (klass || self.klass).composite_query_constraints_list
875
934
  elsif (klass || self.klass).composite_primary_key?
@@ -927,7 +986,7 @@ module ActiveRecord
927
986
  end
928
987
 
929
988
  def klass
930
- @klass ||= delegate_reflection.compute_class(class_name)
989
+ @klass ||= delegate_reflection.compute_class(compute_name(class_name))
931
990
  end
932
991
 
933
992
  # Returns the source of the through reflection. It checks both a singularized
@@ -948,6 +1007,8 @@ module ActiveRecord
948
1007
  # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
949
1008
  #
950
1009
  def source_reflection
1010
+ return unless source_reflection_name
1011
+
951
1012
  through_reflection.klass._reflect_on_association(source_reflection_name)
952
1013
  end
953
1014
 
@@ -1105,7 +1166,7 @@ module ActiveRecord
1105
1166
  end
1106
1167
 
1107
1168
  if parent_reflection.nil?
1108
- reflections = active_record.reflections.keys.map(&:to_sym)
1169
+ reflections = active_record.normalized_reflections.keys
1109
1170
 
1110
1171
  if reflections.index(through_reflection.name) > reflections.index(name)
1111
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)