activerecord 7.1.5.2 → 8.1.2.1
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 +567 -2443
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +3 -2
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +18 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +36 -5
- 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 +268 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +8 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +250 -315
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +20 -27
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +96 -68
- data/lib/active_record/autosave_association.rb +93 -58
- data/lib/active_record/base.rb +5 -7
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- 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 +79 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- 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/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
- 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 +44 -41
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
- data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +66 -0
- data/lib/active_record/connection_handling.rb +88 -46
- data/lib/active_record/core.rb +142 -58
- data/lib/active_record/counter_cache.rb +52 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +28 -5
- data/lib/active_record/database_configurations/hash_config.rb +103 -41
- data/lib/active_record/database_configurations/url_config.rb +30 -1
- data/lib/active_record/database_configurations.rb +8 -4
- data/lib/active_record/delegated_type.rb +43 -19
- data/lib/active_record/dynamic_matchers.rb +56 -71
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +12 -12
- data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
- data/lib/active_record/encryption/encryptor.rb +58 -22
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +57 -36
- data/lib/active_record/errors.rb +76 -26
- data/lib/active_record/explain.rb +14 -25
- data/lib/active_record/explain_registry.rb +51 -2
- 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 +39 -35
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +5 -3
- data/lib/active_record/insert_all.rb +28 -20
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +15 -7
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -34
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +46 -14
- data/lib/active_record/migration/compatibility.rb +40 -25
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +119 -103
- data/lib/active_record/model_schema.rb +66 -78
- data/lib/active_record/nested_attributes.rb +17 -9
- data/lib/active_record/persistence.rb +161 -454
- data/lib/active_record/query_cache.rb +33 -15
- data/lib/active_record/query_logs.rb +106 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +28 -16
- data/lib/active_record/railtie.rb +61 -74
- data/lib/active_record/railties/controller_runtime.rb +19 -5
- data/lib/active_record/railties/databases.rake +64 -78
- 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 +147 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +153 -73
- data/lib/active_record/relation/calculations.rb +146 -96
- data/lib/active_record/relation/delegation.rb +30 -23
- data/lib/active_record/relation/finder_methods.rb +64 -34
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
- 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 +25 -10
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +351 -124
- data/lib/active_record/relation/spawn_methods.rb +9 -25
- data/lib/active_record/relation/where_clause.rb +17 -30
- data/lib/active_record/relation.rb +590 -124
- data/lib/active_record/result.rb +131 -59
- data/lib/active_record/runtime_registry.rb +44 -21
- data/lib/active_record/sanitization.rb +33 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +66 -31
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +68 -20
- data/lib/active_record/statement_cache.rb +29 -25
- 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 +7 -33
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +118 -90
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +123 -89
- data/lib/active_record/testing/query_assertions.rb +127 -0
- data/lib/active_record/timestamp.rb +6 -4
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +129 -0
- data/lib/active_record/transactions.rb +97 -18
- data/lib/active_record/translation.rb +0 -2
- 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 +12 -7
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +5 -4
- data/lib/active_record/validations/associated.rb +10 -4
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +203 -59
- data/lib/arel/alias_predication.rb +3 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- 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/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +6 -5
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -4
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +14 -5
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -3
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +56 -12
- data/lib/arel/visitors/sqlite.rb +80 -8
- data/lib/arel/visitors/to_sql.rb +34 -37
- data/lib/arel.rb +10 -4
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +23 -14
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -167
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
|
@@ -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
|
|
@@ -87,6 +87,14 @@ module ActiveRecord
|
|
|
87
87
|
#
|
|
88
88
|
# Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
|
|
89
89
|
# # returns an Array of the required fields.
|
|
90
|
+
#
|
|
91
|
+
# ==== Edge Cases
|
|
92
|
+
#
|
|
93
|
+
# Person.find(37) # raises ActiveRecord::RecordNotFound exception if the record with the given ID does not exist.
|
|
94
|
+
# Person.find([37]) # raises ActiveRecord::RecordNotFound exception if the record with the given ID in the input array does not exist.
|
|
95
|
+
# Person.find(nil) # raises ActiveRecord::RecordNotFound exception if the argument is nil.
|
|
96
|
+
# Person.find([]) # returns an empty array if the argument is an empty array.
|
|
97
|
+
# Person.find # raises ActiveRecord::RecordNotFound exception if the argument is not provided.
|
|
90
98
|
def find(*args)
|
|
91
99
|
return super if block_given?
|
|
92
100
|
find_with_ids(*args)
|
|
@@ -133,14 +141,14 @@ module ActiveRecord
|
|
|
133
141
|
#
|
|
134
142
|
# Product.where(["price = %?", price]).sole
|
|
135
143
|
def sole
|
|
136
|
-
found, undesired =
|
|
144
|
+
found, undesired = take(2)
|
|
137
145
|
|
|
138
146
|
if found.nil?
|
|
139
147
|
raise_record_not_found_exception!
|
|
140
|
-
elsif undesired.
|
|
141
|
-
raise ActiveRecord::SoleRecordExceeded.new(self)
|
|
142
|
-
else
|
|
148
|
+
elsif undesired.nil?
|
|
143
149
|
found
|
|
150
|
+
else
|
|
151
|
+
raise ActiveRecord::SoleRecordExceeded.new(self)
|
|
144
152
|
end
|
|
145
153
|
end
|
|
146
154
|
|
|
@@ -366,7 +374,11 @@ module ActiveRecord
|
|
|
366
374
|
relation = construct_relation_for_exists(conditions)
|
|
367
375
|
return false if relation.where_clause.contradiction?
|
|
368
376
|
|
|
369
|
-
skip_query_cache_if_necessary
|
|
377
|
+
skip_query_cache_if_necessary do
|
|
378
|
+
with_connection do |c|
|
|
379
|
+
c.select_rows(relation.arel, "#{model.name} Exists?").size == 1
|
|
380
|
+
end
|
|
381
|
+
end
|
|
370
382
|
end
|
|
371
383
|
|
|
372
384
|
# Returns true if the relation contains the given record or false otherwise.
|
|
@@ -377,7 +389,7 @@ module ActiveRecord
|
|
|
377
389
|
def include?(record)
|
|
378
390
|
# The existing implementation relies on receiving an Active Record instance as the input parameter named record.
|
|
379
391
|
# Any non-Active Record object passed to this implementation is guaranteed to return `false`.
|
|
380
|
-
return false unless record.is_a?(
|
|
392
|
+
return false unless record.is_a?(model)
|
|
381
393
|
|
|
382
394
|
if loaded? || offset_value || limit_value || having_clause.any?
|
|
383
395
|
records.include?(record)
|
|
@@ -403,9 +415,9 @@ module ActiveRecord
|
|
|
403
415
|
# the expected number of results should be provided in the +expected_size+
|
|
404
416
|
# argument.
|
|
405
417
|
def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
|
|
406
|
-
conditions = " [#{arel.where_sql(
|
|
418
|
+
conditions = " [#{arel.where_sql(model)}]" unless where_clause.empty?
|
|
407
419
|
|
|
408
|
-
name =
|
|
420
|
+
name = model.name
|
|
409
421
|
|
|
410
422
|
if ids.nil?
|
|
411
423
|
error = +"Couldn't find #{name}"
|
|
@@ -430,7 +442,7 @@ module ActiveRecord
|
|
|
430
442
|
if distinct_value && offset_value
|
|
431
443
|
relation = except(:order).limit!(1)
|
|
432
444
|
else
|
|
433
|
-
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)
|
|
434
446
|
end
|
|
435
447
|
|
|
436
448
|
case conditions
|
|
@@ -460,7 +472,9 @@ module ActiveRecord
|
|
|
460
472
|
)
|
|
461
473
|
)
|
|
462
474
|
relation = skip_query_cache_if_necessary do
|
|
463
|
-
|
|
475
|
+
model.with_connection do |c|
|
|
476
|
+
c.distinct_relation_for_primary_key(relation)
|
|
477
|
+
end
|
|
464
478
|
end
|
|
465
479
|
end
|
|
466
480
|
|
|
@@ -476,9 +490,9 @@ module ActiveRecord
|
|
|
476
490
|
end
|
|
477
491
|
|
|
478
492
|
def find_with_ids(*ids)
|
|
479
|
-
raise UnknownPrimaryKey.new(
|
|
493
|
+
raise UnknownPrimaryKey.new(model) if primary_key.nil?
|
|
480
494
|
|
|
481
|
-
expects_array = if
|
|
495
|
+
expects_array = if model.composite_primary_key?
|
|
482
496
|
ids.first.first.is_a?(Array)
|
|
483
497
|
else
|
|
484
498
|
ids.first.is_a?(Array)
|
|
@@ -490,7 +504,7 @@ module ActiveRecord
|
|
|
490
504
|
|
|
491
505
|
ids = ids.compact.uniq
|
|
492
506
|
|
|
493
|
-
model_name =
|
|
507
|
+
model_name = model.name
|
|
494
508
|
|
|
495
509
|
case ids.size
|
|
496
510
|
when 0
|
|
@@ -512,7 +526,7 @@ module ActiveRecord
|
|
|
512
526
|
MSG
|
|
513
527
|
end
|
|
514
528
|
|
|
515
|
-
relation = if
|
|
529
|
+
relation = if model.composite_primary_key?
|
|
516
530
|
where(primary_key.zip(id).to_h)
|
|
517
531
|
else
|
|
518
532
|
where(primary_key => id)
|
|
@@ -560,7 +574,7 @@ module ActiveRecord
|
|
|
560
574
|
result = relation.records
|
|
561
575
|
|
|
562
576
|
if result.size == ids.size
|
|
563
|
-
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) })
|
|
564
578
|
else
|
|
565
579
|
raise_record_not_found_exception!(ids, result.size, ids.size)
|
|
566
580
|
end
|
|
@@ -625,24 +639,40 @@ module ActiveRecord
|
|
|
625
639
|
end
|
|
626
640
|
|
|
627
641
|
def ordered_relation
|
|
628
|
-
if order_values.empty?
|
|
629
|
-
|
|
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
|
|
630
663
|
else
|
|
631
664
|
self
|
|
632
665
|
end
|
|
633
666
|
end
|
|
634
667
|
|
|
635
668
|
def _order_columns
|
|
636
|
-
|
|
669
|
+
columns = Array(model.implicit_order_column)
|
|
637
670
|
|
|
638
|
-
|
|
639
|
-
oc << query_constraints_list if query_constraints_list
|
|
671
|
+
return columns.compact if columns.length.positive? && columns.last.nil?
|
|
640
672
|
|
|
641
|
-
|
|
642
|
-
oc << primary_key
|
|
643
|
-
end
|
|
673
|
+
columns += Array(model.query_constraints_list || model.primary_key)
|
|
644
674
|
|
|
645
|
-
|
|
675
|
+
columns.uniq.compact
|
|
646
676
|
end
|
|
647
677
|
end
|
|
648
678
|
end
|
|
@@ -7,16 +7,15 @@ module ActiveRecord
|
|
|
7
7
|
class HashMerger # :nodoc:
|
|
8
8
|
attr_reader :relation, :hash
|
|
9
9
|
|
|
10
|
-
def initialize(relation, hash
|
|
10
|
+
def initialize(relation, hash)
|
|
11
11
|
hash.assert_valid_keys(*Relation::VALUE_METHODS)
|
|
12
12
|
|
|
13
13
|
@relation = relation
|
|
14
14
|
@hash = hash
|
|
15
|
-
@rewhere = rewhere
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
def merge
|
|
19
|
-
Merger.new(relation, other
|
|
18
|
+
Merger.new(relation, other).merge
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
# Applying values to a relation has some side effects. E.g.
|
|
@@ -25,7 +24,7 @@ module ActiveRecord
|
|
|
25
24
|
# the values.
|
|
26
25
|
def other
|
|
27
26
|
other = Relation.create(
|
|
28
|
-
relation.
|
|
27
|
+
relation.model,
|
|
29
28
|
table: relation.table,
|
|
30
29
|
predicate_builder: relation.predicate_builder
|
|
31
30
|
)
|
|
@@ -44,11 +43,10 @@ module ActiveRecord
|
|
|
44
43
|
class Merger # :nodoc:
|
|
45
44
|
attr_reader :relation, :values, :other
|
|
46
45
|
|
|
47
|
-
def initialize(relation, other
|
|
46
|
+
def initialize(relation, other)
|
|
48
47
|
@relation = relation
|
|
49
48
|
@values = other.values
|
|
50
49
|
@other = other
|
|
51
|
-
@rewhere = rewhere
|
|
52
50
|
end
|
|
53
51
|
|
|
54
52
|
NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS -
|
|
@@ -86,7 +84,7 @@ module ActiveRecord
|
|
|
86
84
|
def merge_select_values
|
|
87
85
|
return if other.select_values.empty?
|
|
88
86
|
|
|
89
|
-
if other.
|
|
87
|
+
if other.model == relation.model
|
|
90
88
|
relation.select_values |= other.select_values
|
|
91
89
|
else
|
|
92
90
|
relation.select_values |= other.instance_eval do
|
|
@@ -98,12 +96,12 @@ module ActiveRecord
|
|
|
98
96
|
def merge_preloads
|
|
99
97
|
return if other.preload_values.empty? && other.includes_values.empty?
|
|
100
98
|
|
|
101
|
-
if other.
|
|
99
|
+
if other.model == relation.model
|
|
102
100
|
relation.preload_values |= other.preload_values unless other.preload_values.empty?
|
|
103
101
|
relation.includes_values |= other.includes_values unless other.includes_values.empty?
|
|
104
102
|
else
|
|
105
|
-
reflection = relation.
|
|
106
|
-
r.class_name == other.
|
|
103
|
+
reflection = relation.model.reflect_on_all_associations.find do |r|
|
|
104
|
+
r.class_name == other.model.name
|
|
107
105
|
end || return
|
|
108
106
|
|
|
109
107
|
unless other.preload_values.empty?
|
|
@@ -119,7 +117,7 @@ module ActiveRecord
|
|
|
119
117
|
def merge_joins
|
|
120
118
|
return if other.joins_values.empty?
|
|
121
119
|
|
|
122
|
-
if other.
|
|
120
|
+
if other.model == relation.model
|
|
123
121
|
relation.joins_values |= other.joins_values
|
|
124
122
|
else
|
|
125
123
|
associations, others = other.joins_values.partition do |join|
|
|
@@ -138,7 +136,7 @@ module ActiveRecord
|
|
|
138
136
|
def merge_outer_joins
|
|
139
137
|
return if other.left_outer_joins_values.empty?
|
|
140
138
|
|
|
141
|
-
if other.
|
|
139
|
+
if other.model == relation.model
|
|
142
140
|
relation.left_outer_joins_values |= other.left_outer_joins_values
|
|
143
141
|
else
|
|
144
142
|
associations, others = other.left_outer_joins_values.partition do |join|
|
|
@@ -178,7 +176,7 @@ module ActiveRecord
|
|
|
178
176
|
def merge_clauses
|
|
179
177
|
relation.from_clause = other.from_clause if replace_from_clause?
|
|
180
178
|
|
|
181
|
-
where_clause = relation.where_clause.merge(other.where_clause
|
|
179
|
+
where_clause = relation.where_clause.merge(other.where_clause)
|
|
182
180
|
relation.where_clause = where_clause unless where_clause.empty?
|
|
183
181
|
|
|
184
182
|
having_clause = relation.having_clause.merge(other.having_clause)
|
|
@@ -187,7 +185,7 @@ module ActiveRecord
|
|
|
187
185
|
|
|
188
186
|
def replace_from_clause?
|
|
189
187
|
relation.from_clause.empty? && !other.from_clause.empty? &&
|
|
190
|
-
relation.
|
|
188
|
+
relation.model.base_class == other.model.base_class
|
|
191
189
|
end
|
|
192
190
|
end
|
|
193
191
|
end
|
|
@@ -13,7 +13,7 @@ module ActiveRecord
|
|
|
13
13
|
return attribute.in([]) if value.empty?
|
|
14
14
|
|
|
15
15
|
values = value.map { |x| x.is_a?(Base) ? x.id : x }
|
|
16
|
-
nils = values.
|
|
16
|
+
nils = values.compact!
|
|
17
17
|
ranges = values.extract! { |v| v.is_a?(Range) }
|
|
18
18
|
|
|
19
19
|
values_predicate =
|
|
@@ -23,7 +23,7 @@ module ActiveRecord
|
|
|
23
23
|
else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
if nils
|
|
27
27
|
values_predicate = values_predicate.or(attribute.eq(nil))
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -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?
|
|
@@ -57,9 +57,17 @@ module ActiveRecord
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def convert_to_id(value)
|
|
60
|
-
|
|
60
|
+
if primary_key.is_a?(Array)
|
|
61
|
+
primary_key.map do |attribute|
|
|
62
|
+
next nil if value.nil?
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
if attribute == "id"
|
|
65
|
+
value.id_value
|
|
66
|
+
else
|
|
67
|
+
value.public_send(attribute)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
elsif value.respond_to?(primary_key)
|
|
63
71
|
value.public_send(primary_key)
|
|
64
72
|
else
|
|
65
73
|
value
|
|
@@ -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
|
|
|
@@ -28,16 +28,16 @@ module ActiveRecord
|
|
|
28
28
|
def self.references(attributes)
|
|
29
29
|
attributes.each_with_object([]) do |(key, value), result|
|
|
30
30
|
if value.is_a?(Hash)
|
|
31
|
-
result << Arel.sql(key)
|
|
31
|
+
result << Arel.sql(key, retryable: true)
|
|
32
32
|
elsif (idx = key.rindex("."))
|
|
33
|
-
result << Arel.sql(key[0, idx])
|
|
33
|
+
result << Arel.sql(key[0, idx], retryable: true)
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
end
|
|
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)
|
|
@@ -142,7 +157,7 @@ module ActiveRecord
|
|
|
142
157
|
queries.first
|
|
143
158
|
else
|
|
144
159
|
queries.map! { |query| query.reduce(&:and) }
|
|
145
|
-
queries =
|
|
160
|
+
queries = Arel::Nodes::Or.new(queries)
|
|
146
161
|
Arel::Nodes::Grouping.new(queries)
|
|
147
162
|
end
|
|
148
163
|
end
|
|
@@ -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
|
|
|
@@ -35,7 +37,7 @@ module ActiveRecord
|
|
|
35
37
|
def nil?
|
|
36
38
|
unless value_before_type_cast.is_a?(StatementCache::Substitute)
|
|
37
39
|
value_before_type_cast.nil? ||
|
|
38
|
-
type.respond_to?(:subtype) && serializable? && value_for_database.nil?
|
|
40
|
+
(type.respond_to?(:subtype) || type.respond_to?(:normalizer)) && serializable? && value_for_database.nil?
|
|
39
41
|
end
|
|
40
42
|
end
|
|
41
43
|
|