activerecord 8.0.2 → 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 +459 -413
- data/README.rdoc +2 -2
- 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 +9 -1
- 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_association.rb +3 -3
- 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 +2 -0
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +17 -4
- data/lib/active_record/attributes.rb +38 -24
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +2 -4
- 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 +384 -49
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -1
- 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 +89 -23
- data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -13
- 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/database_statements.rb +18 -16
- 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 +12 -7
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -27
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
- 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 +1 -1
- data/lib/active_record/core.rb +13 -10
- 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 +56 -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 +2 -2
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
- data/lib/active_record/encryption/encryptor.rb +27 -25
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +37 -20
- data/lib/active_record/errors.rb +20 -4
- data/lib/active_record/explain_registry.rb +0 -1
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -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 +12 -7
- 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 +14 -1
- 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 +31 -21
- 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 +7 -7
- data/lib/active_record/querying.rb +4 -4
- data/lib/active_record/railtie.rb +34 -5
- data/lib/active_record/railties/databases.rake +23 -19
- 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 +35 -25
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +41 -24
- data/lib/active_record/relation/merger.rb +2 -2
- 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 +43 -33
- data/lib/active_record/relation/spawn_methods.rb +6 -6
- data/lib/active_record/relation/where_clause.rb +7 -10
- data/lib/active_record/relation.rb +37 -15
- 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/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +46 -18
- 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 +24 -35
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +11 -3
- 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 +34 -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 -4
- 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 +68 -5
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -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/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +13 -4
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +5 -21
- data/lib/arel.rb +3 -1
- metadata +15 -11
- 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
|
-
#
|
35
|
-
#
|
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
|
-
#
|
41
|
-
#
|
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 =
|
144
|
+
found, undesired = take(2)
|
145
145
|
|
146
146
|
if found.nil?
|
147
147
|
raise_record_not_found_exception!
|
@@ -424,12 +424,13 @@ module ActiveRecord
|
|
424
424
|
error << " with#{conditions}" if conditions
|
425
425
|
raise RecordNotFound.new(error, name, key)
|
426
426
|
elsif Array.wrap(ids).size == 1
|
427
|
-
|
427
|
+
id = Array.wrap(ids)[0]
|
428
|
+
error = "Couldn't find #{name} with '#{key}'=#{id.inspect}#{conditions}"
|
428
429
|
raise RecordNotFound.new(error, name, key, ids)
|
429
430
|
else
|
430
431
|
error = +"Couldn't find all #{name.pluralize} with '#{key}': "
|
431
|
-
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
|
432
|
-
error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
|
432
|
+
error << "(#{ids.map(&:inspect).join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
|
433
|
+
error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.map(&:inspect).join(', ')}." if not_found_ids
|
433
434
|
raise RecordNotFound.new(error, name, key, ids)
|
434
435
|
end
|
435
436
|
end
|
@@ -441,7 +442,7 @@ module ActiveRecord
|
|
441
442
|
if distinct_value && offset_value
|
442
443
|
relation = except(:order).limit!(1)
|
443
444
|
else
|
444
|
-
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)
|
445
446
|
end
|
446
447
|
|
447
448
|
case conditions
|
@@ -638,24 +639,40 @@ module ActiveRecord
|
|
638
639
|
end
|
639
640
|
|
640
641
|
def ordered_relation
|
641
|
-
if order_values.empty?
|
642
|
-
|
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
|
643
663
|
else
|
644
664
|
self
|
645
665
|
end
|
646
666
|
end
|
647
667
|
|
648
668
|
def _order_columns
|
649
|
-
|
669
|
+
columns = Array(model.implicit_order_column)
|
650
670
|
|
651
|
-
|
652
|
-
oc << model.query_constraints_list if model.query_constraints_list
|
671
|
+
return columns.compact if columns.length.positive? && columns.last.nil?
|
653
672
|
|
654
|
-
|
655
|
-
oc << model.primary_key
|
656
|
-
end
|
673
|
+
columns += Array(model.query_constraints_list || model.primary_key)
|
657
674
|
|
658
|
-
|
675
|
+
columns.uniq.compact
|
659
676
|
end
|
660
677
|
end
|
661
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
|
88
|
+
relation.select_values += other.select_values if relation.select_values != other.select_values
|
89
89
|
else
|
90
|
-
relation.select_values
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
@@ -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
|
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
|
|
@@ -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
|
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(
|
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
|
-
|
2018
|
-
|
2019
|
-
|
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
|
-
|
2271
|
-
|
2272
|
-
|
2273
|
-
|
2274
|
-
|
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
|
55
|
+
# Removes the condition(s) specified in +skips+ from the query.
|
56
56
|
#
|
57
|
-
# Post.order('id asc').except(:order) #
|
58
|
-
# Post.where('id > 10').order('id asc').except(:where) #
|
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
|
-
#
|
63
|
+
# Keeps only the condition(s) specified in +onlies+ in the query, removing all others.
|
64
64
|
#
|
65
|
-
# Post.order('id asc').only(:where) #
|
66
|
-
# Post.order('id asc').only(:where, :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
|
-
|
139
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 :
|
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
|
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
|
-
#
|
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 =
|
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 :
|
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
|
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(
|
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, "
|
1465
|
+
c.select_all(relation.arel, "#{model.name} Eager Load", async: async)
|
1444
1466
|
end
|
1445
1467
|
end
|
1446
1468
|
end
|