activerecord 7.2.3 → 8.1.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 (198) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +612 -1055
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +35 -11
  6. data/lib/active_record/associations/builder/association.rb +23 -11
  7. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  8. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  9. data/lib/active_record/associations/builder/has_one.rb +1 -1
  10. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  11. data/lib/active_record/associations/collection_association.rb +1 -1
  12. data/lib/active_record/associations/collection_proxy.rb +22 -4
  13. data/lib/active_record/associations/deprecation.rb +88 -0
  14. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  15. data/lib/active_record/associations/errors.rb +3 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  17. data/lib/active_record/associations/join_dependency.rb +4 -2
  18. data/lib/active_record/associations/preloader/association.rb +2 -2
  19. data/lib/active_record/associations/preloader/batch.rb +7 -1
  20. data/lib/active_record/associations/preloader/branch.rb +1 -0
  21. data/lib/active_record/associations/singular_association.rb +8 -3
  22. data/lib/active_record/associations.rb +192 -24
  23. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  24. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  25. data/lib/active_record/attribute_methods/query.rb +34 -0
  26. data/lib/active_record/attribute_methods/serialization.rb +16 -3
  27. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  28. data/lib/active_record/attributes.rb +3 -0
  29. data/lib/active_record/autosave_association.rb +69 -27
  30. data/lib/active_record/base.rb +1 -2
  31. data/lib/active_record/coders/json.rb +14 -5
  32. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  33. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  34. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  35. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +412 -88
  36. data/lib/active_record/connection_adapters/abstract/database_statements.rb +137 -75
  37. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  38. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  39. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  40. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  41. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  42. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -32
  43. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  44. data/lib/active_record/connection_adapters/abstract_adapter.rb +150 -91
  45. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +63 -52
  46. data/lib/active_record/connection_adapters/column.rb +17 -4
  47. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  48. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  49. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  50. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  55. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  56. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  57. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  58. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  59. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  60. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  61. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  62. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  63. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  64. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +139 -63
  66. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  67. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  68. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  69. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  70. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  71. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  72. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  73. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -14
  74. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +102 -37
  75. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  76. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  77. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  78. data/lib/active_record/connection_adapters.rb +1 -56
  79. data/lib/active_record/connection_handling.rb +25 -2
  80. data/lib/active_record/core.rb +33 -17
  81. data/lib/active_record/counter_cache.rb +33 -8
  82. data/lib/active_record/database_configurations/database_config.rb +9 -1
  83. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  84. data/lib/active_record/database_configurations/url_config.rb +13 -3
  85. data/lib/active_record/database_configurations.rb +7 -3
  86. data/lib/active_record/delegated_type.rb +1 -1
  87. data/lib/active_record/dynamic_matchers.rb +54 -69
  88. data/lib/active_record/encryption/config.rb +3 -1
  89. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  90. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  91. data/lib/active_record/encryption/encryptor.rb +28 -8
  92. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  93. data/lib/active_record/encryption/scheme.rb +9 -2
  94. data/lib/active_record/enum.rb +33 -30
  95. data/lib/active_record/errors.rb +33 -9
  96. data/lib/active_record/explain.rb +1 -1
  97. data/lib/active_record/explain_registry.rb +51 -2
  98. data/lib/active_record/filter_attribute_handler.rb +73 -0
  99. data/lib/active_record/fixtures.rb +2 -4
  100. data/lib/active_record/future_result.rb +15 -9
  101. data/lib/active_record/gem_version.rb +2 -2
  102. data/lib/active_record/inheritance.rb +1 -1
  103. data/lib/active_record/insert_all.rb +14 -9
  104. data/lib/active_record/locking/optimistic.rb +8 -1
  105. data/lib/active_record/locking/pessimistic.rb +5 -0
  106. data/lib/active_record/log_subscriber.rb +3 -13
  107. data/lib/active_record/middleware/shard_selector.rb +34 -17
  108. data/lib/active_record/migration/command_recorder.rb +45 -12
  109. data/lib/active_record/migration/compatibility.rb +37 -24
  110. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  111. data/lib/active_record/migration.rb +48 -42
  112. data/lib/active_record/model_schema.rb +38 -13
  113. data/lib/active_record/nested_attributes.rb +6 -6
  114. data/lib/active_record/persistence.rb +162 -133
  115. data/lib/active_record/query_cache.rb +22 -15
  116. data/lib/active_record/query_logs.rb +100 -52
  117. data/lib/active_record/query_logs_formatter.rb +17 -28
  118. data/lib/active_record/querying.rb +8 -8
  119. data/lib/active_record/railtie.rb +35 -30
  120. data/lib/active_record/railties/controller_runtime.rb +11 -6
  121. data/lib/active_record/railties/databases.rake +26 -38
  122. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  123. data/lib/active_record/railties/job_runtime.rb +10 -11
  124. data/lib/active_record/reflection.rb +53 -21
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  126. data/lib/active_record/relation/batches.rb +147 -73
  127. data/lib/active_record/relation/calculations.rb +52 -40
  128. data/lib/active_record/relation/delegation.rb +25 -15
  129. data/lib/active_record/relation/finder_methods.rb +40 -24
  130. data/lib/active_record/relation/merger.rb +8 -8
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  132. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  133. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  134. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  135. data/lib/active_record/relation/predicate_builder.rb +22 -7
  136. data/lib/active_record/relation/query_attribute.rb +3 -1
  137. data/lib/active_record/relation/query_methods.rb +140 -86
  138. data/lib/active_record/relation/spawn_methods.rb +7 -7
  139. data/lib/active_record/relation/where_clause.rb +2 -9
  140. data/lib/active_record/relation.rb +107 -75
  141. data/lib/active_record/result.rb +109 -24
  142. data/lib/active_record/runtime_registry.rb +42 -58
  143. data/lib/active_record/sanitization.rb +9 -6
  144. data/lib/active_record/schema_dumper.rb +18 -11
  145. data/lib/active_record/schema_migration.rb +2 -1
  146. data/lib/active_record/scoping/named.rb +5 -2
  147. data/lib/active_record/scoping.rb +0 -1
  148. data/lib/active_record/signed_id.rb +43 -15
  149. data/lib/active_record/statement_cache.rb +24 -20
  150. data/lib/active_record/store.rb +51 -22
  151. data/lib/active_record/structured_event_subscriber.rb +85 -0
  152. data/lib/active_record/table_metadata.rb +6 -23
  153. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  154. data/lib/active_record/tasks/database_tasks.rb +85 -85
  155. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  156. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  157. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  158. data/lib/active_record/test_databases.rb +14 -4
  159. data/lib/active_record/test_fixtures.rb +39 -2
  160. data/lib/active_record/testing/query_assertions.rb +8 -2
  161. data/lib/active_record/timestamp.rb +4 -2
  162. data/lib/active_record/token_for.rb +1 -1
  163. data/lib/active_record/transaction.rb +2 -5
  164. data/lib/active_record/transactions.rb +37 -16
  165. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  166. data/lib/active_record/type/internal/timezone.rb +7 -0
  167. data/lib/active_record/type/json.rb +13 -2
  168. data/lib/active_record/type/serialized.rb +16 -4
  169. data/lib/active_record/type/type_map.rb +1 -1
  170. data/lib/active_record/type_caster/connection.rb +2 -1
  171. data/lib/active_record/validations/associated.rb +1 -1
  172. data/lib/active_record/validations/uniqueness.rb +8 -8
  173. data/lib/active_record.rb +84 -49
  174. data/lib/arel/alias_predication.rb +2 -0
  175. data/lib/arel/collectors/bind.rb +2 -2
  176. data/lib/arel/collectors/sql_string.rb +1 -1
  177. data/lib/arel/collectors/substitute_binds.rb +2 -2
  178. data/lib/arel/crud.rb +6 -11
  179. data/lib/arel/nodes/binary.rb +1 -1
  180. data/lib/arel/nodes/count.rb +2 -2
  181. data/lib/arel/nodes/function.rb +4 -10
  182. data/lib/arel/nodes/named_function.rb +2 -2
  183. data/lib/arel/nodes/node.rb +2 -2
  184. data/lib/arel/nodes/sql_literal.rb +1 -1
  185. data/lib/arel/nodes.rb +0 -2
  186. data/lib/arel/predications.rb +1 -3
  187. data/lib/arel/select_manager.rb +7 -2
  188. data/lib/arel/table.rb +3 -7
  189. data/lib/arel/visitors/dot.rb +0 -3
  190. data/lib/arel/visitors/postgresql.rb +55 -0
  191. data/lib/arel/visitors/sqlite.rb +55 -8
  192. data/lib/arel/visitors/to_sql.rb +3 -21
  193. data/lib/arel.rb +3 -1
  194. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  195. metadata +16 -13
  196. data/lib/active_record/explain_subscriber.rb +0 -34
  197. data/lib/active_record/normalization.rb +0 -163
  198. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -92,10 +92,11 @@ module ActiveRecord
92
92
  @scope.joins!(association)
93
93
  end
94
94
 
95
+ association_conditions = Array(reflection.association_primary_key).index_with(nil)
95
96
  if reflection.options[:class_name]
96
- self.not(association => { reflection.association_primary_key => nil })
97
+ self.not(association => association_conditions)
97
98
  else
98
- self.not(reflection.table_name => { reflection.association_primary_key => nil })
99
+ self.not(reflection.table_name => association_conditions)
99
100
  end
100
101
  end
101
102
 
@@ -124,10 +125,11 @@ module ActiveRecord
124
125
  associations.each do |association|
125
126
  reflection = scope_association_reflection(association)
126
127
  @scope.left_outer_joins!(association)
128
+ association_conditions = Array(reflection.association_primary_key).index_with(nil)
127
129
  if reflection.options[:class_name]
128
- @scope.where!(association => { reflection.association_primary_key => nil })
130
+ @scope.where!(association => association_conditions)
129
131
  else
130
- @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
132
+ @scope.where!(reflection.table_name => association_conditions)
131
133
  end
132
134
  end
133
135
 
@@ -136,9 +138,10 @@ module ActiveRecord
136
138
 
137
139
  private
138
140
  def scope_association_reflection(association)
139
- reflection = @scope.klass._reflect_on_association(association)
141
+ model = @scope.model
142
+ reflection = model._reflect_on_association(association)
140
143
  unless reflection
141
- raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
144
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{model.name}`.")
142
145
  end
143
146
  reflection
144
147
  end
@@ -254,6 +257,10 @@ module ActiveRecord
254
257
  self
255
258
  end
256
259
 
260
+ def all # :nodoc:
261
+ spawn
262
+ end
263
+
257
264
  # Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
258
265
  # Performs a single query joining all specified associations. For example:
259
266
  #
@@ -500,7 +507,7 @@ module ActiveRecord
500
507
  #
501
508
  # Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
502
509
  # # => ActiveRecord::Relation
503
- # # WITH post_and_replies AS (
510
+ # # WITH RECURSIVE post_and_replies AS (
504
511
  # # (SELECT * FROM posts WHERE id = 42)
505
512
  # # UNION ALL
506
513
  # # (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
@@ -569,7 +576,7 @@ module ActiveRecord
569
576
  end
570
577
 
571
578
  def group!(*args) # :nodoc:
572
- self.group_values += args
579
+ self.group_values |= args
573
580
  self
574
581
  end
575
582
 
@@ -698,26 +705,39 @@ module ActiveRecord
698
705
  # # WHEN "conversations"."status" = 0 THEN 3
699
706
  # # END ASC
700
707
  #
701
- def in_order_of(column, values)
702
- klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
708
+ # +filter+ can be set to +false+ to include all results instead of only the ones specified in +values+.
709
+ #
710
+ # Conversation.in_order_of(:status, [:archived, :active], filter: false)
711
+ # # SELECT "conversations".* FROM "conversations"
712
+ # # ORDER BY CASE
713
+ # # WHEN "conversations"."status" = 1 THEN 1
714
+ # # WHEN "conversations"."status" = 0 THEN 2
715
+ # # ELSE 3
716
+ # # END ASC
717
+ def in_order_of(column, values, filter: true)
718
+ model.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
703
719
  return spawn.none! if values.empty?
704
720
 
705
721
  references = column_references([column])
706
722
  self.references_values |= references unless references.empty?
707
723
 
708
- values = values.map { |value| type_caster.type_cast_for_database(column, value) }
724
+ values = values.map { |value| model.type_caster.type_cast_for_database(column, value) }
709
725
  arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
710
726
 
711
- where_clause =
712
- if values.include?(nil)
713
- arel_column.in(values.compact).or(arel_column.eq(nil))
714
- else
715
- arel_column.in(values)
716
- end
727
+ scope = spawn.order!(build_case_for_value_position(arel_column, values, filter: filter))
717
728
 
718
- spawn
719
- .order!(build_case_for_value_position(arel_column, values))
720
- .where!(where_clause)
729
+ if filter
730
+ where_clause =
731
+ if values.include?(nil)
732
+ arel_column.in(values.compact).or(arel_column.eq(nil))
733
+ else
734
+ arel_column.in(values)
735
+ end
736
+
737
+ scope = scope.where!(where_clause)
738
+ end
739
+
740
+ scope
721
741
  end
722
742
 
723
743
  # Replaces any existing order defined on the relation with the specified order.
@@ -789,7 +809,7 @@ module ActiveRecord
789
809
  end
790
810
 
791
811
  def unscope!(*args) # :nodoc:
792
- self.unscope_values += args
812
+ self.unscope_values |= args
793
813
 
794
814
  args.each do |scope|
795
815
  case scope
@@ -1193,6 +1213,7 @@ module ActiveRecord
1193
1213
  end
1194
1214
 
1195
1215
  def limit!(value) # :nodoc:
1216
+ value = Integer(value) unless value.nil?
1196
1217
  self.limit_value = value
1197
1218
  self
1198
1219
  end
@@ -1445,7 +1466,7 @@ module ActiveRecord
1445
1466
  modules << Module.new(&block) if block
1446
1467
  modules.flatten!
1447
1468
 
1448
- self.extending_values += modules
1469
+ self.extending_values |= modules
1449
1470
  extend(*extending_values) if extending_values.any?
1450
1471
 
1451
1472
  self
@@ -1513,7 +1534,7 @@ module ActiveRecord
1513
1534
 
1514
1535
  # Like #annotate, but modifies relation in place.
1515
1536
  def annotate!(*args) # :nodoc:
1516
- self.annotate_values += args
1537
+ self.annotate_values |= args
1517
1538
  self
1518
1539
  end
1519
1540
 
@@ -1556,8 +1577,8 @@ module ActiveRecord
1556
1577
  records.flatten!(1)
1557
1578
  records.compact!
1558
1579
 
1559
- unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
1560
- raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1580
+ unless records.all?(model) && relations.all? { |relation| relation.model == model }
1581
+ raise ArgumentError, "You must only pass a single or collection of #{model.name} objects to ##{__callee__}."
1561
1582
  end
1562
1583
 
1563
1584
  spawn.excluding!(records + relations.flat_map(&:ids))
@@ -1572,12 +1593,12 @@ module ActiveRecord
1572
1593
 
1573
1594
  # Returns the Arel object associated with the relation.
1574
1595
  def arel(aliases = nil) # :nodoc:
1575
- @arel ||= with_connection { |c| build_arel(c, aliases) }
1596
+ @arel ||= build_arel(aliases)
1576
1597
  end
1577
1598
 
1578
1599
  def construct_join_dependency(associations, join_type) # :nodoc:
1579
1600
  ActiveRecord::Associations::JoinDependency.new(
1580
- klass, table, associations, join_type
1601
+ model, table, associations, join_type
1581
1602
  )
1582
1603
  end
1583
1604
 
@@ -1606,15 +1627,15 @@ module ActiveRecord
1606
1627
  elsif opts.include?("?")
1607
1628
  parts = [build_bound_sql_literal(opts, rest)]
1608
1629
  else
1609
- parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1630
+ parts = [Arel.sql(model.sanitize_sql([opts, *rest]))]
1610
1631
  end
1611
1632
  when Hash
1612
1633
  opts = opts.transform_keys do |key|
1613
1634
  if key.is_a?(Array)
1614
- key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
1635
+ key.map { |k| model.attribute_aliases[k.to_s] || k.to_s }
1615
1636
  else
1616
1637
  key = key.to_s
1617
- klass.attribute_aliases[key] || key
1638
+ model.attribute_aliases[key] || key
1618
1639
  end
1619
1640
  end
1620
1641
  references = PredicateBuilder.references(opts)
@@ -1633,21 +1654,16 @@ module ActiveRecord
1633
1654
  end
1634
1655
  alias :build_having_clause :build_where_clause
1635
1656
 
1636
- def async!
1657
+ def async! # :nodoc:
1637
1658
  @async = true
1638
1659
  self
1639
1660
  end
1640
1661
 
1641
- protected
1642
- def arel_columns(columns)
1662
+ def arel_columns(columns) # :nodoc:
1643
1663
  columns.flat_map do |field|
1644
1664
  case field
1645
- when Symbol
1646
- arel_column(field.to_s) do |attr_name|
1647
- adapter_class.quote_table_name(attr_name)
1648
- end
1649
- when String
1650
- arel_column(field, &:itself)
1665
+ when Symbol, String
1666
+ arel_column(field)
1651
1667
  when Proc
1652
1668
  field.call
1653
1669
  when Hash
@@ -1731,32 +1747,27 @@ module ActiveRecord
1731
1747
  raise UnmodifiableRelation if @loaded || @arel
1732
1748
  end
1733
1749
 
1734
- def build_arel(connection, aliases = nil)
1750
+ def build_arel(aliases)
1735
1751
  arel = Arel::SelectManager.new(table)
1736
1752
 
1737
1753
  build_joins(arel.join_sources, aliases)
1738
1754
 
1739
1755
  arel.where(where_clause.ast) unless where_clause.empty?
1740
1756
  arel.having(having_clause.ast) unless having_clause.empty?
1741
- arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
1757
+ arel.take(build_cast_value("LIMIT", limit_value)) if limit_value
1742
1758
  arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
1743
- arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1759
+ arel.group(*arel_columns(group_values)) unless group_values.empty?
1744
1760
 
1745
1761
  build_order(arel)
1746
1762
  build_with(arel)
1747
1763
  build_select(arel)
1748
1764
 
1749
1765
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1766
+ arel.comment(*annotate_values) unless annotate_values.empty?
1750
1767
  arel.distinct(distinct_value)
1751
1768
  arel.from(build_from) unless from_clause.empty?
1752
1769
  arel.lock(lock_value) if lock_value
1753
1770
 
1754
- unless annotate_values.empty?
1755
- annotates = annotate_values
1756
- annotates = annotates.uniq if annotates.size > 1
1757
- arel.comment(*annotates)
1758
- end
1759
-
1760
1771
  arel
1761
1772
  end
1762
1773
 
@@ -1830,7 +1841,7 @@ module ActiveRecord
1830
1841
 
1831
1842
  joins = joins_values.dup
1832
1843
  if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1833
- stashed_eager_load = joins.pop if joins.last.base_klass == klass
1844
+ stashed_eager_load = joins.pop if joins.last.base_klass == model
1834
1845
  end
1835
1846
 
1836
1847
  joins.each_with_index do |join, i|
@@ -1887,8 +1898,8 @@ module ActiveRecord
1887
1898
  def build_select(arel)
1888
1899
  if select_values.any?
1889
1900
  arel.project(*arel_columns(select_values))
1890
- elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1891
- arel.project(*klass.column_names.map { |field| table[field] })
1901
+ elsif model.ignored_columns.any? || model.enumerate_columns_in_select_statements
1902
+ arel.project(*model.column_names.map { |field| table[field] })
1892
1903
  else
1893
1904
  arel.project(table[Arel.star])
1894
1905
  end
@@ -1912,7 +1923,8 @@ module ActiveRecord
1912
1923
 
1913
1924
  def build_with_expression_from_value(value, nested = false)
1914
1925
  case value
1915
- when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1926
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::BoundSqlLiteral
1927
+ Arel::Nodes::Grouping.new(value)
1916
1928
  when ActiveRecord::Relation
1917
1929
  if nested
1918
1930
  value.arel.ast
@@ -1939,37 +1951,76 @@ module ActiveRecord
1939
1951
  with_table = Arel::Table.new(name)
1940
1952
 
1941
1953
  table.join(with_table, kind).on(
1942
- with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
1954
+ with_table[model.model_name.to_s.foreign_key].eq(table[model.primary_key])
1943
1955
  ).join_sources.first
1944
1956
  end
1945
1957
 
1958
+ def arel_columns_from_hash(fields)
1959
+ fields.flat_map do |table_name, columns|
1960
+ table_name = table_name.name if table_name.is_a?(Symbol)
1961
+ case columns
1962
+ when Symbol, String
1963
+ arel_column_with_table(table_name, columns)
1964
+ when Array
1965
+ columns.map do |column|
1966
+ arel_column_with_table(table_name, column)
1967
+ end
1968
+ else
1969
+ raise TypeError, "Expected Symbol, String or Array, got: #{columns.class}"
1970
+ end
1971
+ end
1972
+ end
1973
+
1974
+ def arel_column_with_table(table_name, column_name)
1975
+ self.references_values |= [Arel.sql(table_name, retryable: true)]
1976
+
1977
+ if column_name.is_a?(Symbol) || !column_name.match?(/\W/)
1978
+ predicate_builder.resolve_arel_attribute(table_name, column_name) do
1979
+ lookup_table_klass_from_join_dependencies(table_name)
1980
+ end
1981
+ else
1982
+ Arel.sql("#{model.adapter_class.quote_table_name(table_name)}.#{column_name}")
1983
+ end
1984
+ end
1985
+
1946
1986
  def arel_column(field)
1947
- field = klass.attribute_aliases[field] || field
1987
+ field = field.name if is_symbol = field.is_a?(Symbol)
1988
+
1989
+ field = model.attribute_aliases[field] || field
1948
1990
  from = from_clause.name || from_clause.value
1949
1991
 
1950
- if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1992
+ if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
1951
1993
  table[field]
1952
1994
  elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
1953
- self.references_values |= [Arel.sql(table, retryable: true)]
1954
- predicate_builder.resolve_arel_attribute(table, column) do
1955
- lookup_table_klass_from_join_dependencies(table)
1956
- end
1957
- else
1995
+ arel_column_with_table(table, column)
1996
+ elsif block_given?
1958
1997
  yield field
1998
+ elsif Arel.arel_node?(field)
1999
+ field
2000
+ elsif is_symbol
2001
+ Arel.sql(model.adapter_class.quote_table_name(field), retryable: true)
2002
+ else
2003
+ Arel.sql(field)
1959
2004
  end
1960
2005
  end
1961
2006
 
1962
2007
  def table_name_matches?(from)
1963
2008
  table_name = Regexp.escape(table.name)
1964
- quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
2009
+ quoted_table_name = Regexp.escape(model.adapter_class.quote_table_name(table.name))
1965
2010
  /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1966
2011
  end
1967
2012
 
1968
2013
  def reverse_sql_order(order_query)
1969
2014
  if order_query.empty?
1970
- return [table[primary_key].desc] if primary_key
1971
- raise IrreversibleOrderError,
1972
- "Relation has no current order and table has no primary key to be used as default order"
2015
+ if !_reverse_order_columns.empty?
2016
+ return _reverse_order_columns.map { |column| table[column].desc }
2017
+ end
2018
+
2019
+ raise IrreversibleOrderError, <<~MSG.squish
2020
+ Relation has no order values, and #{model} has no order columns to use as a default.
2021
+ Set at least one of `implicit_order_column`, or `primary_key` on the model when no
2022
+ `order `is specified on the relation.
2023
+ MSG
1973
2024
  end
1974
2025
 
1975
2026
  order_query.flat_map do |o|
@@ -1994,6 +2045,13 @@ module ActiveRecord
1994
2045
  end
1995
2046
  end
1996
2047
 
2048
+ def _reverse_order_columns
2049
+ roc = []
2050
+ roc << model.implicit_order_column if model.implicit_order_column
2051
+ roc << model.primary_key if model.primary_key
2052
+ roc.flatten.uniq.compact
2053
+ end
2054
+
1997
2055
  def does_not_support_reverse?(order)
1998
2056
  # Account for String subclasses like Arel::Nodes::SqlLiteral that
1999
2057
  # override methods like #count.
@@ -2032,7 +2090,7 @@ module ActiveRecord
2032
2090
  end
2033
2091
 
2034
2092
  def preprocess_order_args(order_args)
2035
- @klass.disallow_raw_sql!(
2093
+ model.disallow_raw_sql!(
2036
2094
  flattened_args(order_args),
2037
2095
  permit: model.adapter_class.column_name_with_order_matcher
2038
2096
  )
@@ -2070,7 +2128,7 @@ module ActiveRecord
2070
2128
 
2071
2129
  def sanitize_order_arguments(order_args)
2072
2130
  order_args.map! do |arg|
2073
- klass.sanitize_sql_for_order(arg)
2131
+ model.sanitize_sql_for_order(arg)
2074
2132
  end
2075
2133
  end
2076
2134
 
@@ -2108,17 +2166,18 @@ module ActiveRecord
2108
2166
  if attr_name == "count" && !group_values.empty?
2109
2167
  table[attr_name]
2110
2168
  else
2111
- Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
2169
+ Arel.sql(model.adapter_class.quote_table_name(attr_name), retryable: true)
2112
2170
  end
2113
2171
  end
2114
2172
  end
2115
2173
 
2116
- def build_case_for_value_position(column, values)
2174
+ def build_case_for_value_position(column, values, filter: true)
2117
2175
  node = Arel::Nodes::Case.new
2118
2176
  values.each.with_index(1) do |value, order|
2119
2177
  node.when(column.eq(value)).then(order)
2120
2178
  end
2121
2179
 
2180
+ node = node.else(values.length + 1) unless filter
2122
2181
  Arel::Nodes::Ascending.new(node)
2123
2182
  end
2124
2183
 
@@ -2176,34 +2235,29 @@ module ActiveRecord
2176
2235
  def process_select_args(fields)
2177
2236
  fields.flat_map do |field|
2178
2237
  if field.is_a?(Hash)
2179
- arel_columns_from_hash(field)
2238
+ arel_column_aliases_from_hash(field)
2180
2239
  else
2181
2240
  field
2182
2241
  end
2183
2242
  end
2184
2243
  end
2185
2244
 
2186
- def arel_columns_from_hash(fields)
2245
+ def arel_column_aliases_from_hash(fields)
2187
2246
  fields.flat_map do |key, columns_aliases|
2247
+ table_name = key.is_a?(Symbol) ? key.name : key
2188
2248
  case columns_aliases
2189
2249
  when Hash
2190
2250
  columns_aliases.map do |column, column_alias|
2191
- if values[:joins]&.include?(key)
2192
- references = PredicateBuilder.references({ key.to_s => fields[key] })
2193
- self.references_values |= references unless references.empty?
2194
- end
2195
- arel_column("#{key}.#{column}") do
2196
- predicate_builder.resolve_arel_attribute(key.to_s, column)
2197
- end.as(column_alias.to_s)
2251
+ arel_column_with_table(table_name, column)
2252
+ .as(model.adapter_class.quote_column_name(column_alias.to_s))
2198
2253
  end
2199
2254
  when Array
2200
2255
  columns_aliases.map do |column|
2201
- arel_column("#{key}.#{column}", &:itself)
2256
+ arel_column_with_table(table_name, column)
2202
2257
  end
2203
2258
  when String, Symbol
2204
- arel_column(key.to_s) do
2205
- predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
2206
- end.as(columns_aliases.to_s)
2259
+ arel_column(key)
2260
+ .as(model.adapter_class.quote_column_name(columns_aliases.to_s))
2207
2261
  end
2208
2262
  end
2209
2263
  end
@@ -2224,11 +2278,11 @@ module ActiveRecord
2224
2278
  values = other.values
2225
2279
  STRUCTURAL_VALUE_METHODS.reject do |method|
2226
2280
  v1, v2 = @values[method], values[method]
2227
- if v1.is_a?(Array)
2228
- next true unless v2.is_a?(Array)
2229
- v1 = v1.uniq
2230
- v2 = v2.uniq
2231
- end
2281
+
2282
+ # `and`/`or` are focused to combine where-like clauses, so it relaxes
2283
+ # the difference when other's multi values are uninitialized.
2284
+ next true if v1.is_a?(Array) && v2.nil?
2285
+
2232
2286
  v1 == v2
2233
2287
  end
2234
2288
  end
@@ -7,7 +7,7 @@ require "active_record/relation/merger"
7
7
  module ActiveRecord
8
8
  module SpawnMethods
9
9
  def spawn # :nodoc:
10
- already_in_scope?(klass.scope_registry) ? klass.all : clone
10
+ already_in_scope?(model.scope_registry) ? model.all : clone
11
11
  end
12
12
 
13
13
  # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
@@ -52,18 +52,18 @@ module ActiveRecord
52
52
  end
53
53
  end
54
54
 
55
- # Removes from the query the condition(s) specified in +skips+.
55
+ # Removes the condition(s) specified in +skips+ from the query.
56
56
  #
57
- # Post.order('id asc').except(:order) # discards the order condition
58
- # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
57
+ # Post.order('id asc').except(:order) # removes the order condition
58
+ # Post.where('id > 10').order('id asc').except(:where) # removes the where condition but keeps the order
59
59
  def except(*skips)
60
60
  relation_with values.except(*skips)
61
61
  end
62
62
 
63
- # Removes any condition from the query other than the one(s) specified in +onlies+.
63
+ # Keeps only the condition(s) specified in +onlies+ in the query, removing all others.
64
64
  #
65
- # Post.order('id asc').only(:where) # discards the order condition
66
- # Post.order('id asc').only(:where, :order) # uses the specified order
65
+ # Post.order('id asc').only(:where) # keeps only the where condition, removes the order
66
+ # Post.order('id asc').only(:where, :order) # keeps only the where and order conditions
67
67
  def only(*onlies)
68
68
  relation_with values.slice(*onlies)
69
69
  end
@@ -182,7 +182,7 @@ module ActiveRecord
182
182
  non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) }
183
183
 
184
184
  predicates.reject do |node|
185
- if !non_attrs.empty? && node.equality? && node.left.is_a?(Arel::Predications)
185
+ if !non_attrs.empty? && equality_node?(node) && node.left.is_a?(Arel::Predications)
186
186
  non_attrs.include?(node.left)
187
187
  end || Arel.fetch_attribute(node) do |attr|
188
188
  attrs.include?(attr) || columns.include?(attr.name.to_s)
@@ -194,7 +194,7 @@ module ActiveRecord
194
194
  non_empty_predicates.map do |node|
195
195
  case node
196
196
  when Arel::Nodes::SqlLiteral, ::String
197
- wrap_sql_literal(node)
197
+ Arel::Nodes::Grouping.new(node)
198
198
  else node
199
199
  end
200
200
  end
@@ -205,13 +205,6 @@ module ActiveRecord
205
205
  predicates - ARRAY_WITH_EMPTY_STRING
206
206
  end
207
207
 
208
- def wrap_sql_literal(node)
209
- if ::String === node
210
- node = Arel.sql(node)
211
- end
212
- Arel::Nodes::Grouping.new(node)
213
- end
214
-
215
208
  def extract_node_value(node)
216
209
  if node.respond_to?(:value_before_type_cast)
217
210
  node.value_before_type_cast