activerecord 7.1.6 → 7.2.3

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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +839 -2248
  3. data/README.rdoc +16 -16
  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 +31 -23
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/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 +16 -8
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  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 +59 -292
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +1 -13
  31. data/lib/active_record/attribute_methods/serialization.rb +5 -25
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  33. data/lib/active_record/attribute_methods.rb +51 -60
  34. data/lib/active_record/attributes.rb +93 -68
  35. data/lib/active_record/autosave_association.rb +25 -32
  36. data/lib/active_record/base.rb +4 -5
  37. data/lib/active_record/callbacks.rb +1 -1
  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 +294 -72
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -75
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -2
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -6
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +46 -44
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +53 -15
  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 +6 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +19 -18
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -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/interval.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  58. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +30 -8
  60. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +36 -26
  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 +57 -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 +26 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +133 -78
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  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 +68 -49
  76. data/lib/active_record/core.rb +112 -44
  77. data/lib/active_record/counter_cache.rb +19 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  79. data/lib/active_record/database_configurations/database_config.rb +19 -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 +42 -18
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +25 -5
  87. data/lib/active_record/encryption/encryptor.rb +35 -19
  88. data/lib/active_record/encryption/key_provider.rb +1 -1
  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/enum.rb +31 -13
  94. data/lib/active_record/errors.rb +49 -23
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixture_set/table_row.rb +19 -2
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +8 -4
  99. data/lib/active_record/gem_version.rb +2 -2
  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 +7 -6
  105. data/lib/active_record/log_subscriber.rb +0 -21
  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 +87 -77
  112. data/lib/active_record/model_schema.rb +31 -68
  113. data/lib/active_record/nested_attributes.rb +11 -3
  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 +19 -8
  117. data/lib/active_record/query_logs.rb +19 -0
  118. data/lib/active_record/querying.rb +25 -13
  119. data/lib/active_record/railtie.rb +39 -57
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +42 -44
  122. data/lib/active_record/reflection.rb +98 -36
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +14 -8
  125. data/lib/active_record/relation/calculations.rb +127 -89
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +26 -12
  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/association_query_value.rb +10 -2
  131. data/lib/active_record/relation/predicate_builder.rb +3 -3
  132. data/lib/active_record/relation/query_attribute.rb +1 -1
  133. data/lib/active_record/relation/query_methods.rb +238 -65
  134. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  135. data/lib/active_record/relation/spawn_methods.rb +2 -18
  136. data/lib/active_record/relation/where_clause.rb +15 -21
  137. data/lib/active_record/relation.rb +508 -74
  138. data/lib/active_record/result.rb +31 -44
  139. data/lib/active_record/runtime_registry.rb +39 -0
  140. data/lib/active_record/sanitization.rb +24 -19
  141. data/lib/active_record/schema.rb +8 -6
  142. data/lib/active_record/schema_dumper.rb +48 -20
  143. data/lib/active_record/schema_migration.rb +30 -14
  144. data/lib/active_record/scoping/named.rb +1 -0
  145. data/lib/active_record/secure_token.rb +3 -3
  146. data/lib/active_record/signed_id.rb +27 -7
  147. data/lib/active_record/statement_cache.rb +7 -7
  148. data/lib/active_record/table_metadata.rb +1 -10
  149. data/lib/active_record/tasks/database_tasks.rb +69 -41
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +8 -1
  152. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  153. data/lib/active_record/test_fixtures.rb +86 -89
  154. data/lib/active_record/testing/query_assertions.rb +121 -0
  155. data/lib/active_record/timestamp.rb +2 -2
  156. data/lib/active_record/token_for.rb +22 -12
  157. data/lib/active_record/touch_later.rb +1 -1
  158. data/lib/active_record/transaction.rb +132 -0
  159. data/lib/active_record/transactions.rb +73 -15
  160. data/lib/active_record/translation.rb +0 -2
  161. data/lib/active_record/type/serialized.rb +1 -3
  162. data/lib/active_record/type_caster/connection.rb +4 -4
  163. data/lib/active_record/validations/associated.rb +9 -3
  164. data/lib/active_record/validations/uniqueness.rb +15 -10
  165. data/lib/active_record/validations.rb +4 -1
  166. data/lib/active_record.rb +148 -39
  167. data/lib/arel/alias_predication.rb +1 -1
  168. data/lib/arel/collectors/bind.rb +3 -1
  169. data/lib/arel/collectors/composite.rb +7 -0
  170. data/lib/arel/collectors/sql_string.rb +1 -1
  171. data/lib/arel/collectors/substitute_binds.rb +1 -1
  172. data/lib/arel/crud.rb +2 -0
  173. data/lib/arel/delete_manager.rb +5 -0
  174. data/lib/arel/nodes/binary.rb +0 -6
  175. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  176. data/lib/arel/nodes/delete_statement.rb +4 -2
  177. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  178. data/lib/arel/nodes/node.rb +4 -3
  179. data/lib/arel/nodes/sql_literal.rb +7 -0
  180. data/lib/arel/nodes/update_statement.rb +4 -2
  181. data/lib/arel/nodes.rb +2 -2
  182. data/lib/arel/predications.rb +1 -1
  183. data/lib/arel/select_manager.rb +7 -3
  184. data/lib/arel/tree_manager.rb +3 -2
  185. data/lib/arel/update_manager.rb +7 -1
  186. data/lib/arel/visitors/dot.rb +3 -0
  187. data/lib/arel/visitors/mysql.rb +9 -4
  188. data/lib/arel/visitors/postgresql.rb +1 -12
  189. data/lib/arel/visitors/sqlite.rb +25 -0
  190. data/lib/arel/visitors/to_sql.rb +31 -16
  191. data/lib/arel.rb +7 -3
  192. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  193. metadata +16 -10
@@ -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,7 +428,15 @@ 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 ||= _klass(class_name)
432
+ end
433
+
434
+ def _klass(class_name) # :nodoc:
435
+ if active_record.name.demodulize == class_name
436
+ return compute_class("::#{class_name}") rescue NameError
437
+ end
438
+
439
+ compute_class(class_name)
413
440
  end
414
441
 
415
442
  def compute_class(name)
@@ -435,15 +462,24 @@ module ActiveRecord
435
462
  name.to_s.camelize
436
463
  end
437
464
 
438
- def validate_reflection!
439
- return unless options[:foreign_key].is_a?(Array)
465
+ def normalize_options(options)
466
+ counter_cache = options.delete(:counter_cache)
440
467
 
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
468
+ if counter_cache
469
+ active = true
470
+
471
+ case counter_cache
472
+ when String, Symbol
473
+ column = -counter_cache.to_s
474
+ when Hash
475
+ active = counter_cache.fetch(:active, true)
476
+ column = counter_cache[:column]&.to_s
477
+ end
478
+
479
+ options[:counter_cache] = { active: active, column: column }
480
+ end
481
+
482
+ options
447
483
  end
448
484
  end
449
485
 
@@ -494,6 +530,17 @@ module ActiveRecord
494
530
  @foreign_key = nil
495
531
  @association_foreign_key = nil
496
532
  @association_primary_key = nil
533
+ if options[:query_constraints]
534
+ ActiveRecord.deprecator.warn <<~MSG.squish
535
+ Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is deprecated.
536
+ To maintain current behavior, use the `foreign_key` option instead.
537
+ MSG
538
+ end
539
+
540
+ # If the foreign key is an array, set query constraints options and don't use the foreign key
541
+ if options[:foreign_key].is_a?(Array)
542
+ options[:query_constraints] = options.delete(:foreign_key)
543
+ end
497
544
 
498
545
  ensure_option_not_given_as_class!(:class_name)
499
546
  end
@@ -503,7 +550,9 @@ module ActiveRecord
503
550
  if polymorphic?
504
551
  key = [key, owner._read_attribute(@foreign_type)]
505
552
  end
506
- klass.cached_find_by_statement(key, &block)
553
+ klass.with_connection do |connection|
554
+ klass.cached_find_by_statement(connection, key, &block)
555
+ end
507
556
  end
508
557
 
509
558
  def join_table
@@ -511,10 +560,14 @@ module ActiveRecord
511
560
  end
512
561
 
513
562
  def foreign_key(infer_from_inverse_of: true)
514
- @foreign_key ||= if options[:query_constraints]
563
+ @foreign_key ||= if options[:foreign_key]
564
+ if options[:foreign_key].is_a?(Array)
565
+ options[:foreign_key].map { |fk| fk.to_s.freeze }.freeze
566
+ else
567
+ options[:foreign_key].to_s.freeze
568
+ end
569
+ elsif options[:query_constraints]
515
570
  options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
516
- elsif options[:foreign_key]
517
- options[:foreign_key].to_s
518
571
  else
519
572
  derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
520
573
 
@@ -711,6 +764,10 @@ module ActiveRecord
711
764
 
712
765
  begin
713
766
  reflection = klass._reflect_on_association(inverse_name)
767
+ if !reflection && active_record.automatically_invert_plural_associations
768
+ plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
769
+ reflection = klass._reflect_on_association(plural_inverse_name)
770
+ end
714
771
  rescue NameError => error
715
772
  raise unless error.name.to_s == class_name
716
773
 
@@ -720,7 +777,7 @@ module ActiveRecord
720
777
  end
721
778
 
722
779
  if valid_inverse_reflection?(reflection)
723
- inverse_name
780
+ reflection.name
724
781
  end
725
782
  end
726
783
  end
@@ -933,7 +990,7 @@ module ActiveRecord
933
990
  end
934
991
 
935
992
  def klass
936
- @klass ||= delegate_reflection.compute_class(class_name)
993
+ @klass ||= delegate_reflection._klass(class_name)
937
994
  end
938
995
 
939
996
  # Returns the source of the through reflection. It checks both a singularized
@@ -954,6 +1011,8 @@ module ActiveRecord
954
1011
  # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
955
1012
  #
956
1013
  def source_reflection
1014
+ return unless source_reflection_name
1015
+
957
1016
  through_reflection.klass._reflect_on_association(source_reflection_name)
958
1017
  end
959
1018
 
@@ -1111,7 +1170,7 @@ module ActiveRecord
1111
1170
  end
1112
1171
 
1113
1172
  if parent_reflection.nil?
1114
- reflections = active_record.reflections.keys.map(&:to_sym)
1173
+ reflections = active_record.normalized_reflections.keys
1115
1174
 
1116
1175
  if reflections.index(through_reflection.name) > reflections.index(name)
1117
1176
  raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
@@ -1180,7 +1239,10 @@ module ActiveRecord
1180
1239
  end
1181
1240
 
1182
1241
  def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
1183
- scopes = @previous_reflection.join_scopes(table, predicate_builder, klass, record) + super
1242
+ scopes = super
1243
+ unless @previous_reflection.through_reflection?
1244
+ scopes += @previous_reflection.join_scopes(table, predicate_builder, klass, record)
1245
+ end
1184
1246
  scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
1185
1247
  end
1186
1248
 
@@ -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.
@@ -241,14 +241,14 @@ module ActiveRecord
241
241
  raise ArgumentError, ":order must be :asc or :desc or an array consisting of :asc or :desc, got #{order.inspect}"
242
242
  end
243
243
 
244
- unless block
245
- return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, order: order, use_ranges: use_ranges)
246
- end
247
-
248
244
  if arel.orders.present?
249
245
  act_on_ignored_order(error_on_ignore)
250
246
  end
251
247
 
248
+ unless block
249
+ return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, order: order, use_ranges: use_ranges)
250
+ end
251
+
252
252
  batch_limit = of
253
253
 
254
254
  if limit_value
@@ -341,19 +341,25 @@ module ActiveRecord
341
341
 
342
342
  if start || finish
343
343
  records = records.filter do |record|
344
- (start.nil? || record.id >= start) && (finish.nil? || record.id <= finish)
344
+ id = record.id
345
+
346
+ if order == :asc
347
+ (start.nil? || id >= start) && (finish.nil? || id <= finish)
348
+ else
349
+ (start.nil? || id <= start) && (finish.nil? || id >= finish)
350
+ end
345
351
  end
346
352
  end
347
353
 
348
- records = records.sort_by { |record| record.id }
354
+ records.sort_by!(&:id)
349
355
 
350
356
  if order == :desc
351
357
  records.reverse!
352
358
  end
353
359
 
354
- (0...records.size).step(batch_limit).each do |start|
360
+ records.each_slice(batch_limit) do |subrecords|
355
361
  subrelation = relation.spawn
356
- subrelation.load_records(records[start, batch_limit])
362
+ subrelation.load_records(subrecords)
357
363
 
358
364
  yield subrelation
359
365
  end