activerecord 7.2.4 → 8.1.4

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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1013 -878
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +69 -27
  6. data/lib/active_record/associations/association_scope.rb +1 -1
  7. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +36 -8
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +7 -3
  14. data/lib/active_record/associations/collection_proxy.rb +26 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/foreign_association.rb +3 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  20. data/lib/active_record/associations/has_one_association.rb +1 -0
  21. data/lib/active_record/associations/join_dependency.rb +3 -2
  22. data/lib/active_record/associations/preloader/association.rb +3 -3
  23. data/lib/active_record/associations/preloader/batch.rb +7 -1
  24. data/lib/active_record/associations/preloader/branch.rb +1 -0
  25. data/lib/active_record/associations/singular_association.rb +8 -3
  26. data/lib/active_record/associations.rb +192 -24
  27. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  28. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  29. data/lib/active_record/attribute_methods/query.rb +34 -0
  30. data/lib/active_record/attribute_methods/serialization.rb +18 -5
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  32. data/lib/active_record/attributes.rb +3 -0
  33. data/lib/active_record/autosave_association.rb +69 -27
  34. data/lib/active_record/base.rb +1 -2
  35. data/lib/active_record/callbacks.rb +1 -2
  36. data/lib/active_record/coders/column_serializer.rb +1 -1
  37. data/lib/active_record/coders/json.rb +16 -6
  38. data/lib/active_record/coders/yaml_column.rb +2 -2
  39. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  41. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
  53. data/lib/active_record/connection_adapters/column.rb +14 -4
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  56. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  57. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
  59. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  60. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  61. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  62. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  63. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
  64. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  65. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  67. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  69. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  70. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  71. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  72. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  73. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  75. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  76. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  77. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  78. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  79. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  80. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  81. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
  82. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
  83. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  84. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
  85. data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
  86. data/lib/active_record/connection_adapters.rb +1 -56
  87. data/lib/active_record/connection_handling.rb +25 -2
  88. data/lib/active_record/core.rb +33 -17
  89. data/lib/active_record/counter_cache.rb +35 -8
  90. data/lib/active_record/database_configurations/database_config.rb +9 -1
  91. data/lib/active_record/database_configurations/hash_config.rb +74 -9
  92. data/lib/active_record/database_configurations/url_config.rb +15 -3
  93. data/lib/active_record/database_configurations.rb +7 -3
  94. data/lib/active_record/delegated_type.rb +1 -1
  95. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  96. data/lib/active_record/dynamic_matchers.rb +54 -69
  97. data/lib/active_record/encryption/config.rb +3 -1
  98. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  99. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  100. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  101. data/lib/active_record/encryption/encryptor.rb +28 -8
  102. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  103. data/lib/active_record/encryption/scheme.rb +9 -2
  104. data/lib/active_record/enum.rb +33 -30
  105. data/lib/active_record/errors.rb +33 -9
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/explain_registry.rb +51 -2
  108. data/lib/active_record/filter_attribute_handler.rb +76 -0
  109. data/lib/active_record/fixtures.rb +2 -4
  110. data/lib/active_record/future_result.rb +15 -9
  111. data/lib/active_record/gem_version.rb +2 -2
  112. data/lib/active_record/inheritance.rb +9 -4
  113. data/lib/active_record/insert_all.rb +14 -9
  114. data/lib/active_record/locking/optimistic.rb +8 -1
  115. data/lib/active_record/locking/pessimistic.rb +5 -0
  116. data/lib/active_record/log_subscriber.rb +31 -29
  117. data/lib/active_record/message_pack.rb +13 -0
  118. data/lib/active_record/middleware/shard_selector.rb +34 -17
  119. data/lib/active_record/migration/command_recorder.rb +56 -13
  120. data/lib/active_record/migration/compatibility.rb +37 -24
  121. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  122. data/lib/active_record/migration.rb +48 -42
  123. data/lib/active_record/model_schema.rb +40 -15
  124. data/lib/active_record/nested_attributes.rb +14 -12
  125. data/lib/active_record/persistence.rb +163 -133
  126. data/lib/active_record/query_cache.rb +22 -15
  127. data/lib/active_record/query_logs.rb +100 -52
  128. data/lib/active_record/query_logs_formatter.rb +17 -28
  129. data/lib/active_record/querying.rb +21 -9
  130. data/lib/active_record/railtie.rb +35 -30
  131. data/lib/active_record/railties/controller_runtime.rb +11 -6
  132. data/lib/active_record/railties/databases.rake +26 -38
  133. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  134. data/lib/active_record/railties/job_runtime.rb +10 -11
  135. data/lib/active_record/reflection.rb +54 -22
  136. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  137. data/lib/active_record/relation/batches.rb +147 -73
  138. data/lib/active_record/relation/calculations.rb +60 -43
  139. data/lib/active_record/relation/delegation.rb +25 -15
  140. data/lib/active_record/relation/finder_methods.rb +73 -43
  141. data/lib/active_record/relation/merger.rb +12 -10
  142. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  143. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  144. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  145. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  146. data/lib/active_record/relation/predicate_builder.rb +23 -8
  147. data/lib/active_record/relation/query_attribute.rb +3 -1
  148. data/lib/active_record/relation/query_methods.rb +200 -96
  149. data/lib/active_record/relation/spawn_methods.rb +7 -7
  150. data/lib/active_record/relation/where_clause.rb +2 -9
  151. data/lib/active_record/relation.rb +127 -87
  152. data/lib/active_record/result.rb +109 -24
  153. data/lib/active_record/runtime_registry.rb +42 -58
  154. data/lib/active_record/sanitization.rb +9 -6
  155. data/lib/active_record/schema.rb +1 -1
  156. data/lib/active_record/schema_dumper.rb +17 -10
  157. data/lib/active_record/schema_migration.rb +2 -1
  158. data/lib/active_record/scoping/named.rb +5 -2
  159. data/lib/active_record/scoping.rb +0 -1
  160. data/lib/active_record/signed_id.rb +44 -16
  161. data/lib/active_record/statement_cache.rb +24 -20
  162. data/lib/active_record/store.rb +53 -22
  163. data/lib/active_record/structured_event_subscriber.rb +85 -0
  164. data/lib/active_record/table_metadata.rb +6 -23
  165. data/lib/active_record/tasks/abstract_tasks.rb +83 -0
  166. data/lib/active_record/tasks/database_tasks.rb +85 -85
  167. data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
  168. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  169. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  170. data/lib/active_record/test_databases.rb +18 -4
  171. data/lib/active_record/test_fixtures.rb +52 -6
  172. data/lib/active_record/testing/query_assertions.rb +8 -2
  173. data/lib/active_record/timestamp.rb +4 -2
  174. data/lib/active_record/token_for.rb +1 -1
  175. data/lib/active_record/transaction.rb +2 -5
  176. data/lib/active_record/transactions.rb +56 -32
  177. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  178. data/lib/active_record/type/internal/timezone.rb +7 -0
  179. data/lib/active_record/type/json.rb +13 -2
  180. data/lib/active_record/type/serialized.rb +16 -4
  181. data/lib/active_record/type/type_map.rb +1 -1
  182. data/lib/active_record/type_caster/connection.rb +2 -1
  183. data/lib/active_record/validations/associated.rb +1 -1
  184. data/lib/active_record/validations/uniqueness.rb +8 -8
  185. data/lib/active_record.rb +85 -50
  186. data/lib/arel/alias_predication.rb +2 -0
  187. data/lib/arel/collectors/bind.rb +2 -2
  188. data/lib/arel/collectors/sql_string.rb +1 -1
  189. data/lib/arel/collectors/substitute_binds.rb +2 -2
  190. data/lib/arel/crud.rb +6 -11
  191. data/lib/arel/nodes/binary.rb +1 -1
  192. data/lib/arel/nodes/count.rb +2 -2
  193. data/lib/arel/nodes/function.rb +4 -10
  194. data/lib/arel/nodes/named_function.rb +2 -2
  195. data/lib/arel/nodes/node.rb +2 -2
  196. data/lib/arel/nodes/sql_literal.rb +1 -1
  197. data/lib/arel/nodes.rb +0 -2
  198. data/lib/arel/predications.rb +1 -3
  199. data/lib/arel/select_manager.rb +7 -2
  200. data/lib/arel/table.rb +3 -7
  201. data/lib/arel/visitors/dot.rb +0 -3
  202. data/lib/arel/visitors/postgresql.rb +55 -0
  203. data/lib/arel/visitors/sqlite.rb +55 -8
  204. data/lib/arel/visitors/to_sql.rb +4 -22
  205. data/lib/arel.rb +3 -1
  206. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  207. metadata +15 -12
  208. data/lib/active_record/explain_subscriber.rb +0 -34
  209. data/lib/active_record/normalization.rb +0 -163
  210. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/module/delegation"
4
3
 
5
4
  module ActiveRecord
6
5
  module Delegation # :nodoc:
@@ -22,6 +21,9 @@ module ActiveRecord
22
21
  end
23
22
 
24
23
  module DelegateCache # :nodoc:
24
+ @delegate_base_methods = true
25
+ singleton_class.attr_accessor :delegate_base_methods
26
+
25
27
  def relation_delegate_class(klass)
26
28
  @relation_delegate_cache[klass]
27
29
  end
@@ -75,12 +77,12 @@ module ActiveRecord
75
77
  if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
76
78
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
77
79
  def #{method}(...)
78
- scoping { klass.#{method}(...) }
80
+ scoping { model.#{method}(...) }
79
81
  end
80
82
  RUBY
81
83
  else
82
84
  define_method(method) do |*args, **kwargs, &block|
83
- scoping { klass.public_send(method, *args, **kwargs, &block) }
85
+ scoping { model.public_send(method, *args, **kwargs, &block) }
84
86
  end
85
87
  end
86
88
  end
@@ -92,15 +94,15 @@ module ActiveRecord
92
94
 
93
95
  # This module creates compiled delegation methods dynamically at runtime, which makes
94
96
  # subsequent calls to that method faster by avoiding method_missing. The delegations
95
- # may vary depending on the klass of a relation, so we create a subclass of Relation
96
- # for each different klass, and the delegations are compiled into that subclass only.
97
+ # may vary depending on the model of a relation, so we create a subclass of Relation
98
+ # for each different model, and the delegations are compiled into that subclass only.
97
99
 
98
100
  delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
99
101
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
100
102
  :to_sentence, :to_fs, :to_formatted_s, :as_json,
101
103
  :shuffle, :split, :slice, :index, :rindex, to: :records
102
104
 
103
- delegate :primary_key, :lease_connection, :connection, :with_connection, :transaction, to: :klass
105
+ delegate :primary_key, :with_connection, :connection, :table_name, :transaction, :sanitize_sql_like, :unscoped, :name, to: :model
104
106
 
105
107
  module ClassSpecificRelation # :nodoc:
106
108
  extend ActiveSupport::Concern
@@ -113,11 +115,19 @@ module ActiveRecord
113
115
 
114
116
  private
115
117
  def method_missing(method, ...)
116
- if @klass.respond_to?(method)
117
- unless Delegation.uncacheable_methods.include?(method)
118
- @klass.generate_relation_method(method)
118
+ if model.respond_to?(method)
119
+ if !DelegateCache.delegate_base_methods && Base.respond_to?(method)
120
+ # A common mistake in Active Record's own code is to call `ActiveRecord::Base`
121
+ # class methods on Association. It works because it's automatically delegated, but
122
+ # can introduce subtle bugs because it sets the global scope.
123
+ # We can't deprecate this behavior because gems might depend on it, however we
124
+ # can ban it from Active Record's own test suite to avoid regressions.
125
+ raise NotImplementedError, "Active Record code shouldn't rely on association delegation into ActiveRecord::Base methods"
126
+ elsif !Delegation.uncacheable_methods.include?(method)
127
+ model.generate_relation_method(method)
119
128
  end
120
- scoping { @klass.public_send(method, ...) }
129
+
130
+ scoping { model.public_send(method, ...) }
121
131
  else
122
132
  super
123
133
  end
@@ -125,19 +135,19 @@ module ActiveRecord
125
135
  end
126
136
 
127
137
  module ClassMethods # :nodoc:
128
- def create(klass, *args, **kwargs)
129
- relation_class_for(klass).new(klass, *args, **kwargs)
138
+ def create(model, ...)
139
+ relation_class_for(model).new(model, ...)
130
140
  end
131
141
 
132
142
  private
133
- def relation_class_for(klass)
134
- klass.relation_delegate_class(self)
143
+ def relation_class_for(model)
144
+ model.relation_delegate_class(self)
135
145
  end
136
146
  end
137
147
 
138
148
  private
139
149
  def respond_to_missing?(method, _)
140
- super || @klass.respond_to?(method)
150
+ super || model.respond_to?(method)
141
151
  end
142
152
  end
143
153
  end
@@ -141,14 +141,14 @@ module ActiveRecord
141
141
  #
142
142
  # Product.where(["price = %?", price]).sole
143
143
  def sole
144
- found, undesired = first(2)
144
+ found, undesired = take(2)
145
145
 
146
146
  if found.nil?
147
147
  raise_record_not_found_exception!
148
- elsif undesired.present?
149
- raise ActiveRecord::SoleRecordExceeded.new(self)
150
- else
148
+ elsif undesired.nil?
151
149
  found
150
+ else
151
+ raise ActiveRecord::SoleRecordExceeded.new(self)
152
152
  end
153
153
  end
154
154
 
@@ -376,7 +376,7 @@ module ActiveRecord
376
376
 
377
377
  skip_query_cache_if_necessary do
378
378
  with_connection do |c|
379
- c.select_rows(relation.arel, "#{name} Exists?").size == 1
379
+ c.select_rows(relation.arel, "#{model.name} Exists?").size == 1
380
380
  end
381
381
  end
382
382
  end
@@ -389,7 +389,7 @@ module ActiveRecord
389
389
  def include?(record)
390
390
  # The existing implementation relies on receiving an Active Record instance as the input parameter named record.
391
391
  # Any non-Active Record object passed to this implementation is guaranteed to return `false`.
392
- return false unless record.is_a?(klass)
392
+ return false unless record.is_a?(model)
393
393
 
394
394
  if loaded? || offset_value || limit_value || having_clause.any?
395
395
  records.include?(record)
@@ -415,9 +415,9 @@ module ActiveRecord
415
415
  # the expected number of results should be provided in the +expected_size+
416
416
  # argument.
417
417
  def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
418
- conditions = " [#{arel.where_sql(klass)}]" unless where_clause.empty?
418
+ conditions = " [#{arel.where_sql(model)}]" unless where_clause.empty?
419
419
 
420
- name = @klass.name
420
+ name = model.name
421
421
 
422
422
  if ids.nil?
423
423
  error = +"Couldn't find #{name}"
@@ -442,7 +442,7 @@ module ActiveRecord
442
442
  if distinct_value && offset_value
443
443
  relation = except(:order).limit!(1)
444
444
  else
445
- relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
445
+ relation = except(:select, :distinct, :order)._select!(Arel.sql(ONE_AS_ONE, retryable: true)).limit!(1)
446
446
  end
447
447
 
448
448
  case conditions
@@ -462,17 +462,17 @@ module ActiveRecord
462
462
  relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
463
463
 
464
464
  if eager_loading && has_limit_or_offset? && !(
465
- using_limitable_reflections?(join_dependency.reflections) &&
466
- using_limitable_reflections?(
467
- construct_join_dependency(
468
- select_association_list(joins_values).concat(
469
- select_association_list(left_outer_joins_values)
470
- ), nil
471
- ).reflections
472
- )
465
+ using_limitable_reflections?(join_dependency.reflections) &&
466
+ using_limitable_reflections?(
467
+ construct_join_dependency(
468
+ select_association_list(joins_values).concat(
469
+ select_association_list(left_outer_joins_values)
470
+ ), nil
471
+ ).reflections
473
472
  )
473
+ )
474
474
  relation = skip_query_cache_if_necessary do
475
- klass.with_connection do |c|
475
+ model.with_connection do |c|
476
476
  c.distinct_relation_for_primary_key(relation)
477
477
  end
478
478
  end
@@ -490,21 +490,22 @@ module ActiveRecord
490
490
  end
491
491
 
492
492
  def find_with_ids(*ids)
493
- raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
493
+ raise UnknownPrimaryKey.new(model) if primary_key.nil?
494
+
495
+ first_item = ids.first
496
+ return [] if first_item.is_a?(Array) && first_item.empty?
494
497
 
495
- expects_array = if klass.composite_primary_key?
496
- ids.first.first.is_a?(Array)
498
+ expects_array = if model.composite_primary_key?
499
+ first_item.first.is_a?(Array)
497
500
  else
498
- ids.first.is_a?(Array)
501
+ first_item.is_a?(Array)
499
502
  end
500
503
 
501
- return [] if expects_array && ids.first.empty?
502
-
503
- ids = ids.first if expects_array
504
+ ids = first_item if expects_array
504
505
 
505
506
  ids = ids.compact.uniq
506
507
 
507
- model_name = @klass.name
508
+ model_name = model.name
508
509
 
509
510
  case ids.size
510
511
  when 0
@@ -526,7 +527,7 @@ module ActiveRecord
526
527
  MSG
527
528
  end
528
529
 
529
- relation = if klass.composite_primary_key?
530
+ relation = if model.composite_primary_key?
530
531
  where(primary_key.zip(id).to_h)
531
532
  else
532
533
  where(primary_key => id)
@@ -542,10 +543,6 @@ module ActiveRecord
542
543
  def find_some(ids)
543
544
  return find_some_ordered(ids) unless order_values.present?
544
545
 
545
- relation = where(primary_key => ids)
546
- relation = relation.select(table[primary_key]) unless select_values.empty?
547
- result = relation.to_a
548
-
549
546
  expected_size =
550
547
  if limit_value && ids.size > limit_value
551
548
  limit_value
@@ -554,10 +551,18 @@ module ActiveRecord
554
551
  end
555
552
 
556
553
  # 11 ids with limit 3, offset 9 should give 2 results.
557
- if offset_value && (ids.size - offset_value < expected_size)
558
- expected_size = ids.size - offset_value
554
+ if offset_value
555
+ if ids.size <= offset_value
556
+ return []
557
+ elsif ids.size - offset_value < expected_size
558
+ expected_size = ids.size - offset_value
559
+ end
559
560
  end
560
561
 
562
+ relation = where(primary_key => ids)
563
+ relation = relation.select(table[primary_key]) unless select_values.empty?
564
+ result = relation.to_a
565
+
561
566
  if result.size == expected_size
562
567
  result
563
568
  else
@@ -567,6 +572,7 @@ module ActiveRecord
567
572
 
568
573
  def find_some_ordered(ids)
569
574
  ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
575
+ return [] if ids.empty?
570
576
 
571
577
  relation = except(:limit, :offset)
572
578
  relation = relation.where(primary_key => ids)
@@ -574,12 +580,20 @@ module ActiveRecord
574
580
  result = relation.records
575
581
 
576
582
  if result.size == ids.size
577
- result.in_order_of(:id, ids.map { |id| @klass.type_for_attribute(primary_key).cast(id) })
583
+ result.in_order_of(:id, ids.map { |id| cast_primary_key(id) })
578
584
  else
579
585
  raise_record_not_found_exception!(ids, result.size, ids.size)
580
586
  end
581
587
  end
582
588
 
589
+ def cast_primary_key(id)
590
+ if model.composite_primary_key?
591
+ primary_key.zip(id).map! { |attr, value| model.type_for_attribute(attr).cast(value) }
592
+ else
593
+ model.type_for_attribute(primary_key).cast(id)
594
+ end
595
+ end
596
+
583
597
  def find_take
584
598
  if loaded?
585
599
  records.first
@@ -639,24 +653,40 @@ module ActiveRecord
639
653
  end
640
654
 
641
655
  def ordered_relation
642
- if order_values.empty? && (implicit_order_column || !query_constraints_list.nil? || primary_key)
643
- order(_order_columns.map { |column| table[column].asc })
656
+ if order_values.empty?
657
+ if !_order_columns.empty?
658
+ return order(_order_columns.map { |column| table[column].asc })
659
+ end
660
+
661
+ if ActiveRecord.raise_on_missing_required_finder_order_columns
662
+ raise MissingRequiredOrderError, <<~MSG.squish
663
+ Relation has no order values, and #{model} has no order columns to use as a default.
664
+ Set at least one of `implicit_order_column`, `query_constraints` or `primary_key` on
665
+ the model when no `order `is specified on the relation.
666
+ MSG
667
+ else
668
+ ActiveRecord.deprecator.warn(<<~MSG)
669
+ Calling order dependent finder methods (e.g. `#first`, `#second`) without `order` values on the relation,
670
+ and on a model (#{model}) that does not have any order columns (`implicit_order_column`, `query_constraints`,
671
+ or `primary_key`) to fall back on is deprecated and will raise `ActiveRecord::MissingRequiredOrderError`
672
+ in Rails 8.2.
673
+ MSG
674
+
675
+ self
676
+ end
644
677
  else
645
678
  self
646
679
  end
647
680
  end
648
681
 
649
682
  def _order_columns
650
- oc = []
683
+ columns = Array(model.implicit_order_column)
651
684
 
652
- oc << implicit_order_column if implicit_order_column
653
- oc << query_constraints_list if query_constraints_list
685
+ return columns.compact if columns.length.positive? && columns.last.nil?
654
686
 
655
- if primary_key && query_constraints_list.nil?
656
- oc << primary_key
657
- end
687
+ columns += Array(model.query_constraints_list || model.primary_key)
658
688
 
659
- oc.flatten.uniq.compact
689
+ columns.uniq.compact
660
690
  end
661
691
  end
662
692
  end
@@ -24,7 +24,7 @@ module ActiveRecord
24
24
  # the values.
25
25
  def other
26
26
  other = Relation.create(
27
- relation.klass,
27
+ relation.model,
28
28
  table: relation.table,
29
29
  predicate_builder: relation.predicate_builder
30
30
  )
@@ -84,7 +84,7 @@ module ActiveRecord
84
84
  def merge_select_values
85
85
  return if other.select_values.empty?
86
86
 
87
- if other.klass == relation.klass
87
+ if other.model == relation.model
88
88
  relation.select_values |= other.select_values
89
89
  else
90
90
  relation.select_values |= other.instance_eval do
@@ -96,12 +96,12 @@ module ActiveRecord
96
96
  def merge_preloads
97
97
  return if other.preload_values.empty? && other.includes_values.empty?
98
98
 
99
- if other.klass == relation.klass
99
+ if other.model == relation.model
100
100
  relation.preload_values |= other.preload_values unless other.preload_values.empty?
101
101
  relation.includes_values |= other.includes_values unless other.includes_values.empty?
102
102
  else
103
- reflection = relation.klass.reflect_on_all_associations.find do |r|
104
- r.class_name == other.klass.name
103
+ reflection = relation.model.reflect_on_all_associations.find do |r|
104
+ r.class_name == other.model.name
105
105
  end || return
106
106
 
107
107
  unless other.preload_values.empty?
@@ -117,7 +117,7 @@ module ActiveRecord
117
117
  def merge_joins
118
118
  return if other.joins_values.empty?
119
119
 
120
- if other.klass == relation.klass
120
+ if other.model == relation.model
121
121
  relation.joins_values |= other.joins_values
122
122
  else
123
123
  associations, others = other.joins_values.partition do |join|
@@ -136,7 +136,7 @@ module ActiveRecord
136
136
  def merge_outer_joins
137
137
  return if other.left_outer_joins_values.empty?
138
138
 
139
- if other.klass == relation.klass
139
+ if other.model == relation.model
140
140
  relation.left_outer_joins_values |= other.left_outer_joins_values
141
141
  else
142
142
  associations, others = other.left_outer_joins_values.partition do |join|
@@ -161,8 +161,10 @@ module ActiveRecord
161
161
  relation.order!(*other.order_values)
162
162
  end
163
163
 
164
- extensions = other.extensions - relation.extensions
165
- relation.extending!(*extensions) if extensions.any?
164
+ unless other.extensions.empty?
165
+ extensions = other.extensions - relation.extensions
166
+ relation.extending!(*extensions) if extensions.any?
167
+ end
166
168
  end
167
169
 
168
170
  def merge_single_values
@@ -185,7 +187,7 @@ module ActiveRecord
185
187
 
186
188
  def replace_from_clause?
187
189
  relation.from_clause.empty? && !other.from_clause.empty? &&
188
- relation.klass.base_class == other.klass.base_class
190
+ relation.model.base_class == other.model.base_class
189
191
  end
190
192
  end
191
193
  end
@@ -31,7 +31,9 @@ module ActiveRecord
31
31
  values_predicate
32
32
  else
33
33
  array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
34
- array_predicates.inject(values_predicate, &:or)
34
+ values_predicate.or(
35
+ Arel::Nodes::Grouping.new Arel::Nodes::Or.new(array_predicates)
36
+ )
35
37
  end
36
38
  end
37
39
 
@@ -3,24 +3,24 @@
3
3
  module ActiveRecord
4
4
  class PredicateBuilder
5
5
  class AssociationQueryValue # :nodoc:
6
- def initialize(associated_table, value)
7
- @associated_table = associated_table
6
+ def initialize(reflection, value)
7
+ @reflection = reflection
8
8
  @value = value
9
9
  end
10
10
 
11
11
  def queries
12
- if associated_table.join_foreign_key.is_a?(Array)
12
+ if reflection.join_foreign_key.is_a?(Array)
13
13
  id_list = ids
14
14
  id_list = id_list.pluck(primary_key) if id_list.is_a?(Relation)
15
15
 
16
- id_list.map { |ids_set| associated_table.join_foreign_key.zip(ids_set).to_h }
16
+ id_list.map { |ids_set| reflection.join_foreign_key.zip(ids_set).to_h }
17
17
  else
18
- [ associated_table.join_foreign_key => ids ]
18
+ [ reflection.join_foreign_key => ids ]
19
19
  end
20
20
  end
21
21
 
22
22
  private
23
- attr_reader :associated_table, :value
23
+ attr_reader :reflection, :value
24
24
 
25
25
  def ids
26
26
  case value
@@ -37,15 +37,15 @@ module ActiveRecord
37
37
  end
38
38
 
39
39
  def primary_key
40
- associated_table.join_primary_key
40
+ reflection.join_primary_key
41
41
  end
42
42
 
43
43
  def primary_type
44
- associated_table.join_primary_type
44
+ reflection.join_primary_type
45
45
  end
46
46
 
47
47
  def polymorphic_name
48
- associated_table.polymorphic_name_association
48
+ reflection.polymorphic_name
49
49
  end
50
50
 
51
51
  def select_clause?
@@ -3,24 +3,24 @@
3
3
  module ActiveRecord
4
4
  class PredicateBuilder
5
5
  class PolymorphicArrayValue # :nodoc:
6
- def initialize(associated_table, values)
7
- @associated_table = associated_table
6
+ def initialize(reflection, values)
7
+ @reflection = reflection
8
8
  @values = values
9
9
  end
10
10
 
11
11
  def queries
12
- return [ associated_table.join_foreign_key => values ] if values.empty?
12
+ return [ reflection.join_foreign_key => values ] if values.empty?
13
13
 
14
14
  type_to_ids_mapping.map do |type, ids|
15
15
  query = {}
16
- query[associated_table.join_foreign_type] = type if type
17
- query[associated_table.join_foreign_key] = ids
16
+ query[reflection.join_foreign_type] = type if type
17
+ query[reflection.join_foreign_key] = ids
18
18
  query
19
19
  end
20
20
  end
21
21
 
22
22
  private
23
- attr_reader :associated_table, :values
23
+ attr_reader :reflection, :values
24
24
 
25
25
  def type_to_ids_mapping
26
26
  default_hash = Hash.new { |hsh, key| hsh[key] = [] }
@@ -30,14 +30,14 @@ module ActiveRecord
30
30
  end
31
31
 
32
32
  def primary_key(value)
33
- associated_table.join_primary_key(klass(value))
33
+ reflection.join_primary_key(klass(value))
34
34
  end
35
35
 
36
36
  def klass(value)
37
37
  if value.is_a?(Base)
38
38
  value.class
39
39
  elsif value.is_a?(Relation)
40
- value.klass
40
+ value.model
41
41
  end
42
42
  end
43
43
 
@@ -9,10 +9,11 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  if value.select_values.empty?
12
- if value.klass.composite_primary_key?
13
- raise ArgumentError, "Cannot map composite primary key #{value.klass.primary_key} to #{attribute.name}"
12
+ model = value.model
13
+ if model.composite_primary_key?
14
+ raise ArgumentError, "Cannot map composite primary key #{model.primary_key} to #{attribute.name}"
14
15
  else
15
- value = value.select(value.table[value.klass.primary_key])
16
+ value = value.select(value.table[model.primary_key])
16
17
  end
17
18
  end
18
19
 
@@ -37,7 +37,7 @@ module ActiveRecord
37
37
 
38
38
  # Define how a class is converted to Arel nodes when passed to +where+.
39
39
  # The handler can be any object that responds to +call+, and will be used
40
- # for any value that +===+ the class given. For example:
40
+ # for any value that <tt>===</tt> the class given. For example:
41
41
  #
42
42
  # MyCustomDateRange = Struct.new(:start, :end)
43
43
  # handler = proc do |column, range|
@@ -72,38 +72,53 @@ module ActiveRecord
72
72
  table.associated_table(table_name, &block).arel_table[column_name]
73
73
  end
74
74
 
75
+ def with(table)
76
+ other = dup
77
+ other.table = table
78
+ other
79
+ end
80
+
75
81
  protected
82
+ attr_writer :table
83
+
76
84
  def expand_from_hash(attributes, &block)
77
- return ["1=0"] if attributes.empty?
85
+ return [Arel.sql("1=0", retryable: true)] if attributes.empty?
78
86
 
79
87
  attributes.flat_map do |key, value|
88
+ if key.is_a?(Array) && key.size == 1
89
+ key = key.first
90
+ value = value.flatten
91
+ end
92
+
80
93
  if key.is_a?(Array)
81
94
  queries = Array(value).map do |ids_set|
82
95
  raise ArgumentError, "Expected corresponding value for #{key} to be an Array" unless ids_set.is_a?(Array)
83
- expand_from_hash(key.zip(ids_set).to_h)
96
+ expand_from_hash(convert_dot_notation_to_hash(key.zip(ids_set).to_h))
84
97
  end
85
98
  grouping_queries(queries)
86
99
  elsif value.is_a?(Hash) && !table.has_column?(key)
87
100
  table.associated_table(key, &block)
88
101
  .predicate_builder.expand_from_hash(value.stringify_keys)
89
- elsif table.associated_with?(key)
102
+ elsif (associated_reflection = table.associated_with(key))
90
103
  # Find the foreign key when using queries such as:
91
104
  # Post.where(author: author)
92
105
  #
93
106
  # For polymorphic relationships, find the foreign key and type:
94
107
  # PriceEstimate.where(estimate_of: treasure)
95
- associated_table = table.associated_table(key)
96
- if associated_table.polymorphic_association?
108
+
109
+ if associated_reflection.polymorphic?
97
110
  value = [value] unless value.is_a?(Array)
98
111
  klass = PolymorphicArrayValue
99
- elsif associated_table.through_association?
112
+ elsif associated_reflection.through_reflection?
113
+ associated_table = table.associated_table(key)
114
+
100
115
  next associated_table.predicate_builder.expand_from_hash(
101
116
  associated_table.primary_key => value
102
117
  )
103
118
  end
104
119
 
105
120
  klass ||= AssociationQueryValue
106
- queries = klass.new(associated_table, value).queries.map! do |query|
121
+ queries = klass.new(associated_reflection, value).queries.map! do |query|
107
122
  # If the query produced is identical to attributes don't go any deeper.
108
123
  # Prevents stack level too deep errors when association and foreign_key are identical.
109
124
  query == attributes ? self[key, value] : expand_from_hash(query)
@@ -15,7 +15,9 @@ module ActiveRecord
15
15
  elsif @type.serialized?
16
16
  value_for_database
17
17
  elsif @type.mutable? # If the type is simply mutable, we deep_dup it.
18
- @value_before_type_cast = @value_before_type_cast.deep_dup
18
+ unless @value_before_type_cast.frozen?
19
+ @value_before_type_cast = @value_before_type_cast.deep_dup
20
+ end
19
21
  end
20
22
  end
21
23