activerecord 8.0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +421 -657
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +0 -2
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/preloader/batch.rb +1 -7
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods.rb +1 -1
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +2 -2
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +383 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +27 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -18
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +88 -25
- data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +96 -49
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -23
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +10 -12
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/column.rb +2 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +3 -15
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +57 -33
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +8 -12
- data/lib/active_record/core.rb +5 -4
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +50 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +23 -7
- data/lib/active_record/explain_registry.rb +0 -1
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/future_result.rb +0 -2
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +1 -5
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +15 -2
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +26 -16
- data/lib/active_record/model_schema.rb +10 -7
- data/lib/active_record/nested_attributes.rb +2 -0
- data/lib/active_record/persistence.rb +34 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +3 -7
- data/lib/active_record/railtie.rb +32 -3
- data/lib/active_record/railties/databases.rake +16 -4
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +42 -3
- data/lib/active_record/relation/batches.rb +26 -12
- data/lib/active_record/relation/calculations.rb +20 -9
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +28 -12
- data/lib/active_record/relation/merger.rb +2 -2
- data/lib/active_record/relation/predicate_builder/array_handler.rb +1 -3
- data/lib/active_record/relation/predicate_builder.rb +2 -2
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +40 -31
- data/lib/active_record/relation/where_clause.rb +2 -11
- data/lib/active_record/relation.rb +26 -14
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +12 -10
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +2 -21
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +10 -2
- data/lib/active_record/test_fixtures.rb +27 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +32 -10
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +11 -9
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +65 -3
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +3 -21
- data/lib/arel.rb +3 -1
- metadata +14 -10
- data/lib/active_record/normalization.rb +0 -163
|
@@ -426,7 +426,7 @@ module ActiveRecord
|
|
|
426
426
|
end
|
|
427
427
|
|
|
428
428
|
def _select!(*fields) # :nodoc:
|
|
429
|
-
self.select_values
|
|
429
|
+
self.select_values += fields
|
|
430
430
|
self
|
|
431
431
|
end
|
|
432
432
|
|
|
@@ -576,7 +576,7 @@ module ActiveRecord
|
|
|
576
576
|
end
|
|
577
577
|
|
|
578
578
|
def group!(*args) # :nodoc:
|
|
579
|
-
self.group_values
|
|
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
|
|
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
|
|
@@ -1465,7 +1466,7 @@ module ActiveRecord
|
|
|
1465
1466
|
modules << Module.new(&block) if block
|
|
1466
1467
|
modules.flatten!
|
|
1467
1468
|
|
|
1468
|
-
self.extending_values
|
|
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
|
|
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 ||=
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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",
|
|
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
|
|
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
|
|
|
@@ -1928,8 +1923,7 @@ module ActiveRecord
|
|
|
1928
1923
|
|
|
1929
1924
|
def build_with_expression_from_value(value, nested = false)
|
|
1930
1925
|
case value
|
|
1931
|
-
when Arel::Nodes::SqlLiteral
|
|
1932
|
-
Arel::Nodes::Grouping.new(value)
|
|
1926
|
+
when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
|
|
1933
1927
|
when ActiveRecord::Relation
|
|
1934
1928
|
if nested
|
|
1935
1929
|
value.arel.ast
|
|
@@ -1991,7 +1985,7 @@ module ActiveRecord
|
|
|
1991
1985
|
def arel_column(field)
|
|
1992
1986
|
field = field.name if is_symbol = field.is_a?(Symbol)
|
|
1993
1987
|
|
|
1994
|
-
field = model.attribute_aliases[field] || field
|
|
1988
|
+
field = model.attribute_aliases[field] || field
|
|
1995
1989
|
from = from_clause.name || from_clause.value
|
|
1996
1990
|
|
|
1997
1991
|
if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
|
@@ -2002,8 +1996,10 @@ module ActiveRecord
|
|
|
2002
1996
|
yield field
|
|
2003
1997
|
elsif Arel.arel_node?(field)
|
|
2004
1998
|
field
|
|
1999
|
+
elsif is_symbol
|
|
2000
|
+
Arel.sql(model.adapter_class.quote_table_name(field), retryable: true)
|
|
2005
2001
|
else
|
|
2006
|
-
Arel.sql(
|
|
2002
|
+
Arel.sql(field)
|
|
2007
2003
|
end
|
|
2008
2004
|
end
|
|
2009
2005
|
|
|
@@ -2015,9 +2011,15 @@ module ActiveRecord
|
|
|
2015
2011
|
|
|
2016
2012
|
def reverse_sql_order(order_query)
|
|
2017
2013
|
if order_query.empty?
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
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
|
|
2021
2023
|
end
|
|
2022
2024
|
|
|
2023
2025
|
order_query.flat_map do |o|
|
|
@@ -2042,6 +2044,13 @@ module ActiveRecord
|
|
|
2042
2044
|
end
|
|
2043
2045
|
end
|
|
2044
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
|
+
|
|
2045
2054
|
def does_not_support_reverse?(order)
|
|
2046
2055
|
# Account for String subclasses like Arel::Nodes::SqlLiteral that
|
|
2047
2056
|
# override methods like #count.
|
|
@@ -2268,11 +2277,11 @@ module ActiveRecord
|
|
|
2268
2277
|
values = other.values
|
|
2269
2278
|
STRUCTURAL_VALUE_METHODS.reject do |method|
|
|
2270
2279
|
v1, v2 = @values[method], values[method]
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
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
|
+
|
|
2276
2285
|
v1 == v2
|
|
2277
2286
|
end
|
|
2278
2287
|
end
|
|
@@ -176,13 +176,11 @@ module ActiveRecord
|
|
|
176
176
|
end
|
|
177
177
|
|
|
178
178
|
def except_predicates(columns)
|
|
179
|
-
return predicates if columns.empty?
|
|
180
|
-
|
|
181
179
|
attrs = columns.extract! { |node| node.is_a?(Arel::Attribute) }
|
|
182
180
|
non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) }
|
|
183
181
|
|
|
184
182
|
predicates.reject do |node|
|
|
185
|
-
if !non_attrs.empty? &&
|
|
183
|
+
if !non_attrs.empty? && node.equality? && node.left.is_a?(Arel::Predications)
|
|
186
184
|
non_attrs.include?(node.left)
|
|
187
185
|
end || Arel.fetch_attribute(node) do |attr|
|
|
188
186
|
attrs.include?(attr) || columns.include?(attr.name.to_s)
|
|
@@ -194,7 +192,7 @@ module ActiveRecord
|
|
|
194
192
|
non_empty_predicates.map do |node|
|
|
195
193
|
case node
|
|
196
194
|
when Arel::Nodes::SqlLiteral, ::String
|
|
197
|
-
|
|
195
|
+
Arel::Nodes::Grouping.new(node)
|
|
198
196
|
else node
|
|
199
197
|
end
|
|
200
198
|
end
|
|
@@ -205,13 +203,6 @@ module ActiveRecord
|
|
|
205
203
|
predicates - ARRAY_WITH_EMPTY_STRING
|
|
206
204
|
end
|
|
207
205
|
|
|
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
206
|
def extract_node_value(node)
|
|
216
207
|
if node.respond_to?(:value_before_type_cast)
|
|
217
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
|
-
|
|
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
|
|
|
@@ -307,7 +307,7 @@ module ActiveRecord
|
|
|
307
307
|
end
|
|
308
308
|
end
|
|
309
309
|
|
|
310
|
-
# Like #find_or_create_by, but calls {new}[rdoc-ref:Core
|
|
310
|
+
# Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
|
|
311
311
|
# instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
|
|
312
312
|
def find_or_initialize_by(attributes, &block)
|
|
313
313
|
find_by(attributes) || new(attributes, &block)
|
|
@@ -600,6 +600,18 @@ module ActiveRecord
|
|
|
600
600
|
|
|
601
601
|
return 0 if @none
|
|
602
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
|
+
|
|
603
615
|
if updates.is_a?(Hash)
|
|
604
616
|
if model.locking_enabled? &&
|
|
605
617
|
!updates.key?(model.locking_column) &&
|
|
@@ -613,17 +625,15 @@ module ActiveRecord
|
|
|
613
625
|
end
|
|
614
626
|
|
|
615
627
|
model.with_connection do |c|
|
|
616
|
-
arel = eager_loading? ? apply_join_dependency.arel :
|
|
628
|
+
arel = eager_loading? ? apply_join_dependency.arel : arel()
|
|
617
629
|
arel.source.left = table
|
|
618
630
|
|
|
619
|
-
group_values_arel_columns = arel_columns(group_values.uniq)
|
|
620
|
-
having_clause_ast = having_clause.ast unless having_clause.empty?
|
|
621
631
|
key = if model.composite_primary_key?
|
|
622
632
|
primary_key.map { |pk| table[pk] }
|
|
623
633
|
else
|
|
624
634
|
table[primary_key]
|
|
625
635
|
end
|
|
626
|
-
stmt = arel.compile_update(values, key
|
|
636
|
+
stmt = arel.compile_update(values, key)
|
|
627
637
|
c.update(stmt, "#{model} Update All").tap { reset }
|
|
628
638
|
end
|
|
629
639
|
end
|
|
@@ -1010,7 +1020,7 @@ module ActiveRecord
|
|
|
1010
1020
|
#
|
|
1011
1021
|
# Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
|
|
1012
1022
|
#
|
|
1013
|
-
#
|
|
1023
|
+
# Both calls delete the affected posts all at once with a single DELETE statement.
|
|
1014
1024
|
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
|
|
1015
1025
|
# +after_destroy+ callbacks, use the #destroy_all method instead.
|
|
1016
1026
|
#
|
|
@@ -1021,7 +1031,7 @@ module ActiveRecord
|
|
|
1021
1031
|
def delete_all
|
|
1022
1032
|
return 0 if @none
|
|
1023
1033
|
|
|
1024
|
-
invalid_methods =
|
|
1034
|
+
invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
|
|
1025
1035
|
value = @values[method]
|
|
1026
1036
|
method == :distinct ? value : value&.any?
|
|
1027
1037
|
end
|
|
@@ -1030,17 +1040,15 @@ module ActiveRecord
|
|
|
1030
1040
|
end
|
|
1031
1041
|
|
|
1032
1042
|
model.with_connection do |c|
|
|
1033
|
-
arel = eager_loading? ? apply_join_dependency.arel :
|
|
1043
|
+
arel = eager_loading? ? apply_join_dependency.arel : arel()
|
|
1034
1044
|
arel.source.left = table
|
|
1035
1045
|
|
|
1036
|
-
group_values_arel_columns = arel_columns(group_values.uniq)
|
|
1037
|
-
having_clause_ast = having_clause.ast unless having_clause.empty?
|
|
1038
1046
|
key = if model.composite_primary_key?
|
|
1039
1047
|
primary_key.map { |pk| table[pk] }
|
|
1040
1048
|
else
|
|
1041
1049
|
table[primary_key]
|
|
1042
1050
|
end
|
|
1043
|
-
stmt = arel.compile_delete(key
|
|
1051
|
+
stmt = arel.compile_delete(key)
|
|
1044
1052
|
|
|
1045
1053
|
c.delete(stmt, "#{model} Delete All").tap { reset }
|
|
1046
1054
|
end
|
|
@@ -1405,12 +1413,16 @@ module ActiveRecord
|
|
|
1405
1413
|
|
|
1406
1414
|
def _increment_attribute(attribute, value = 1)
|
|
1407
1415
|
bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
|
|
1408
|
-
expr = table.coalesce(
|
|
1416
|
+
expr = table.coalesce(attribute, 0)
|
|
1409
1417
|
expr = value < 0 ? expr - bind : expr + bind
|
|
1410
1418
|
expr.expr
|
|
1411
1419
|
end
|
|
1412
1420
|
|
|
1413
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
|
+
|
|
1414
1426
|
skip_query_cache_if_necessary do
|
|
1415
1427
|
rows = if scheduled?
|
|
1416
1428
|
future = @future_result
|
|
@@ -1450,7 +1462,7 @@ module ActiveRecord
|
|
|
1450
1462
|
else
|
|
1451
1463
|
relation = join_dependency.apply_column_aliases(relation)
|
|
1452
1464
|
@_join_dependency = join_dependency
|
|
1453
|
-
c.select_all(relation.arel, "
|
|
1465
|
+
c.select_all(relation.arel, "#{model.name} Eager Load", async: async)
|
|
1454
1466
|
end
|
|
1455
1467
|
end
|
|
1456
1468
|
end
|
data/lib/active_record/result.rb
CHANGED
|
@@ -29,6 +29,11 @@ module ActiveRecord
|
|
|
29
29
|
# ...
|
|
30
30
|
# ]
|
|
31
31
|
#
|
|
32
|
+
# # Get the number of rows affected by the query:
|
|
33
|
+
# result = ActiveRecord::Base.lease_connection.exec_query('INSERT INTO posts (title, body) VALUES ("title_3", "body_3"), ("title_4", "body_4")')
|
|
34
|
+
# result.affected_rows
|
|
35
|
+
# # => 2
|
|
36
|
+
#
|
|
32
37
|
# # ActiveRecord::Result also includes Enumerable.
|
|
33
38
|
# result.each do |row|
|
|
34
39
|
# puts row['title'] + " " + row['body']
|
|
@@ -89,24 +94,26 @@ module ActiveRecord
|
|
|
89
94
|
alias_method :to_hash, :to_h
|
|
90
95
|
end
|
|
91
96
|
|
|
92
|
-
attr_reader :columns, :rows, :
|
|
97
|
+
attr_reader :columns, :rows, :affected_rows
|
|
93
98
|
|
|
94
|
-
def self.empty(async: false) # :nodoc:
|
|
99
|
+
def self.empty(async: false, affected_rows: nil) # :nodoc:
|
|
95
100
|
if async
|
|
96
|
-
|
|
101
|
+
FutureResult.wrap(new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows)).freeze
|
|
97
102
|
else
|
|
98
|
-
|
|
103
|
+
new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows).freeze
|
|
99
104
|
end
|
|
100
105
|
end
|
|
101
106
|
|
|
102
|
-
def initialize(columns, rows, column_types = nil)
|
|
107
|
+
def initialize(columns, rows, column_types = nil, affected_rows: nil)
|
|
103
108
|
# We freeze the strings to prevent them getting duped when
|
|
104
109
|
# used as keys in ActiveRecord::Base's @attributes hash
|
|
105
110
|
@columns = columns.each(&:-@).freeze
|
|
106
111
|
@rows = rows
|
|
107
112
|
@hash_rows = nil
|
|
108
|
-
@column_types = column_types
|
|
113
|
+
@column_types = column_types.freeze
|
|
114
|
+
@types_hash = nil
|
|
109
115
|
@column_indexes = nil
|
|
116
|
+
@affected_rows = affected_rows
|
|
110
117
|
end
|
|
111
118
|
|
|
112
119
|
# Returns true if this result set includes the column named +name+
|
|
@@ -154,6 +161,24 @@ module ActiveRecord
|
|
|
154
161
|
n ? hash_rows.last(n) : hash_rows.last
|
|
155
162
|
end
|
|
156
163
|
|
|
164
|
+
# Returns the +ActiveRecord::Type+ type of all columns.
|
|
165
|
+
# Note that not all database adapters return the result types,
|
|
166
|
+
# so the hash may be empty.
|
|
167
|
+
def column_types
|
|
168
|
+
if @column_types
|
|
169
|
+
@types_hash ||= begin
|
|
170
|
+
types = {}
|
|
171
|
+
@columns.each_with_index do |name, index|
|
|
172
|
+
type = @column_types[index] || Type.default_value
|
|
173
|
+
types[name] = types[index] = type
|
|
174
|
+
end
|
|
175
|
+
types.freeze
|
|
176
|
+
end
|
|
177
|
+
else
|
|
178
|
+
EMPTY_HASH
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
157
182
|
def result # :nodoc:
|
|
158
183
|
self
|
|
159
184
|
end
|
|
@@ -162,7 +187,7 @@ module ActiveRecord
|
|
|
162
187
|
self
|
|
163
188
|
end
|
|
164
189
|
|
|
165
|
-
def cast_values(type_overrides =
|
|
190
|
+
def cast_values(type_overrides = nil) # :nodoc:
|
|
166
191
|
if columns.one?
|
|
167
192
|
# Separated to avoid allocating an array per row
|
|
168
193
|
|
|
@@ -190,13 +215,13 @@ module ActiveRecord
|
|
|
190
215
|
|
|
191
216
|
def initialize_copy(other)
|
|
192
217
|
@rows = rows.dup
|
|
193
|
-
@column_types = column_types.dup
|
|
194
218
|
@hash_rows = nil
|
|
195
219
|
end
|
|
196
220
|
|
|
197
221
|
def freeze # :nodoc:
|
|
198
222
|
hash_rows.freeze
|
|
199
|
-
indexed_rows
|
|
223
|
+
indexed_rows
|
|
224
|
+
column_types
|
|
200
225
|
super
|
|
201
226
|
end
|
|
202
227
|
|
|
@@ -204,7 +229,7 @@ module ActiveRecord
|
|
|
204
229
|
@column_indexes ||= begin
|
|
205
230
|
index = 0
|
|
206
231
|
hash = {}
|
|
207
|
-
length
|
|
232
|
+
length = columns.length
|
|
208
233
|
while index < length
|
|
209
234
|
hash[columns[index]] = index
|
|
210
235
|
index += 1
|
|
@@ -222,10 +247,14 @@ module ActiveRecord
|
|
|
222
247
|
|
|
223
248
|
private
|
|
224
249
|
def column_type(name, index, type_overrides)
|
|
225
|
-
type_overrides
|
|
226
|
-
|
|
227
|
-
|
|
250
|
+
if type_overrides
|
|
251
|
+
type_overrides.fetch(name) do
|
|
252
|
+
column_type(name, index, nil)
|
|
228
253
|
end
|
|
254
|
+
elsif @column_types
|
|
255
|
+
@column_types[index] || Type.default_value
|
|
256
|
+
else
|
|
257
|
+
Type.default_value
|
|
229
258
|
end
|
|
230
259
|
end
|
|
231
260
|
|
|
@@ -237,14 +266,8 @@ module ActiveRecord
|
|
|
237
266
|
end
|
|
238
267
|
end
|
|
239
268
|
|
|
240
|
-
|
|
269
|
+
EMPTY_ARRAY = [].freeze
|
|
241
270
|
EMPTY_HASH = {}.freeze
|
|
242
|
-
private_constant :EMPTY_HASH
|
|
243
|
-
|
|
244
|
-
EMPTY = new(empty_array, empty_array, EMPTY_HASH).freeze
|
|
245
|
-
private_constant :EMPTY
|
|
246
|
-
|
|
247
|
-
EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
|
|
248
|
-
private_constant :EMPTY_ASYNC
|
|
271
|
+
private_constant :EMPTY_ARRAY, :EMPTY_HASH
|
|
249
272
|
end
|
|
250
273
|
end
|
|
@@ -161,6 +161,8 @@ module ActiveRecord
|
|
|
161
161
|
#
|
|
162
162
|
# sanitize_sql_array(["role = ?", 0])
|
|
163
163
|
# # => "role = '0'"
|
|
164
|
+
#
|
|
165
|
+
# Before using this method, please consider if Arel.sql would be better for your use-case
|
|
164
166
|
def sanitize_sql_array(ary)
|
|
165
167
|
statement, *values = ary
|
|
166
168
|
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
|
|
@@ -165,7 +165,7 @@ module ActiveRecord
|
|
|
165
165
|
# first dump primary key column
|
|
166
166
|
pk = @connection.primary_key(table)
|
|
167
167
|
|
|
168
|
-
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
|
|
168
|
+
tbl.print " create_table #{relation_name(remove_prefix_and_suffix(table)).inspect}"
|
|
169
169
|
|
|
170
170
|
case pk
|
|
171
171
|
when String
|
|
@@ -192,7 +192,7 @@ module ActiveRecord
|
|
|
192
192
|
tbl.puts ", force: :cascade do |t|"
|
|
193
193
|
|
|
194
194
|
# then dump all non-primary key columns
|
|
195
|
-
columns.each do |column|
|
|
195
|
+
columns.sort_by(&:name).each do |column|
|
|
196
196
|
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
|
|
197
197
|
next if column.name == pk
|
|
198
198
|
|
|
@@ -233,7 +233,7 @@ module ActiveRecord
|
|
|
233
233
|
if (indexes = @connection.indexes(table)).any?
|
|
234
234
|
add_index_statements = indexes.map do |index|
|
|
235
235
|
table_name = remove_prefix_and_suffix(index.table).inspect
|
|
236
|
-
" add_index #{([table_name] + index_parts(index)).join(', ')}"
|
|
236
|
+
" add_index #{([relation_name(table_name)] + index_parts(index)).join(', ')}"
|
|
237
237
|
end
|
|
238
238
|
|
|
239
239
|
stream.puts add_index_statements.sort.join("\n")
|
|
@@ -277,6 +277,7 @@ module ActiveRecord
|
|
|
277
277
|
index_parts << "nulls_not_distinct: #{index.nulls_not_distinct.inspect}" if index.nulls_not_distinct
|
|
278
278
|
index_parts << "type: #{index.type.inspect}" if index.type
|
|
279
279
|
index_parts << "comment: #{index.comment.inspect}" if index.comment
|
|
280
|
+
index_parts << "enabled: #{index.enabled.inspect}" if @connection.supports_disabling_indexes? && index.disabled?
|
|
280
281
|
index_parts
|
|
281
282
|
end
|
|
282
283
|
|
|
@@ -317,8 +318,8 @@ module ActiveRecord
|
|
|
317
318
|
if (foreign_keys = @connection.foreign_keys(table)).any?
|
|
318
319
|
add_foreign_key_statements = foreign_keys.map do |foreign_key|
|
|
319
320
|
parts = [
|
|
320
|
-
|
|
321
|
-
remove_prefix_and_suffix(foreign_key.to_table).inspect,
|
|
321
|
+
relation_name(remove_prefix_and_suffix(foreign_key.from_table)).inspect,
|
|
322
|
+
relation_name(remove_prefix_and_suffix(foreign_key.to_table)).inspect,
|
|
322
323
|
]
|
|
323
324
|
|
|
324
325
|
if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table, "id")
|
|
@@ -329,16 +330,13 @@ module ActiveRecord
|
|
|
329
330
|
parts << "primary_key: #{foreign_key.primary_key.inspect}"
|
|
330
331
|
end
|
|
331
332
|
|
|
332
|
-
if foreign_key.export_name_on_schema_dump?
|
|
333
|
-
parts << "name: #{foreign_key.name.inspect}"
|
|
334
|
-
end
|
|
335
|
-
|
|
333
|
+
parts << "name: #{foreign_key.name.inspect}" if foreign_key.export_name_on_schema_dump?
|
|
336
334
|
parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
|
|
337
335
|
parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
|
|
338
336
|
parts << "deferrable: #{foreign_key.deferrable.inspect}" if foreign_key.deferrable
|
|
339
337
|
parts << "validate: #{foreign_key.validate?.inspect}" unless foreign_key.validate?
|
|
340
338
|
|
|
341
|
-
" #{parts.join(', ')}"
|
|
339
|
+
" add_foreign_key #{parts.join(', ')}"
|
|
342
340
|
end
|
|
343
341
|
|
|
344
342
|
stream.puts add_foreign_key_statements.sort.join("\n")
|
|
@@ -363,6 +361,10 @@ module ActiveRecord
|
|
|
363
361
|
end
|
|
364
362
|
end
|
|
365
363
|
|
|
364
|
+
def relation_name(name)
|
|
365
|
+
name
|
|
366
|
+
end
|
|
367
|
+
|
|
366
368
|
def remove_prefix_and_suffix(table)
|
|
367
369
|
# This method appears at the top when profiling active_record test cases run.
|
|
368
370
|
# Avoid costly calculation when there are no prefix and suffix.
|
|
@@ -6,11 +6,27 @@ module ActiveRecord
|
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
8
|
included do
|
|
9
|
+
class_attribute :_signed_id_verifier, instance_accessor: false, instance_predicate: false
|
|
10
|
+
|
|
9
11
|
##
|
|
10
12
|
# :singleton-method:
|
|
11
13
|
# Set the secret used for the signed id verifier instance when using Active Record outside of \Rails.
|
|
12
14
|
# Within \Rails, this is automatically set using the \Rails application key generator.
|
|
13
15
|
class_attribute :signed_id_verifier_secret, instance_writer: false
|
|
16
|
+
module DeprecateSignedIdVerifierSecret
|
|
17
|
+
def signed_id_verifier_secret=(secret)
|
|
18
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
|
19
|
+
ActiveRecord::Base.signed_id_verifier_secret is deprecated and will be removed in Rails 8.2.
|
|
20
|
+
|
|
21
|
+
If the secret is model-specific, set Model.signed_id_verifier instead.
|
|
22
|
+
|
|
23
|
+
Otherwise, configure Rails.application.message_verifiers (or ActiveRecord.message_verifiers) with the secret.
|
|
24
|
+
MSG
|
|
25
|
+
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
singleton_class.prepend DeprecateSignedIdVerifierSecret
|
|
14
30
|
end
|
|
15
31
|
|
|
16
32
|
module RelationMethods # :nodoc:
|
|
@@ -49,10 +65,11 @@ module ActiveRecord
|
|
|
49
65
|
#
|
|
50
66
|
# travel_back
|
|
51
67
|
# User.find_signed signed_id, purpose: :password_reset # => User.first
|
|
52
|
-
def find_signed(signed_id, purpose: nil)
|
|
68
|
+
def find_signed(signed_id, purpose: nil, on_rotation: nil)
|
|
53
69
|
raise UnknownPrimaryKey.new(self) if primary_key.nil?
|
|
54
70
|
|
|
55
|
-
|
|
71
|
+
options = { on_rotation: on_rotation }.compact
|
|
72
|
+
if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose), **options)
|
|
56
73
|
find_by primary_key => id
|
|
57
74
|
end
|
|
58
75
|
end
|
|
@@ -69,26 +86,33 @@ module ActiveRecord
|
|
|
69
86
|
# signed_id = User.first.signed_id
|
|
70
87
|
# User.first.destroy
|
|
71
88
|
# User.find_signed! signed_id # => ActiveRecord::RecordNotFound
|
|
72
|
-
def find_signed!(signed_id, purpose: nil)
|
|
73
|
-
|
|
89
|
+
def find_signed!(signed_id, purpose: nil, on_rotation: nil)
|
|
90
|
+
options = { on_rotation: on_rotation }.compact
|
|
91
|
+
if id = signed_id_verifier.verify(signed_id, purpose: combine_signed_id_purposes(purpose), **options)
|
|
74
92
|
find(id)
|
|
75
93
|
end
|
|
76
94
|
end
|
|
77
95
|
|
|
78
|
-
# The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
|
|
79
|
-
# with the class-level +signed_id_verifier_secret+, which within Rails comes from
|
|
80
|
-
# {Rails.application.key_generator}[rdoc-ref:Rails::Application#key_generator].
|
|
81
|
-
# By default, it's SHA256 for the digest and JSON for the serialization.
|
|
82
96
|
def signed_id_verifier
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
97
|
+
if signed_id_verifier_secret
|
|
98
|
+
@signed_id_verifier ||= begin
|
|
99
|
+
secret = signed_id_verifier_secret
|
|
100
|
+
secret = secret.call if secret.respond_to?(:call)
|
|
101
|
+
|
|
102
|
+
if secret.nil?
|
|
103
|
+
raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed IDs"
|
|
104
|
+
end
|
|
86
105
|
|
|
87
|
-
if secret.nil?
|
|
88
|
-
raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
|
|
89
|
-
else
|
|
90
106
|
ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON, url_safe: true
|
|
91
107
|
end
|
|
108
|
+
else
|
|
109
|
+
return _signed_id_verifier if _signed_id_verifier
|
|
110
|
+
|
|
111
|
+
if ActiveRecord.message_verifiers.nil?
|
|
112
|
+
raise "You must set ActiveRecord.message_verifiers to use signed IDs"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
ActiveRecord.message_verifiers["active_record/signed_id"]
|
|
92
116
|
end
|
|
93
117
|
end
|
|
94
118
|
|
|
@@ -96,7 +120,11 @@ module ActiveRecord
|
|
|
96
120
|
# verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
|
|
97
121
|
# your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details.
|
|
98
122
|
def signed_id_verifier=(verifier)
|
|
99
|
-
|
|
123
|
+
if signed_id_verifier_secret
|
|
124
|
+
@signed_id_verifier = verifier
|
|
125
|
+
else
|
|
126
|
+
self._signed_id_verifier = verifier
|
|
127
|
+
end
|
|
100
128
|
end
|
|
101
129
|
|
|
102
130
|
# :nodoc:
|