activerecord 8.0.2.1 → 8.1.0.beta1

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 (159) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +459 -421
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/belongs_to_association.rb +9 -1
  7. data/lib/active_record/associations/builder/association.rb +16 -5
  8. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  9. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  10. data/lib/active_record/associations/builder/has_one.rb +1 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  12. data/lib/active_record/associations/collection_association.rb +3 -3
  13. data/lib/active_record/associations/collection_proxy.rb +22 -4
  14. data/lib/active_record/associations/deprecation.rb +88 -0
  15. data/lib/active_record/associations/errors.rb +3 -0
  16. data/lib/active_record/associations/join_dependency.rb +2 -0
  17. data/lib/active_record/associations/preloader/branch.rb +1 -0
  18. data/lib/active_record/associations.rb +159 -21
  19. data/lib/active_record/attribute_methods/query.rb +34 -0
  20. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  21. data/lib/active_record/attributes.rb +38 -24
  22. data/lib/active_record/base.rb +0 -1
  23. data/lib/active_record/coders/json.rb +14 -5
  24. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +2 -4
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
  26. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  27. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +384 -49
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -1
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  31. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  32. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  33. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
  35. data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -13
  37. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
  38. data/lib/active_record/connection_adapters/column.rb +17 -4
  39. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  45. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -16
  46. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  47. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  48. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  49. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
  50. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  51. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
  52. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
  53. data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -7
  54. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  55. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -27
  56. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  57. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  58. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
  59. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  60. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  61. data/lib/active_record/connection_adapters.rb +1 -0
  62. data/lib/active_record/connection_handling.rb +1 -1
  63. data/lib/active_record/core.rb +12 -9
  64. data/lib/active_record/counter_cache.rb +33 -8
  65. data/lib/active_record/database_configurations/database_config.rb +5 -1
  66. data/lib/active_record/database_configurations/hash_config.rb +56 -9
  67. data/lib/active_record/database_configurations/url_config.rb +13 -3
  68. data/lib/active_record/database_configurations.rb +7 -3
  69. data/lib/active_record/delegated_type.rb +2 -2
  70. data/lib/active_record/dynamic_matchers.rb +54 -69
  71. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  72. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  73. data/lib/active_record/encryption/encryptor.rb +27 -25
  74. data/lib/active_record/encryption/scheme.rb +1 -1
  75. data/lib/active_record/enum.rb +37 -20
  76. data/lib/active_record/errors.rb +20 -4
  77. data/lib/active_record/explain_registry.rb +0 -1
  78. data/lib/active_record/filter_attribute_handler.rb +73 -0
  79. data/lib/active_record/fixture_set/table_row.rb +19 -2
  80. data/lib/active_record/fixtures.rb +2 -2
  81. data/lib/active_record/gem_version.rb +3 -3
  82. data/lib/active_record/inheritance.rb +1 -1
  83. data/lib/active_record/insert_all.rb +12 -7
  84. data/lib/active_record/locking/optimistic.rb +7 -0
  85. data/lib/active_record/locking/pessimistic.rb +5 -0
  86. data/lib/active_record/log_subscriber.rb +1 -5
  87. data/lib/active_record/middleware/shard_selector.rb +34 -17
  88. data/lib/active_record/migration/command_recorder.rb +14 -1
  89. data/lib/active_record/migration/compatibility.rb +34 -24
  90. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  91. data/lib/active_record/migration.rb +31 -21
  92. data/lib/active_record/model_schema.rb +10 -7
  93. data/lib/active_record/nested_attributes.rb +2 -0
  94. data/lib/active_record/persistence.rb +34 -3
  95. data/lib/active_record/query_cache.rb +22 -15
  96. data/lib/active_record/query_logs.rb +7 -7
  97. data/lib/active_record/querying.rb +4 -4
  98. data/lib/active_record/railtie.rb +34 -5
  99. data/lib/active_record/railties/databases.rake +23 -19
  100. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  101. data/lib/active_record/railties/job_runtime.rb +10 -11
  102. data/lib/active_record/reflection.rb +42 -3
  103. data/lib/active_record/relation/batches.rb +26 -12
  104. data/lib/active_record/relation/calculations.rb +35 -25
  105. data/lib/active_record/relation/delegation.rb +0 -1
  106. data/lib/active_record/relation/finder_methods.rb +37 -21
  107. data/lib/active_record/relation/merger.rb +2 -2
  108. data/lib/active_record/relation/predicate_builder.rb +2 -2
  109. data/lib/active_record/relation/query_attribute.rb +3 -1
  110. data/lib/active_record/relation/query_methods.rb +43 -33
  111. data/lib/active_record/relation/spawn_methods.rb +6 -6
  112. data/lib/active_record/relation/where_clause.rb +7 -10
  113. data/lib/active_record/relation.rb +37 -15
  114. data/lib/active_record/result.rb +44 -21
  115. data/lib/active_record/sanitization.rb +2 -0
  116. data/lib/active_record/schema_dumper.rb +12 -10
  117. data/lib/active_record/scoping.rb +0 -1
  118. data/lib/active_record/secure_token.rb +3 -3
  119. data/lib/active_record/signed_id.rb +46 -18
  120. data/lib/active_record/statement_cache.rb +13 -9
  121. data/lib/active_record/store.rb +44 -19
  122. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  123. data/lib/active_record/tasks/database_tasks.rb +24 -35
  124. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  125. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  126. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  127. data/lib/active_record/test_databases.rb +11 -3
  128. data/lib/active_record/test_fixtures.rb +27 -2
  129. data/lib/active_record/testing/query_assertions.rb +8 -2
  130. data/lib/active_record/timestamp.rb +4 -2
  131. data/lib/active_record/transaction.rb +2 -5
  132. data/lib/active_record/transactions.rb +34 -10
  133. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  134. data/lib/active_record/type/internal/timezone.rb +7 -0
  135. data/lib/active_record/type/json.rb +15 -2
  136. data/lib/active_record/type/serialized.rb +11 -4
  137. data/lib/active_record/type/type_map.rb +1 -1
  138. data/lib/active_record/type_caster/connection.rb +2 -1
  139. data/lib/active_record/validations/associated.rb +1 -1
  140. data/lib/active_record.rb +68 -5
  141. data/lib/arel/alias_predication.rb +2 -0
  142. data/lib/arel/crud.rb +8 -11
  143. data/lib/arel/delete_manager.rb +5 -0
  144. data/lib/arel/nodes/count.rb +2 -2
  145. data/lib/arel/nodes/delete_statement.rb +4 -2
  146. data/lib/arel/nodes/function.rb +4 -10
  147. data/lib/arel/nodes/named_function.rb +2 -2
  148. data/lib/arel/nodes/node.rb +1 -1
  149. data/lib/arel/nodes/update_statement.rb +4 -2
  150. data/lib/arel/nodes.rb +0 -2
  151. data/lib/arel/select_manager.rb +13 -4
  152. data/lib/arel/update_manager.rb +5 -0
  153. data/lib/arel/visitors/dot.rb +2 -3
  154. data/lib/arel/visitors/postgresql.rb +55 -0
  155. data/lib/arel/visitors/sqlite.rb +55 -8
  156. data/lib/arel/visitors/to_sql.rb +5 -21
  157. data/lib/arel.rb +3 -1
  158. metadata +13 -9
  159. data/lib/active_record/normalization.rb +0 -163
@@ -24,22 +24,22 @@ module ActiveRecord
24
24
  # TravelRoute.primary_key = [:origin, :destination]
25
25
  #
26
26
  # TravelRoute.find(["Ottawa", "London"])
27
- # => #<TravelRoute origin: "Ottawa", destination: "London">
27
+ # # => #<TravelRoute origin: "Ottawa", destination: "London">
28
28
  #
29
29
  # TravelRoute.find([["Paris", "Montreal"]])
30
- # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
30
+ # # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
31
31
  #
32
32
  # TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
33
- # => [
34
- # #<TravelRoute origin: "New York", destination: "Las Vegas">,
35
- # #<TravelRoute origin: "New York", destination: "Portland">
36
- # ]
33
+ # # => [
34
+ # # #<TravelRoute origin: "New York", destination: "Las Vegas">,
35
+ # # #<TravelRoute origin: "New York", destination: "Portland">
36
+ # # ]
37
37
  #
38
38
  # TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
39
- # => [
40
- # #<TravelRoute origin: "Berlin", destination: "London">,
41
- # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
42
- # ]
39
+ # # => [
40
+ # # #<TravelRoute origin: "Berlin", destination: "London">,
41
+ # # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
42
+ # # ]
43
43
  #
44
44
  # NOTE: The returned records are in the same order as the ids you provide.
45
45
  # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
@@ -141,7 +141,7 @@ 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!
@@ -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
@@ -639,24 +639,40 @@ module ActiveRecord
639
639
  end
640
640
 
641
641
  def ordered_relation
642
- if order_values.empty? && (model.implicit_order_column || !model.query_constraints_list.nil? || primary_key)
643
- order(_order_columns.map { |column| table[column].asc })
642
+ if order_values.empty?
643
+ if !_order_columns.empty?
644
+ return order(_order_columns.map { |column| table[column].asc })
645
+ end
646
+
647
+ if ActiveRecord.raise_on_missing_required_finder_order_columns
648
+ raise MissingRequiredOrderError, <<~MSG.squish
649
+ Relation has no order values, and #{model} has no order columns to use as a default.
650
+ Set at least one of `implicit_order_column`, `query_constraints` or `primary_key` on
651
+ the model when no `order `is specified on the relation.
652
+ MSG
653
+ else
654
+ ActiveRecord.deprecator.warn(<<~MSG)
655
+ Calling order dependent finder methods (e.g. `#first`, `#second`) without `order` values on the relation,
656
+ and on a model (#{model}) that does not have any order columns (`implicit_order_column`, `query_constraints`,
657
+ or `primary_key`) to fall back on is deprecated and will raise `ActiveRecord::MissingRequiredOrderError`
658
+ in Rails 8.2.
659
+ MSG
660
+
661
+ self
662
+ end
644
663
  else
645
664
  self
646
665
  end
647
666
  end
648
667
 
649
668
  def _order_columns
650
- oc = []
669
+ columns = Array(model.implicit_order_column)
651
670
 
652
- oc << model.implicit_order_column if model.implicit_order_column
653
- oc << model.query_constraints_list if model.query_constraints_list
671
+ return columns.compact if columns.length.positive? && columns.last.nil?
654
672
 
655
- if model.primary_key && model.query_constraints_list.nil?
656
- oc << model.primary_key
657
- end
673
+ columns += Array(model.query_constraints_list || model.primary_key)
658
674
 
659
- oc.flatten.uniq.compact
675
+ columns.uniq.compact
660
676
  end
661
677
  end
662
678
  end
@@ -85,9 +85,9 @@ module ActiveRecord
85
85
  return if other.select_values.empty?
86
86
 
87
87
  if other.model == relation.model
88
- relation.select_values |= other.select_values
88
+ relation.select_values += other.select_values if relation.select_values != other.select_values
89
89
  else
90
- relation.select_values |= other.instance_eval do
90
+ relation.select_values += other.instance_eval do
91
91
  arel_columns(select_values)
92
92
  end
93
93
  end
@@ -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|
@@ -82,7 +82,7 @@ module ActiveRecord
82
82
  attr_writer :table
83
83
 
84
84
  def expand_from_hash(attributes, &block)
85
- return ["1=0"] if attributes.empty?
85
+ return [Arel.sql("1=0", retryable: true)] if attributes.empty?
86
86
 
87
87
  attributes.flat_map do |key, value|
88
88
  if key.is_a?(Array) && key.size == 1
@@ -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
 
@@ -426,7 +426,7 @@ module ActiveRecord
426
426
  end
427
427
 
428
428
  def _select!(*fields) # :nodoc:
429
- self.select_values |= fields
429
+ self.select_values += fields
430
430
  self
431
431
  end
432
432
 
@@ -510,7 +510,7 @@ module ActiveRecord
510
510
  # # WITH RECURSIVE post_and_replies AS (
511
511
  # # (SELECT * FROM posts WHERE id = 42)
512
512
  # # UNION ALL
513
- # # (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)
514
514
  # # )
515
515
  # # SELECT * FROM posts
516
516
  #
@@ -576,7 +576,7 @@ module ActiveRecord
576
576
  end
577
577
 
578
578
  def group!(*args) # :nodoc:
579
- self.group_values += args
579
+ self.group_values |= args
580
580
  self
581
581
  end
582
582
 
@@ -809,7 +809,7 @@ module ActiveRecord
809
809
  end
810
810
 
811
811
  def unscope!(*args) # :nodoc:
812
- self.unscope_values += args
812
+ self.unscope_values |= args
813
813
 
814
814
  args.each do |scope|
815
815
  case scope
@@ -1213,6 +1213,7 @@ module ActiveRecord
1213
1213
  end
1214
1214
 
1215
1215
  def limit!(value) # :nodoc:
1216
+ value = Integer(value) unless value.nil?
1216
1217
  self.limit_value = value
1217
1218
  self
1218
1219
  end
@@ -1299,13 +1300,13 @@ module ActiveRecord
1299
1300
  #
1300
1301
  # users = User.readonly
1301
1302
  # users.first.save
1302
- # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1303
+ # # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1303
1304
  #
1304
1305
  # To make a readonly relation writable, pass +false+.
1305
1306
  #
1306
1307
  # users.readonly(false)
1307
1308
  # users.first.save
1308
- # => true
1309
+ # # => true
1309
1310
  def readonly(value = true)
1310
1311
  spawn.readonly!(value)
1311
1312
  end
@@ -1320,7 +1321,7 @@ module ActiveRecord
1320
1321
  #
1321
1322
  # user = User.strict_loading.first
1322
1323
  # user.comments.to_a
1323
- # => ActiveRecord::StrictLoadingViolationError
1324
+ # # => ActiveRecord::StrictLoadingViolationError
1324
1325
  def strict_loading(value = true)
1325
1326
  spawn.strict_loading!(value)
1326
1327
  end
@@ -1465,7 +1466,7 @@ module ActiveRecord
1465
1466
  modules << Module.new(&block) if block
1466
1467
  modules.flatten!
1467
1468
 
1468
- self.extending_values += modules
1469
+ self.extending_values |= modules
1469
1470
  extend(*extending_values) if extending_values.any?
1470
1471
 
1471
1472
  self
@@ -1533,7 +1534,7 @@ module ActiveRecord
1533
1534
 
1534
1535
  # Like #annotate, but modifies relation in place.
1535
1536
  def annotate!(*args) # :nodoc:
1536
- self.annotate_values += args
1537
+ self.annotate_values |= args
1537
1538
  self
1538
1539
  end
1539
1540
 
@@ -1592,7 +1593,7 @@ module ActiveRecord
1592
1593
 
1593
1594
  # Returns the Arel object associated with the relation.
1594
1595
  def arel(aliases = nil) # :nodoc:
1595
- @arel ||= with_connection { |c| build_arel(c, aliases) }
1596
+ @arel ||= build_arel(aliases)
1596
1597
  end
1597
1598
 
1598
1599
  def construct_join_dependency(associations, join_type) # :nodoc:
@@ -1626,7 +1627,7 @@ module ActiveRecord
1626
1627
  elsif opts.include?("?")
1627
1628
  parts = [build_bound_sql_literal(opts, rest)]
1628
1629
  else
1629
- parts = [model.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1630
+ parts = [Arel.sql(model.sanitize_sql([opts, *rest]))]
1630
1631
  end
1631
1632
  when Hash
1632
1633
  opts = opts.transform_keys do |key|
@@ -1653,13 +1654,12 @@ module ActiveRecord
1653
1654
  end
1654
1655
  alias :build_having_clause :build_where_clause
1655
1656
 
1656
- def async!
1657
+ def async! # :nodoc:
1657
1658
  @async = true
1658
1659
  self
1659
1660
  end
1660
1661
 
1661
- protected
1662
- def arel_columns(columns)
1662
+ def arel_columns(columns) # :nodoc:
1663
1663
  columns.flat_map do |field|
1664
1664
  case field
1665
1665
  when Symbol, String
@@ -1747,32 +1747,27 @@ module ActiveRecord
1747
1747
  raise UnmodifiableRelation if @loaded || @arel
1748
1748
  end
1749
1749
 
1750
- def build_arel(connection, aliases = nil)
1750
+ def build_arel(aliases)
1751
1751
  arel = Arel::SelectManager.new(table)
1752
1752
 
1753
1753
  build_joins(arel.join_sources, aliases)
1754
1754
 
1755
1755
  arel.where(where_clause.ast) unless where_clause.empty?
1756
1756
  arel.having(having_clause.ast) unless having_clause.empty?
1757
- 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
1758
1758
  arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
1759
- arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1759
+ arel.group(*arel_columns(group_values)) unless group_values.empty?
1760
1760
 
1761
1761
  build_order(arel)
1762
1762
  build_with(arel)
1763
1763
  build_select(arel)
1764
1764
 
1765
1765
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1766
+ arel.comment(*annotate_values) unless annotate_values.empty?
1766
1767
  arel.distinct(distinct_value)
1767
1768
  arel.from(build_from) unless from_clause.empty?
1768
1769
  arel.lock(lock_value) if lock_value
1769
1770
 
1770
- unless annotate_values.empty?
1771
- annotates = annotate_values
1772
- annotates = annotates.uniq if annotates.size > 1
1773
- arel.comment(*annotates)
1774
- end
1775
-
1776
1771
  arel
1777
1772
  end
1778
1773
 
@@ -1990,7 +1985,7 @@ module ActiveRecord
1990
1985
  def arel_column(field)
1991
1986
  field = field.name if is_symbol = field.is_a?(Symbol)
1992
1987
 
1993
- field = model.attribute_aliases[field] || field.to_s
1988
+ field = model.attribute_aliases[field] || field
1994
1989
  from = from_clause.name || from_clause.value
1995
1990
 
1996
1991
  if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
@@ -2001,8 +1996,10 @@ module ActiveRecord
2001
1996
  yield field
2002
1997
  elsif Arel.arel_node?(field)
2003
1998
  field
1999
+ elsif is_symbol
2000
+ Arel.sql(model.adapter_class.quote_table_name(field), retryable: true)
2004
2001
  else
2005
- Arel.sql(is_symbol ? model.adapter_class.quote_table_name(field) : field)
2002
+ Arel.sql(field)
2006
2003
  end
2007
2004
  end
2008
2005
 
@@ -2014,9 +2011,15 @@ module ActiveRecord
2014
2011
 
2015
2012
  def reverse_sql_order(order_query)
2016
2013
  if order_query.empty?
2017
- return [table[primary_key].desc] if primary_key
2018
- raise IrreversibleOrderError,
2019
- "Relation has no current order and table has no primary key to be used as default order"
2014
+ if !_reverse_order_columns.empty?
2015
+ return _reverse_order_columns.map { |column| table[column].desc }
2016
+ end
2017
+
2018
+ raise IrreversibleOrderError, <<~MSG.squish
2019
+ Relation has no order values, and #{model} has no order columns to use as a default.
2020
+ Set at least one of `implicit_order_column`, or `primary_key` on the model when no
2021
+ `order `is specified on the relation.
2022
+ MSG
2020
2023
  end
2021
2024
 
2022
2025
  order_query.flat_map do |o|
@@ -2041,6 +2044,13 @@ module ActiveRecord
2041
2044
  end
2042
2045
  end
2043
2046
 
2047
+ def _reverse_order_columns
2048
+ roc = []
2049
+ roc << model.implicit_order_column if model.implicit_order_column
2050
+ roc << model.primary_key if model.primary_key
2051
+ roc.flatten.uniq.compact
2052
+ end
2053
+
2044
2054
  def does_not_support_reverse?(order)
2045
2055
  # Account for String subclasses like Arel::Nodes::SqlLiteral that
2046
2056
  # override methods like #count.
@@ -2267,11 +2277,11 @@ module ActiveRecord
2267
2277
  values = other.values
2268
2278
  STRUCTURAL_VALUE_METHODS.reject do |method|
2269
2279
  v1, v2 = @values[method], values[method]
2270
- if v1.is_a?(Array)
2271
- next true unless v2.is_a?(Array)
2272
- v1 = v1.uniq
2273
- v2 = v2.uniq
2274
- end
2280
+
2281
+ # `and`/`or` are focused to combine where-like clauses, so it relaxes
2282
+ # the difference when other's multi values are uninitialized.
2283
+ next true if v1.is_a?(Array) && v2.nil?
2284
+
2275
2285
  v1 == v2
2276
2286
  end
2277
2287
  end
@@ -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
 
@@ -188,7 +192,7 @@ module ActiveRecord
188
192
  non_empty_predicates.map do |node|
189
193
  case node
190
194
  when Arel::Nodes::SqlLiteral, ::String
191
- wrap_sql_literal(node)
195
+ Arel::Nodes::Grouping.new(node)
192
196
  else node
193
197
  end
194
198
  end
@@ -199,13 +203,6 @@ module ActiveRecord
199
203
  predicates - ARRAY_WITH_EMPTY_STRING
200
204
  end
201
205
 
202
- def wrap_sql_literal(node)
203
- if ::String === node
204
- node = Arel.sql(node)
205
- end
206
- Arel::Nodes::Grouping.new(node)
207
- end
208
-
209
206
  def extract_node_value(node)
210
207
  if node.respond_to?(:value_before_type_cast)
211
208
  node.value_before_type_cast
@@ -60,7 +60,7 @@ module ActiveRecord
60
60
  :reverse_order, :distinct, :create_with, :skip_query_cache]
61
61
 
62
62
  CLAUSE_METHODS = [:where, :having, :from]
63
- INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :with, :with_recursive]
63
+ INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL = [:distinct, :with, :with_recursive]
64
64
 
65
65
  VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
66
66
 
@@ -272,7 +272,12 @@ module ActiveRecord
272
272
  # such situation.
273
273
  def create_or_find_by(attributes, &block)
274
274
  with_connection do |connection|
275
- transaction(requires_new: true) { create(attributes, &block) }
275
+ record = nil
276
+ transaction(requires_new: true) do
277
+ record = create(attributes, &block)
278
+ record._last_transaction_return_status || raise(ActiveRecord::Rollback)
279
+ end
280
+ record
276
281
  rescue ActiveRecord::RecordNotUnique
277
282
  if connection.transaction_open?
278
283
  where(attributes).lock.find_by!(attributes)
@@ -287,7 +292,12 @@ module ActiveRecord
287
292
  # is raised if the created record is invalid.
288
293
  def create_or_find_by!(attributes, &block)
289
294
  with_connection do |connection|
290
- transaction(requires_new: true) { create!(attributes, &block) }
295
+ record = nil
296
+ transaction(requires_new: true) do
297
+ record = create!(attributes, &block)
298
+ record._last_transaction_return_status || raise(ActiveRecord::Rollback)
299
+ end
300
+ record
291
301
  rescue ActiveRecord::RecordNotUnique
292
302
  if connection.transaction_open?
293
303
  where(attributes).lock.find_by!(attributes)
@@ -590,6 +600,18 @@ module ActiveRecord
590
600
 
591
601
  return 0 if @none
592
602
 
603
+ invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
604
+ value = @values[method]
605
+ method == :distinct ? value : value&.any?
606
+ end
607
+ if invalid_methods.any?
608
+ ActiveRecord.deprecator.warn <<~MESSAGE
609
+ `#{invalid_methods.join(', ')}` is not supported by `update_all` and was never included in the generated query.
610
+
611
+ Calling `#{invalid_methods.join(', ')}` with `update_all` will raise an error in Rails 8.2.
612
+ MESSAGE
613
+ end
614
+
593
615
  if updates.is_a?(Hash)
594
616
  if model.locking_enabled? &&
595
617
  !updates.key?(model.locking_column) &&
@@ -603,17 +625,15 @@ module ActiveRecord
603
625
  end
604
626
 
605
627
  model.with_connection do |c|
606
- arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
628
+ arel = eager_loading? ? apply_join_dependency.arel : arel()
607
629
  arel.source.left = table
608
630
 
609
- group_values_arel_columns = arel_columns(group_values.uniq)
610
- having_clause_ast = having_clause.ast unless having_clause.empty?
611
631
  key = if model.composite_primary_key?
612
632
  primary_key.map { |pk| table[pk] }
613
633
  else
614
634
  table[primary_key]
615
635
  end
616
- stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
636
+ stmt = arel.compile_update(values, key)
617
637
  c.update(stmt, "#{model} Update All").tap { reset }
618
638
  end
619
639
  end
@@ -949,7 +969,7 @@ module ActiveRecord
949
969
  # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
950
970
  # If no time argument is passed, the current time is used as default.
951
971
  #
952
- # === Examples
972
+ # ==== Examples
953
973
  #
954
974
  # # Touch all records
955
975
  # Person.all.touch_all
@@ -1011,7 +1031,7 @@ module ActiveRecord
1011
1031
  def delete_all
1012
1032
  return 0 if @none
1013
1033
 
1014
- invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
1034
+ invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
1015
1035
  value = @values[method]
1016
1036
  method == :distinct ? value : value&.any?
1017
1037
  end
@@ -1020,17 +1040,15 @@ module ActiveRecord
1020
1040
  end
1021
1041
 
1022
1042
  model.with_connection do |c|
1023
- arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
1043
+ arel = eager_loading? ? apply_join_dependency.arel : arel()
1024
1044
  arel.source.left = table
1025
1045
 
1026
- group_values_arel_columns = arel_columns(group_values.uniq)
1027
- having_clause_ast = having_clause.ast unless having_clause.empty?
1028
1046
  key = if model.composite_primary_key?
1029
1047
  primary_key.map { |pk| table[pk] }
1030
1048
  else
1031
1049
  table[primary_key]
1032
1050
  end
1033
- stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
1051
+ stmt = arel.compile_delete(key)
1034
1052
 
1035
1053
  c.delete(stmt, "#{model} Delete All").tap { reset }
1036
1054
  end
@@ -1395,12 +1413,16 @@ module ActiveRecord
1395
1413
 
1396
1414
  def _increment_attribute(attribute, value = 1)
1397
1415
  bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
1398
- expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0)
1416
+ expr = table.coalesce(attribute, 0)
1399
1417
  expr = value < 0 ? expr - bind : expr + bind
1400
1418
  expr.expr
1401
1419
  end
1402
1420
 
1403
1421
  def exec_queries(&block)
1422
+ if lock_value && model.current_preventing_writes
1423
+ raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
1424
+ end
1425
+
1404
1426
  skip_query_cache_if_necessary do
1405
1427
  rows = if scheduled?
1406
1428
  future = @future_result
@@ -1440,7 +1462,7 @@ module ActiveRecord
1440
1462
  else
1441
1463
  relation = join_dependency.apply_column_aliases(relation)
1442
1464
  @_join_dependency = join_dependency
1443
- c.select_all(relation.arel, "SQL", async: async)
1465
+ c.select_all(relation.arel, "#{model.name} Eager Load", async: async)
1444
1466
  end
1445
1467
  end
1446
1468
  end