activerecord 7.2.2.1 → 8.0.5

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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +523 -677
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -0
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +34 -10
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +7 -6
  9. data/lib/active_record/associations/collection_association.rb +10 -8
  10. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  11. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  12. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  13. data/lib/active_record/associations/join_dependency.rb +2 -2
  14. data/lib/active_record/associations/preloader/association.rb +2 -2
  15. data/lib/active_record/associations/preloader/batch.rb +7 -1
  16. data/lib/active_record/associations/singular_association.rb +8 -3
  17. data/lib/active_record/associations.rb +34 -4
  18. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  19. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  20. data/lib/active_record/attribute_methods/query.rb +34 -0
  21. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  22. data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
  23. data/lib/active_record/attribute_methods.rb +24 -19
  24. data/lib/active_record/attributes.rb +37 -26
  25. data/lib/active_record/autosave_association.rb +91 -39
  26. data/lib/active_record/base.rb +2 -2
  27. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
  28. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  29. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  30. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +55 -47
  31. data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
  32. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -8
  33. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +40 -10
  37. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  38. data/lib/active_record/connection_adapters/abstract_adapter.rb +92 -70
  39. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +35 -46
  40. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -9
  45. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  46. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -2
  47. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
  48. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  49. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  50. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  51. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  52. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -24
  53. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
  54. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +60 -17
  55. data/lib/active_record/connection_adapters/postgresql_adapter.rb +61 -99
  56. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  57. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  58. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
  59. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
  60. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  61. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  62. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +10 -1
  63. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +63 -17
  64. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  65. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  66. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  67. data/lib/active_record/connection_adapters.rb +0 -56
  68. data/lib/active_record/connection_handling.rb +35 -9
  69. data/lib/active_record/core.rb +57 -22
  70. data/lib/active_record/counter_cache.rb +1 -1
  71. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  72. data/lib/active_record/database_configurations/database_config.rb +4 -0
  73. data/lib/active_record/database_configurations/hash_config.rb +16 -2
  74. data/lib/active_record/delegated_type.rb +18 -18
  75. data/lib/active_record/encryption/config.rb +3 -1
  76. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  77. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  78. data/lib/active_record/encryption/encryptor.rb +37 -28
  79. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  80. data/lib/active_record/encryption/scheme.rb +8 -1
  81. data/lib/active_record/enum.rb +22 -34
  82. data/lib/active_record/errors.rb +16 -8
  83. data/lib/active_record/fixture_set/table_row.rb +19 -2
  84. data/lib/active_record/fixtures.rb +0 -2
  85. data/lib/active_record/future_result.rb +15 -9
  86. data/lib/active_record/gem_version.rb +4 -4
  87. data/lib/active_record/insert_all.rb +3 -3
  88. data/lib/active_record/locking/optimistic.rb +1 -1
  89. data/lib/active_record/log_subscriber.rb +5 -11
  90. data/lib/active_record/migration/command_recorder.rb +32 -12
  91. data/lib/active_record/migration/compatibility.rb +5 -2
  92. data/lib/active_record/migration.rb +40 -43
  93. data/lib/active_record/model_schema.rb +3 -4
  94. data/lib/active_record/nested_attributes.rb +4 -6
  95. data/lib/active_record/persistence.rb +128 -130
  96. data/lib/active_record/query_logs.rb +106 -50
  97. data/lib/active_record/query_logs_formatter.rb +17 -28
  98. data/lib/active_record/querying.rb +12 -12
  99. data/lib/active_record/railtie.rb +4 -28
  100. data/lib/active_record/railties/databases.rake +11 -34
  101. data/lib/active_record/reflection.rb +18 -21
  102. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  103. data/lib/active_record/relation/batches.rb +132 -72
  104. data/lib/active_record/relation/calculations.rb +71 -65
  105. data/lib/active_record/relation/delegation.rb +25 -14
  106. data/lib/active_record/relation/finder_methods.rb +32 -31
  107. data/lib/active_record/relation/merger.rb +8 -8
  108. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  109. data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
  110. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  111. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  112. data/lib/active_record/relation/predicate_builder.rb +13 -0
  113. data/lib/active_record/relation/query_attribute.rb +1 -1
  114. data/lib/active_record/relation/query_methods.rb +121 -70
  115. data/lib/active_record/relation/spawn_methods.rb +7 -7
  116. data/lib/active_record/relation/where_clause.rb +9 -3
  117. data/lib/active_record/relation.rb +95 -67
  118. data/lib/active_record/result.rb +66 -4
  119. data/lib/active_record/sanitization.rb +7 -6
  120. data/lib/active_record/schema_dumper.rb +34 -11
  121. data/lib/active_record/schema_migration.rb +2 -1
  122. data/lib/active_record/scoping/named.rb +5 -2
  123. data/lib/active_record/secure_token.rb +3 -3
  124. data/lib/active_record/signed_id.rb +7 -6
  125. data/lib/active_record/statement_cache.rb +14 -14
  126. data/lib/active_record/store.rb +7 -3
  127. data/lib/active_record/table_metadata.rb +1 -3
  128. data/lib/active_record/tasks/database_tasks.rb +69 -60
  129. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  130. data/lib/active_record/tasks/postgresql_database_tasks.rb +9 -1
  131. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  132. data/lib/active_record/test_databases.rb +1 -1
  133. data/lib/active_record/test_fixtures.rb +12 -0
  134. data/lib/active_record/token_for.rb +1 -1
  135. data/lib/active_record/transactions.rb +8 -7
  136. data/lib/active_record/type/serialized.rb +5 -0
  137. data/lib/active_record/validations/uniqueness.rb +8 -8
  138. data/lib/active_record.rb +22 -49
  139. data/lib/arel/collectors/bind.rb +2 -2
  140. data/lib/arel/collectors/sql_string.rb +1 -1
  141. data/lib/arel/collectors/substitute_binds.rb +2 -2
  142. data/lib/arel/crud.rb +2 -0
  143. data/lib/arel/delete_manager.rb +5 -0
  144. data/lib/arel/nodes/binary.rb +1 -1
  145. data/lib/arel/nodes/delete_statement.rb +4 -2
  146. data/lib/arel/nodes/node.rb +1 -1
  147. data/lib/arel/nodes/sql_literal.rb +1 -1
  148. data/lib/arel/nodes/update_statement.rb +4 -2
  149. data/lib/arel/predications.rb +1 -3
  150. data/lib/arel/select_manager.rb +6 -2
  151. data/lib/arel/table.rb +3 -7
  152. data/lib/arel/update_manager.rb +5 -0
  153. data/lib/arel/visitors/dot.rb +2 -0
  154. data/lib/arel/visitors/to_sql.rb +3 -1
  155. metadata +11 -15
  156. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -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
 
@@ -72,11 +72,24 @@ 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
85
  return ["1=0"] 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)
@@ -35,7 +35,7 @@ module ActiveRecord
35
35
  def nil?
36
36
  unless value_before_type_cast.is_a?(StatementCache::Substitute)
37
37
  value_before_type_cast.nil? ||
38
- type.respond_to?(:subtype) && serializable? && value_for_database.nil?
38
+ (type.respond_to?(:subtype) || type.respond_to?(:normalizer)) && serializable? && value_for_database.nil?
39
39
  end
40
40
  end
41
41
 
@@ -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
  #
@@ -491,7 +498,8 @@ module ActiveRecord
491
498
 
492
499
  # Like #with, but modifies relation in place.
493
500
  def with!(*args) # :nodoc:
494
- self.with_values += args
501
+ args = process_with_args(args)
502
+ self.with_values |= args
495
503
  self
496
504
  end
497
505
 
@@ -499,10 +507,10 @@ module ActiveRecord
499
507
  #
500
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')])
501
509
  # # => ActiveRecord::Relation
502
- # # WITH post_and_replies AS (
510
+ # # WITH RECURSIVE post_and_replies AS (
503
511
  # # (SELECT * FROM posts WHERE id = 42)
504
512
  # # UNION ALL
505
- # # (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
513
+ # # (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
506
514
  # # )
507
515
  # # SELECT * FROM posts
508
516
  #
@@ -514,7 +522,8 @@ module ActiveRecord
514
522
 
515
523
  # Like #with_recursive but modifies the relation in place.
516
524
  def with_recursive!(*args) # :nodoc:
517
- self.with_values += args
525
+ args = process_with_args(args)
526
+ self.with_values |= args
518
527
  @with_is_recursive = true
519
528
  self
520
529
  end
@@ -696,26 +705,39 @@ module ActiveRecord
696
705
  # # WHEN "conversations"."status" = 0 THEN 3
697
706
  # # END ASC
698
707
  #
699
- def in_order_of(column, values)
700
- 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)
701
719
  return spawn.none! if values.empty?
702
720
 
703
721
  references = column_references([column])
704
722
  self.references_values |= references unless references.empty?
705
723
 
706
- 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) }
707
725
  arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
708
726
 
709
- where_clause =
710
- if values.include?(nil)
711
- arel_column.in(values.compact).or(arel_column.eq(nil))
712
- else
713
- arel_column.in(values)
714
- end
727
+ scope = spawn.order!(build_case_for_value_position(arel_column, values, filter: filter))
715
728
 
716
- spawn
717
- .order!(build_case_for_value_position(arel_column, values))
718
- .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
719
741
  end
720
742
 
721
743
  # Replaces any existing order defined on the relation with the specified order.
@@ -1277,13 +1299,13 @@ module ActiveRecord
1277
1299
  #
1278
1300
  # users = User.readonly
1279
1301
  # users.first.save
1280
- # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1302
+ # # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1281
1303
  #
1282
1304
  # To make a readonly relation writable, pass +false+.
1283
1305
  #
1284
1306
  # users.readonly(false)
1285
1307
  # users.first.save
1286
- # => true
1308
+ # # => true
1287
1309
  def readonly(value = true)
1288
1310
  spawn.readonly!(value)
1289
1311
  end
@@ -1298,7 +1320,7 @@ module ActiveRecord
1298
1320
  #
1299
1321
  # user = User.strict_loading.first
1300
1322
  # user.comments.to_a
1301
- # => ActiveRecord::StrictLoadingViolationError
1323
+ # # => ActiveRecord::StrictLoadingViolationError
1302
1324
  def strict_loading(value = true)
1303
1325
  spawn.strict_loading!(value)
1304
1326
  end
@@ -1554,8 +1576,8 @@ module ActiveRecord
1554
1576
  records.flatten!(1)
1555
1577
  records.compact!
1556
1578
 
1557
- unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
1558
- raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1579
+ unless records.all?(model) && relations.all? { |relation| relation.model == model }
1580
+ raise ArgumentError, "You must only pass a single or collection of #{model.name} objects to ##{__callee__}."
1559
1581
  end
1560
1582
 
1561
1583
  spawn.excluding!(records + relations.flat_map(&:ids))
@@ -1575,7 +1597,7 @@ module ActiveRecord
1575
1597
 
1576
1598
  def construct_join_dependency(associations, join_type) # :nodoc:
1577
1599
  ActiveRecord::Associations::JoinDependency.new(
1578
- klass, table, associations, join_type
1600
+ model, table, associations, join_type
1579
1601
  )
1580
1602
  end
1581
1603
 
@@ -1604,15 +1626,15 @@ module ActiveRecord
1604
1626
  elsif opts.include?("?")
1605
1627
  parts = [build_bound_sql_literal(opts, rest)]
1606
1628
  else
1607
- parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1629
+ parts = [model.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1608
1630
  end
1609
1631
  when Hash
1610
1632
  opts = opts.transform_keys do |key|
1611
1633
  if key.is_a?(Array)
1612
- key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
1634
+ key.map { |k| model.attribute_aliases[k.to_s] || k.to_s }
1613
1635
  else
1614
1636
  key = key.to_s
1615
- klass.attribute_aliases[key] || key
1637
+ model.attribute_aliases[key] || key
1616
1638
  end
1617
1639
  end
1618
1640
  references = PredicateBuilder.references(opts)
@@ -1640,12 +1662,8 @@ module ActiveRecord
1640
1662
  def arel_columns(columns)
1641
1663
  columns.flat_map do |field|
1642
1664
  case field
1643
- when Symbol
1644
- arel_column(field.to_s) do |attr_name|
1645
- adapter_class.quote_table_name(attr_name)
1646
- end
1647
- when String
1648
- arel_column(field, &:itself)
1665
+ when Symbol, String
1666
+ arel_column(field)
1649
1667
  when Proc
1650
1668
  field.call
1651
1669
  when Hash
@@ -1828,7 +1846,7 @@ module ActiveRecord
1828
1846
 
1829
1847
  joins = joins_values.dup
1830
1848
  if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1831
- stashed_eager_load = joins.pop if joins.last.base_klass == klass
1849
+ stashed_eager_load = joins.pop if joins.last.base_klass == model
1832
1850
  end
1833
1851
 
1834
1852
  joins.each_with_index do |join, i|
@@ -1885,8 +1903,8 @@ module ActiveRecord
1885
1903
  def build_select(arel)
1886
1904
  if select_values.any?
1887
1905
  arel.project(*arel_columns(select_values))
1888
- elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1889
- arel.project(*klass.column_names.map { |field| table[field] })
1906
+ elsif model.ignored_columns.any? || model.enumerate_columns_in_select_statements
1907
+ arel.project(*model.column_names.map { |field| table[field] })
1890
1908
  else
1891
1909
  arel.project(table[Arel.star])
1892
1910
  end
@@ -1896,8 +1914,6 @@ module ActiveRecord
1896
1914
  return if with_values.empty?
1897
1915
 
1898
1916
  with_statements = with_values.map do |with_value|
1899
- raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
1900
-
1901
1917
  build_with_value_from_hash(with_value)
1902
1918
  end
1903
1919
 
@@ -1912,7 +1928,8 @@ module ActiveRecord
1912
1928
 
1913
1929
  def build_with_expression_from_value(value, nested = false)
1914
1930
  case value
1915
- when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1931
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::BoundSqlLiteral
1932
+ Arel::Nodes::Grouping.new(value)
1916
1933
  when ActiveRecord::Relation
1917
1934
  if nested
1918
1935
  value.arel.ast
@@ -1939,29 +1956,60 @@ module ActiveRecord
1939
1956
  with_table = Arel::Table.new(name)
1940
1957
 
1941
1958
  table.join(with_table, kind).on(
1942
- with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
1959
+ with_table[model.model_name.to_s.foreign_key].eq(table[model.primary_key])
1943
1960
  ).join_sources.first
1944
1961
  end
1945
1962
 
1963
+ def arel_columns_from_hash(fields)
1964
+ fields.flat_map do |table_name, columns|
1965
+ table_name = table_name.name if table_name.is_a?(Symbol)
1966
+ case columns
1967
+ when Symbol, String
1968
+ arel_column_with_table(table_name, columns)
1969
+ when Array
1970
+ columns.map do |column|
1971
+ arel_column_with_table(table_name, column)
1972
+ end
1973
+ else
1974
+ raise TypeError, "Expected Symbol, String or Array, got: #{columns.class}"
1975
+ end
1976
+ end
1977
+ end
1978
+
1979
+ def arel_column_with_table(table_name, column_name)
1980
+ self.references_values |= [Arel.sql(table_name, retryable: true)]
1981
+
1982
+ if column_name.is_a?(Symbol) || !column_name.match?(/\W/)
1983
+ predicate_builder.resolve_arel_attribute(table_name, column_name) do
1984
+ lookup_table_klass_from_join_dependencies(table_name)
1985
+ end
1986
+ else
1987
+ Arel.sql("#{model.adapter_class.quote_table_name(table_name)}.#{column_name}")
1988
+ end
1989
+ end
1990
+
1946
1991
  def arel_column(field)
1947
- field = klass.attribute_aliases[field] || field
1992
+ field = field.name if is_symbol = field.is_a?(Symbol)
1993
+
1994
+ field = model.attribute_aliases[field] || field.to_s
1948
1995
  from = from_clause.name || from_clause.value
1949
1996
 
1950
- if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1997
+ if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
1951
1998
  table[field]
1952
1999
  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
2000
+ arel_column_with_table(table, column)
2001
+ elsif block_given?
1958
2002
  yield field
2003
+ elsif Arel.arel_node?(field)
2004
+ field
2005
+ else
2006
+ Arel.sql(is_symbol ? model.adapter_class.quote_table_name(field) : field)
1959
2007
  end
1960
2008
  end
1961
2009
 
1962
2010
  def table_name_matches?(from)
1963
2011
  table_name = Regexp.escape(table.name)
1964
- quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
2012
+ quoted_table_name = Regexp.escape(model.adapter_class.quote_table_name(table.name))
1965
2013
  /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1966
2014
  end
1967
2015
 
@@ -2032,7 +2080,7 @@ module ActiveRecord
2032
2080
  end
2033
2081
 
2034
2082
  def preprocess_order_args(order_args)
2035
- @klass.disallow_raw_sql!(
2083
+ model.disallow_raw_sql!(
2036
2084
  flattened_args(order_args),
2037
2085
  permit: model.adapter_class.column_name_with_order_matcher
2038
2086
  )
@@ -2070,7 +2118,7 @@ module ActiveRecord
2070
2118
 
2071
2119
  def sanitize_order_arguments(order_args)
2072
2120
  order_args.map! do |arg|
2073
- klass.sanitize_sql_for_order(arg)
2121
+ model.sanitize_sql_for_order(arg)
2074
2122
  end
2075
2123
  end
2076
2124
 
@@ -2096,7 +2144,7 @@ module ActiveRecord
2096
2144
  arg.expr.relation.name
2097
2145
  end
2098
2146
  end
2099
- end.compact
2147
+ end.filter_map { |ref| Arel.sql(ref, retryable: true) if ref }
2100
2148
  end
2101
2149
 
2102
2150
  def extract_table_name_from(string)
@@ -2108,17 +2156,18 @@ module ActiveRecord
2108
2156
  if attr_name == "count" && !group_values.empty?
2109
2157
  table[attr_name]
2110
2158
  else
2111
- Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
2159
+ Arel.sql(model.adapter_class.quote_table_name(attr_name), retryable: true)
2112
2160
  end
2113
2161
  end
2114
2162
  end
2115
2163
 
2116
- def build_case_for_value_position(column, values)
2164
+ def build_case_for_value_position(column, values, filter: true)
2117
2165
  node = Arel::Nodes::Case.new
2118
2166
  values.each.with_index(1) do |value, order|
2119
2167
  node.when(column.eq(value)).then(order)
2120
2168
  end
2121
2169
 
2170
+ node = node.else(values.length + 1) unless filter
2122
2171
  Arel::Nodes::Ascending.new(node)
2123
2172
  end
2124
2173
 
@@ -2176,38 +2225,40 @@ module ActiveRecord
2176
2225
  def process_select_args(fields)
2177
2226
  fields.flat_map do |field|
2178
2227
  if field.is_a?(Hash)
2179
- arel_columns_from_hash(field)
2228
+ arel_column_aliases_from_hash(field)
2180
2229
  else
2181
2230
  field
2182
2231
  end
2183
2232
  end
2184
2233
  end
2185
2234
 
2186
- def arel_columns_from_hash(fields)
2235
+ def arel_column_aliases_from_hash(fields)
2187
2236
  fields.flat_map do |key, columns_aliases|
2237
+ table_name = key.is_a?(Symbol) ? key.name : key
2188
2238
  case columns_aliases
2189
2239
  when Hash
2190
2240
  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)
2241
+ arel_column_with_table(table_name, column)
2242
+ .as(model.adapter_class.quote_column_name(column_alias.to_s))
2198
2243
  end
2199
2244
  when Array
2200
2245
  columns_aliases.map do |column|
2201
- arel_column("#{key}.#{column}", &:itself)
2246
+ arel_column_with_table(table_name, column)
2202
2247
  end
2203
2248
  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)
2249
+ arel_column(key)
2250
+ .as(model.adapter_class.quote_column_name(columns_aliases.to_s))
2207
2251
  end
2208
2252
  end
2209
2253
  end
2210
2254
 
2255
+ def process_with_args(args)
2256
+ args.flat_map do |arg|
2257
+ raise ArgumentError, "Unsupported argument type: #{arg} #{arg.class}" unless arg.is_a?(Hash)
2258
+ arg.map { |k, v| { k => v } }
2259
+ end
2260
+ end
2261
+
2211
2262
  STRUCTURAL_VALUE_METHODS = (
2212
2263
  Relation::VALUE_METHODS -
2213
2264
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
@@ -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
@@ -135,10 +135,14 @@ module ActiveRecord
135
135
 
136
136
  def extract_attribute(node)
137
137
  attr_node = nil
138
- Arel.fetch_attribute(node) do |attr|
139
- return if attr_node&.!= attr # all attr nodes should be the same
138
+
139
+ valid_attrs = Arel.fetch_attribute(node) do |attr|
140
+ !attr_node || attr_node == attr # all attr nodes should be the same
141
+ ensure
140
142
  attr_node = attr
141
143
  end
144
+ return unless valid_attrs # all nested nodes should yield an attribute
145
+
142
146
  attr_node
143
147
  end
144
148
 
@@ -172,11 +176,13 @@ module ActiveRecord
172
176
  end
173
177
 
174
178
  def except_predicates(columns)
179
+ return predicates if columns.empty?
180
+
175
181
  attrs = columns.extract! { |node| node.is_a?(Arel::Attribute) }
176
182
  non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) }
177
183
 
178
184
  predicates.reject do |node|
179
- 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)
180
186
  non_attrs.include?(node.left)
181
187
  end || Arel.fetch_attribute(node) do |attr|
182
188
  attrs.include?(attr) || columns.include?(attr.name.to_s)