activerecord 7.2.3 → 8.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +612 -1055
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/association.rb +35 -11
- data/lib/active_record/associations/builder/association.rb +23 -11
- 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 +1 -1
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/base.rb +1 -2
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +412 -88
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +137 -75
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -32
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +150 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +63 -52
- 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/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- 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/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +139 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -14
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +102 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +25 -2
- data/lib/active_record/core.rb +33 -17
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +67 -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/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +8 -8
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encryptor.rb +28 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +33 -30
- data/lib/active_record/errors.rb +33 -9
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -13
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +45 -12
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +48 -42
- data/lib/active_record/model_schema.rb +38 -13
- data/lib/active_record/nested_attributes.rb +6 -6
- data/lib/active_record/persistence.rb +162 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +100 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +8 -8
- data/lib/active_record/railtie.rb +35 -30
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -38
- 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 +53 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +52 -40
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +40 -24
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +22 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +140 -86
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +2 -9
- data/lib/active_record/relation.rb +107 -75
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema_dumper.rb +18 -11
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +39 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +37 -16
- 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 +13 -2
- data/lib/active_record/type/serialized.rb +16 -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/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +84 -49
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/binary.rb +1 -1
- 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 +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/table.rb +3 -7
- 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
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +16 -13
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "active_support/core_ext/module/delegation"
|
|
4
3
|
|
|
5
4
|
module ActiveRecord
|
|
6
5
|
module Delegation # :nodoc:
|
|
@@ -22,6 +21,9 @@ module ActiveRecord
|
|
|
22
21
|
end
|
|
23
22
|
|
|
24
23
|
module DelegateCache # :nodoc:
|
|
24
|
+
@delegate_base_methods = true
|
|
25
|
+
singleton_class.attr_accessor :delegate_base_methods
|
|
26
|
+
|
|
25
27
|
def relation_delegate_class(klass)
|
|
26
28
|
@relation_delegate_cache[klass]
|
|
27
29
|
end
|
|
@@ -75,12 +77,12 @@ module ActiveRecord
|
|
|
75
77
|
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
|
|
76
78
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
77
79
|
def #{method}(...)
|
|
78
|
-
scoping {
|
|
80
|
+
scoping { model.#{method}(...) }
|
|
79
81
|
end
|
|
80
82
|
RUBY
|
|
81
83
|
else
|
|
82
84
|
define_method(method) do |*args, **kwargs, &block|
|
|
83
|
-
scoping {
|
|
85
|
+
scoping { model.public_send(method, *args, **kwargs, &block) }
|
|
84
86
|
end
|
|
85
87
|
end
|
|
86
88
|
end
|
|
@@ -92,15 +94,15 @@ module ActiveRecord
|
|
|
92
94
|
|
|
93
95
|
# This module creates compiled delegation methods dynamically at runtime, which makes
|
|
94
96
|
# subsequent calls to that method faster by avoiding method_missing. The delegations
|
|
95
|
-
# may vary depending on the
|
|
96
|
-
# for each different
|
|
97
|
+
# may vary depending on the model of a relation, so we create a subclass of Relation
|
|
98
|
+
# for each different model, and the delegations are compiled into that subclass only.
|
|
97
99
|
|
|
98
100
|
delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
|
|
99
101
|
:[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
|
|
100
102
|
:to_sentence, :to_fs, :to_formatted_s, :as_json,
|
|
101
103
|
:shuffle, :split, :slice, :index, :rindex, to: :records
|
|
102
104
|
|
|
103
|
-
delegate :primary_key, :
|
|
105
|
+
delegate :primary_key, :with_connection, :connection, :table_name, :transaction, :sanitize_sql_like, :unscoped, :name, to: :model
|
|
104
106
|
|
|
105
107
|
module ClassSpecificRelation # :nodoc:
|
|
106
108
|
extend ActiveSupport::Concern
|
|
@@ -113,11 +115,19 @@ module ActiveRecord
|
|
|
113
115
|
|
|
114
116
|
private
|
|
115
117
|
def method_missing(method, ...)
|
|
116
|
-
if
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
if model.respond_to?(method)
|
|
119
|
+
if !DelegateCache.delegate_base_methods && Base.respond_to?(method)
|
|
120
|
+
# A common mistake in Active Record's own code is to call `ActiveRecord::Base`
|
|
121
|
+
# class methods on Association. It works because it's automatically delegated, but
|
|
122
|
+
# can introduce subtle bugs because it sets the global scope.
|
|
123
|
+
# We can't deprecate this behavior because gems might depend on it, however we
|
|
124
|
+
# can ban it from Active Record's own test suite to avoid regressions.
|
|
125
|
+
raise NotImplementedError, "Active Record code shouldn't rely on association delegation into ActiveRecord::Base methods"
|
|
126
|
+
elsif !Delegation.uncacheable_methods.include?(method)
|
|
127
|
+
model.generate_relation_method(method)
|
|
119
128
|
end
|
|
120
|
-
|
|
129
|
+
|
|
130
|
+
scoping { model.public_send(method, ...) }
|
|
121
131
|
else
|
|
122
132
|
super
|
|
123
133
|
end
|
|
@@ -125,19 +135,19 @@ module ActiveRecord
|
|
|
125
135
|
end
|
|
126
136
|
|
|
127
137
|
module ClassMethods # :nodoc:
|
|
128
|
-
def create(
|
|
129
|
-
relation_class_for(
|
|
138
|
+
def create(model, ...)
|
|
139
|
+
relation_class_for(model).new(model, ...)
|
|
130
140
|
end
|
|
131
141
|
|
|
132
142
|
private
|
|
133
|
-
def relation_class_for(
|
|
134
|
-
|
|
143
|
+
def relation_class_for(model)
|
|
144
|
+
model.relation_delegate_class(self)
|
|
135
145
|
end
|
|
136
146
|
end
|
|
137
147
|
|
|
138
148
|
private
|
|
139
149
|
def respond_to_missing?(method, _)
|
|
140
|
-
super ||
|
|
150
|
+
super || model.respond_to?(method)
|
|
141
151
|
end
|
|
142
152
|
end
|
|
143
153
|
end
|
|
@@ -141,14 +141,14 @@ 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!
|
|
148
|
-
elsif undesired.
|
|
149
|
-
raise ActiveRecord::SoleRecordExceeded.new(self)
|
|
150
|
-
else
|
|
148
|
+
elsif undesired.nil?
|
|
151
149
|
found
|
|
150
|
+
else
|
|
151
|
+
raise ActiveRecord::SoleRecordExceeded.new(self)
|
|
152
152
|
end
|
|
153
153
|
end
|
|
154
154
|
|
|
@@ -376,7 +376,7 @@ module ActiveRecord
|
|
|
376
376
|
|
|
377
377
|
skip_query_cache_if_necessary do
|
|
378
378
|
with_connection do |c|
|
|
379
|
-
c.select_rows(relation.arel, "#{name} Exists?").size == 1
|
|
379
|
+
c.select_rows(relation.arel, "#{model.name} Exists?").size == 1
|
|
380
380
|
end
|
|
381
381
|
end
|
|
382
382
|
end
|
|
@@ -389,7 +389,7 @@ module ActiveRecord
|
|
|
389
389
|
def include?(record)
|
|
390
390
|
# The existing implementation relies on receiving an Active Record instance as the input parameter named record.
|
|
391
391
|
# Any non-Active Record object passed to this implementation is guaranteed to return `false`.
|
|
392
|
-
return false unless record.is_a?(
|
|
392
|
+
return false unless record.is_a?(model)
|
|
393
393
|
|
|
394
394
|
if loaded? || offset_value || limit_value || having_clause.any?
|
|
395
395
|
records.include?(record)
|
|
@@ -415,9 +415,9 @@ module ActiveRecord
|
|
|
415
415
|
# the expected number of results should be provided in the +expected_size+
|
|
416
416
|
# argument.
|
|
417
417
|
def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
|
|
418
|
-
conditions = " [#{arel.where_sql(
|
|
418
|
+
conditions = " [#{arel.where_sql(model)}]" unless where_clause.empty?
|
|
419
419
|
|
|
420
|
-
name =
|
|
420
|
+
name = model.name
|
|
421
421
|
|
|
422
422
|
if ids.nil?
|
|
423
423
|
error = +"Couldn't find #{name}"
|
|
@@ -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
|
|
@@ -472,7 +472,7 @@ module ActiveRecord
|
|
|
472
472
|
)
|
|
473
473
|
)
|
|
474
474
|
relation = skip_query_cache_if_necessary do
|
|
475
|
-
|
|
475
|
+
model.with_connection do |c|
|
|
476
476
|
c.distinct_relation_for_primary_key(relation)
|
|
477
477
|
end
|
|
478
478
|
end
|
|
@@ -490,9 +490,9 @@ module ActiveRecord
|
|
|
490
490
|
end
|
|
491
491
|
|
|
492
492
|
def find_with_ids(*ids)
|
|
493
|
-
raise UnknownPrimaryKey.new(
|
|
493
|
+
raise UnknownPrimaryKey.new(model) if primary_key.nil?
|
|
494
494
|
|
|
495
|
-
expects_array = if
|
|
495
|
+
expects_array = if model.composite_primary_key?
|
|
496
496
|
ids.first.first.is_a?(Array)
|
|
497
497
|
else
|
|
498
498
|
ids.first.is_a?(Array)
|
|
@@ -504,7 +504,7 @@ module ActiveRecord
|
|
|
504
504
|
|
|
505
505
|
ids = ids.compact.uniq
|
|
506
506
|
|
|
507
|
-
model_name =
|
|
507
|
+
model_name = model.name
|
|
508
508
|
|
|
509
509
|
case ids.size
|
|
510
510
|
when 0
|
|
@@ -526,7 +526,7 @@ module ActiveRecord
|
|
|
526
526
|
MSG
|
|
527
527
|
end
|
|
528
528
|
|
|
529
|
-
relation = if
|
|
529
|
+
relation = if model.composite_primary_key?
|
|
530
530
|
where(primary_key.zip(id).to_h)
|
|
531
531
|
else
|
|
532
532
|
where(primary_key => id)
|
|
@@ -574,7 +574,7 @@ module ActiveRecord
|
|
|
574
574
|
result = relation.records
|
|
575
575
|
|
|
576
576
|
if result.size == ids.size
|
|
577
|
-
result.in_order_of(:id, ids.map { |id|
|
|
577
|
+
result.in_order_of(:id, ids.map { |id| model.type_for_attribute(primary_key).cast(id) })
|
|
578
578
|
else
|
|
579
579
|
raise_record_not_found_exception!(ids, result.size, ids.size)
|
|
580
580
|
end
|
|
@@ -639,24 +639,40 @@ module ActiveRecord
|
|
|
639
639
|
end
|
|
640
640
|
|
|
641
641
|
def ordered_relation
|
|
642
|
-
if order_values.empty?
|
|
643
|
-
|
|
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
|
-
|
|
669
|
+
columns = Array(model.implicit_order_column)
|
|
651
670
|
|
|
652
|
-
|
|
653
|
-
oc << query_constraints_list if query_constraints_list
|
|
671
|
+
return columns.compact if columns.length.positive? && columns.last.nil?
|
|
654
672
|
|
|
655
|
-
|
|
656
|
-
oc << primary_key
|
|
657
|
-
end
|
|
673
|
+
columns += Array(model.query_constraints_list || model.primary_key)
|
|
658
674
|
|
|
659
|
-
|
|
675
|
+
columns.uniq.compact
|
|
660
676
|
end
|
|
661
677
|
end
|
|
662
678
|
end
|
|
@@ -24,7 +24,7 @@ module ActiveRecord
|
|
|
24
24
|
# the values.
|
|
25
25
|
def other
|
|
26
26
|
other = Relation.create(
|
|
27
|
-
relation.
|
|
27
|
+
relation.model,
|
|
28
28
|
table: relation.table,
|
|
29
29
|
predicate_builder: relation.predicate_builder
|
|
30
30
|
)
|
|
@@ -84,7 +84,7 @@ module ActiveRecord
|
|
|
84
84
|
def merge_select_values
|
|
85
85
|
return if other.select_values.empty?
|
|
86
86
|
|
|
87
|
-
if other.
|
|
87
|
+
if other.model == relation.model
|
|
88
88
|
relation.select_values |= other.select_values
|
|
89
89
|
else
|
|
90
90
|
relation.select_values |= other.instance_eval do
|
|
@@ -96,12 +96,12 @@ module ActiveRecord
|
|
|
96
96
|
def merge_preloads
|
|
97
97
|
return if other.preload_values.empty? && other.includes_values.empty?
|
|
98
98
|
|
|
99
|
-
if other.
|
|
99
|
+
if other.model == relation.model
|
|
100
100
|
relation.preload_values |= other.preload_values unless other.preload_values.empty?
|
|
101
101
|
relation.includes_values |= other.includes_values unless other.includes_values.empty?
|
|
102
102
|
else
|
|
103
|
-
reflection = relation.
|
|
104
|
-
r.class_name == other.
|
|
103
|
+
reflection = relation.model.reflect_on_all_associations.find do |r|
|
|
104
|
+
r.class_name == other.model.name
|
|
105
105
|
end || return
|
|
106
106
|
|
|
107
107
|
unless other.preload_values.empty?
|
|
@@ -117,7 +117,7 @@ module ActiveRecord
|
|
|
117
117
|
def merge_joins
|
|
118
118
|
return if other.joins_values.empty?
|
|
119
119
|
|
|
120
|
-
if other.
|
|
120
|
+
if other.model == relation.model
|
|
121
121
|
relation.joins_values |= other.joins_values
|
|
122
122
|
else
|
|
123
123
|
associations, others = other.joins_values.partition do |join|
|
|
@@ -136,7 +136,7 @@ module ActiveRecord
|
|
|
136
136
|
def merge_outer_joins
|
|
137
137
|
return if other.left_outer_joins_values.empty?
|
|
138
138
|
|
|
139
|
-
if other.
|
|
139
|
+
if other.model == relation.model
|
|
140
140
|
relation.left_outer_joins_values |= other.left_outer_joins_values
|
|
141
141
|
else
|
|
142
142
|
associations, others = other.left_outer_joins_values.partition do |join|
|
|
@@ -185,7 +185,7 @@ module ActiveRecord
|
|
|
185
185
|
|
|
186
186
|
def replace_from_clause?
|
|
187
187
|
relation.from_clause.empty? && !other.from_clause.empty? &&
|
|
188
|
-
relation.
|
|
188
|
+
relation.model.base_class == other.model.base_class
|
|
189
189
|
end
|
|
190
190
|
end
|
|
191
191
|
end
|
|
@@ -31,7 +31,9 @@ module ActiveRecord
|
|
|
31
31
|
values_predicate
|
|
32
32
|
else
|
|
33
33
|
array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
|
|
34
|
-
|
|
34
|
+
values_predicate.or(
|
|
35
|
+
Arel::Nodes::Grouping.new Arel::Nodes::Or.new(array_predicates)
|
|
36
|
+
)
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
39
|
|
|
@@ -3,24 +3,24 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
class PredicateBuilder
|
|
5
5
|
class AssociationQueryValue # :nodoc:
|
|
6
|
-
def initialize(
|
|
7
|
-
@
|
|
6
|
+
def initialize(reflection, value)
|
|
7
|
+
@reflection = reflection
|
|
8
8
|
@value = value
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def queries
|
|
12
|
-
if
|
|
12
|
+
if reflection.join_foreign_key.is_a?(Array)
|
|
13
13
|
id_list = ids
|
|
14
14
|
id_list = id_list.pluck(primary_key) if id_list.is_a?(Relation)
|
|
15
15
|
|
|
16
|
-
id_list.map { |ids_set|
|
|
16
|
+
id_list.map { |ids_set| reflection.join_foreign_key.zip(ids_set).to_h }
|
|
17
17
|
else
|
|
18
|
-
[
|
|
18
|
+
[ reflection.join_foreign_key => ids ]
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
private
|
|
23
|
-
attr_reader :
|
|
23
|
+
attr_reader :reflection, :value
|
|
24
24
|
|
|
25
25
|
def ids
|
|
26
26
|
case value
|
|
@@ -37,15 +37,15 @@ module ActiveRecord
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def primary_key
|
|
40
|
-
|
|
40
|
+
reflection.join_primary_key
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def primary_type
|
|
44
|
-
|
|
44
|
+
reflection.join_primary_type
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def polymorphic_name
|
|
48
|
-
|
|
48
|
+
reflection.polymorphic_name
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def select_clause?
|
|
@@ -3,24 +3,24 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
class PredicateBuilder
|
|
5
5
|
class PolymorphicArrayValue # :nodoc:
|
|
6
|
-
def initialize(
|
|
7
|
-
@
|
|
6
|
+
def initialize(reflection, values)
|
|
7
|
+
@reflection = reflection
|
|
8
8
|
@values = values
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def queries
|
|
12
|
-
return [
|
|
12
|
+
return [ reflection.join_foreign_key => values ] if values.empty?
|
|
13
13
|
|
|
14
14
|
type_to_ids_mapping.map do |type, ids|
|
|
15
15
|
query = {}
|
|
16
|
-
query[
|
|
17
|
-
query[
|
|
16
|
+
query[reflection.join_foreign_type] = type if type
|
|
17
|
+
query[reflection.join_foreign_key] = ids
|
|
18
18
|
query
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
private
|
|
23
|
-
attr_reader :
|
|
23
|
+
attr_reader :reflection, :values
|
|
24
24
|
|
|
25
25
|
def type_to_ids_mapping
|
|
26
26
|
default_hash = Hash.new { |hsh, key| hsh[key] = [] }
|
|
@@ -30,14 +30,14 @@ module ActiveRecord
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def primary_key(value)
|
|
33
|
-
|
|
33
|
+
reflection.join_primary_key(klass(value))
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def klass(value)
|
|
37
37
|
if value.is_a?(Base)
|
|
38
38
|
value.class
|
|
39
39
|
elsif value.is_a?(Relation)
|
|
40
|
-
value.
|
|
40
|
+
value.model
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
@@ -9,10 +9,11 @@ module ActiveRecord
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
if value.select_values.empty?
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
model = value.model
|
|
13
|
+
if model.composite_primary_key?
|
|
14
|
+
raise ArgumentError, "Cannot map composite primary key #{model.primary_key} to #{attribute.name}"
|
|
14
15
|
else
|
|
15
|
-
value = value.select(value.table[
|
|
16
|
+
value = value.select(value.table[model.primary_key])
|
|
16
17
|
end
|
|
17
18
|
end
|
|
18
19
|
|
|
@@ -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|
|
|
@@ -72,11 +72,24 @@ module ActiveRecord
|
|
|
72
72
|
table.associated_table(table_name, &block).arel_table[column_name]
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
def with(table)
|
|
76
|
+
other = dup
|
|
77
|
+
other.table = table
|
|
78
|
+
other
|
|
79
|
+
end
|
|
80
|
+
|
|
75
81
|
protected
|
|
82
|
+
attr_writer :table
|
|
83
|
+
|
|
76
84
|
def expand_from_hash(attributes, &block)
|
|
77
|
-
return ["1=0"] if attributes.empty?
|
|
85
|
+
return [Arel.sql("1=0", retryable: true)] if attributes.empty?
|
|
78
86
|
|
|
79
87
|
attributes.flat_map do |key, value|
|
|
88
|
+
if key.is_a?(Array) && key.size == 1
|
|
89
|
+
key = key.first
|
|
90
|
+
value = value.flatten
|
|
91
|
+
end
|
|
92
|
+
|
|
80
93
|
if key.is_a?(Array)
|
|
81
94
|
queries = Array(value).map do |ids_set|
|
|
82
95
|
raise ArgumentError, "Expected corresponding value for #{key} to be an Array" unless ids_set.is_a?(Array)
|
|
@@ -86,24 +99,26 @@ module ActiveRecord
|
|
|
86
99
|
elsif value.is_a?(Hash) && !table.has_column?(key)
|
|
87
100
|
table.associated_table(key, &block)
|
|
88
101
|
.predicate_builder.expand_from_hash(value.stringify_keys)
|
|
89
|
-
elsif table.associated_with
|
|
102
|
+
elsif (associated_reflection = table.associated_with(key))
|
|
90
103
|
# Find the foreign key when using queries such as:
|
|
91
104
|
# Post.where(author: author)
|
|
92
105
|
#
|
|
93
106
|
# For polymorphic relationships, find the foreign key and type:
|
|
94
107
|
# PriceEstimate.where(estimate_of: treasure)
|
|
95
|
-
|
|
96
|
-
if
|
|
108
|
+
|
|
109
|
+
if associated_reflection.polymorphic?
|
|
97
110
|
value = [value] unless value.is_a?(Array)
|
|
98
111
|
klass = PolymorphicArrayValue
|
|
99
|
-
elsif
|
|
112
|
+
elsif associated_reflection.through_reflection?
|
|
113
|
+
associated_table = table.associated_table(key)
|
|
114
|
+
|
|
100
115
|
next associated_table.predicate_builder.expand_from_hash(
|
|
101
116
|
associated_table.primary_key => value
|
|
102
117
|
)
|
|
103
118
|
end
|
|
104
119
|
|
|
105
120
|
klass ||= AssociationQueryValue
|
|
106
|
-
queries = klass.new(
|
|
121
|
+
queries = klass.new(associated_reflection, value).queries.map! do |query|
|
|
107
122
|
# If the query produced is identical to attributes don't go any deeper.
|
|
108
123
|
# Prevents stack level too deep errors when association and foreign_key are identical.
|
|
109
124
|
query == attributes ? self[key, value] : expand_from_hash(query)
|
|
@@ -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
|
|