activerecord 8.0.0 → 8.1.4
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 +1381 -241
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +35 -17
- data/lib/active_record/associations/association_scope.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +20 -4
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +36 -8
- 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 +9 -5
- data/lib/active_record/associations/collection_proxy.rb +26 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/foreign_association.rb +3 -1
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +5 -4
- data/lib/active_record/associations/preloader/association.rb +1 -1
- 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.rb +159 -21
- data/lib/active_record/attribute_methods/primary_key.rb +2 -1
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +19 -6
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attribute_methods.rb +24 -17
- data/lib/active_record/attributes.rb +40 -26
- data/lib/active_record/autosave_association.rb +22 -12
- data/lib/active_record/base.rb +3 -4
- data/lib/active_record/callbacks.rb +1 -2
- data/lib/active_record/coders/column_serializer.rb +1 -1
- data/lib/active_record/coders/json.rb +16 -6
- data/lib/active_record/coders/yaml_column.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +19 -18
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +474 -112
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +68 -41
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -9
- 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 +31 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +94 -28
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -64
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +133 -40
- data/lib/active_record/connection_adapters/column.rb +14 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
- 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 +40 -25
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +66 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
- 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 +29 -33
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
- 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 +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +70 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +99 -54
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +40 -12
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -23
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +15 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +7 -14
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +68 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +8 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +16 -2
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +15 -10
- data/lib/active_record/core.rb +44 -12
- data/lib/active_record/counter_cache.rb +36 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +66 -9
- data/lib/active_record/database_configurations/url_config.rb +15 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +19 -19
- data/lib/active_record/disable_joins_association_relation.rb +5 -1
- 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/encrypted_fixtures.rb +12 -7
- data/lib/active_record/encryption/encryptor.rb +39 -25
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +37 -20
- data/lib/active_record/errors.rb +23 -7
- 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 +76 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/future_result.rb +5 -3
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +9 -4
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +30 -22
- data/lib/active_record/message_pack.rb +13 -0
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +32 -6
- 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 +38 -12
- data/lib/active_record/nested_attributes.rb +10 -6
- data/lib/active_record/persistence.rb +35 -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 +17 -5
- data/lib/active_record/railtie.rb +35 -6
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +24 -20
- 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 +36 -1
- data/lib/active_record/relation/batches.rb +25 -11
- data/lib/active_record/relation/calculations.rb +62 -41
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +76 -45
- data/lib/active_record/relation/merger.rb +4 -2
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +10 -8
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +106 -45
- data/lib/active_record/relation/spawn_methods.rb +6 -6
- data/lib/active_record/relation/where_clause.rb +10 -11
- data/lib/active_record/relation.rb +63 -31
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +41 -21
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +48 -19
- data/lib/active_record/statement_cache.rb +16 -12
- data/lib/active_record/store.rb +46 -19
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/abstract_tasks.rb +83 -0
- data/lib/active_record/tasks/database_tasks.rb +44 -45
- data/lib/active_record/tasks/mysql_database_tasks.rb +4 -56
- 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 +18 -4
- data/lib/active_record/test_fixtures.rb +40 -6
- 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 +58 -32
- 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.rb +72 -7
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- 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 +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- 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 +7 -23
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +16 -15
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
|
@@ -60,37 +60,37 @@ module ActiveRecord
|
|
|
60
60
|
# Person.distinct.count(:age)
|
|
61
61
|
# # => counts the number of different age values
|
|
62
62
|
#
|
|
63
|
-
# If
|
|
63
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group],
|
|
64
64
|
# it returns a Hash whose keys represent the aggregated column,
|
|
65
65
|
# and the values are the respective amounts:
|
|
66
66
|
#
|
|
67
67
|
# Person.group(:city).count
|
|
68
68
|
# # => { 'Rome' => 5, 'Paris' => 3 }
|
|
69
69
|
#
|
|
70
|
-
# If
|
|
70
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
|
|
71
71
|
# keys are an array containing the individual values of each column and the value
|
|
72
|
-
# of each key would be the
|
|
72
|
+
# of each key would be the count.
|
|
73
73
|
#
|
|
74
74
|
# Article.group(:status, :category).count
|
|
75
75
|
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
|
|
76
76
|
#
|
|
77
|
-
# If
|
|
77
|
+
# If +count+ is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
|
78
78
|
#
|
|
79
79
|
# Person.select(:age).count
|
|
80
80
|
# # => counts the number of different age values
|
|
81
81
|
#
|
|
82
|
-
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid
|
|
82
|
+
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid +count+ expressions. The specifics differ
|
|
83
83
|
# between databases. In invalid cases, an error from the database is thrown.
|
|
84
84
|
#
|
|
85
|
-
# When given a block,
|
|
86
|
-
#
|
|
87
|
-
# Returns the number of records for which the block returns a truthy value.
|
|
85
|
+
# When given a block, calls the block with each record in the relation and
|
|
86
|
+
# returns the number of records for which the block returns a truthy value.
|
|
88
87
|
#
|
|
89
88
|
# Person.count { |person| person.age > 21 }
|
|
90
89
|
# # => counts the number of people older that 21
|
|
91
90
|
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
91
|
+
# If the relation hasn't been loaded yet, calling +count+ with a block will
|
|
92
|
+
# load all records in the relation. If there are a lot of records in the
|
|
93
|
+
# relation, loading all records could result in performance issues.
|
|
94
94
|
def count(column_name = nil)
|
|
95
95
|
if block_given?
|
|
96
96
|
unless column_name.nil?
|
|
@@ -159,16 +159,15 @@ module ActiveRecord
|
|
|
159
159
|
#
|
|
160
160
|
# Person.sum(:age) # => 4562
|
|
161
161
|
#
|
|
162
|
-
# When given a block,
|
|
163
|
-
#
|
|
164
|
-
# Returns the sum of +initial_value_or_column+ and the block return
|
|
165
|
-
# values:
|
|
162
|
+
# When given a block, calls the block with each record in the relation and
|
|
163
|
+
# returns the sum of +initial_value_or_column+ plus the block return values:
|
|
166
164
|
#
|
|
167
165
|
# Person.sum { |person| person.age } # => 4562
|
|
168
166
|
# Person.sum(1000) { |person| person.age } # => 5562
|
|
169
167
|
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
168
|
+
# If the relation hasn't been loaded yet, calling +sum+ with a block will
|
|
169
|
+
# load all records in the relation. If there are a lot of records in the
|
|
170
|
+
# relation, loading all records could result in performance issues.
|
|
172
171
|
def sum(initial_value_or_column = 0, &block)
|
|
173
172
|
if block_given?
|
|
174
173
|
map(&block).sum(initial_value_or_column)
|
|
@@ -287,6 +286,11 @@ module ActiveRecord
|
|
|
287
286
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
|
288
287
|
# # => ['0', '27761', '173']
|
|
289
288
|
#
|
|
289
|
+
# Be aware that #pluck ignores any previous select clauses
|
|
290
|
+
#
|
|
291
|
+
# Person.select(:name).pluck(:id)
|
|
292
|
+
# # SELECT people.id FROM people
|
|
293
|
+
#
|
|
290
294
|
# See also #ids.
|
|
291
295
|
def pluck(*column_names)
|
|
292
296
|
if @none
|
|
@@ -315,7 +319,7 @@ module ActiveRecord
|
|
|
315
319
|
columns = relation.arel_columns(column_names)
|
|
316
320
|
relation.select_values = columns
|
|
317
321
|
result = skip_query_cache_if_necessary do
|
|
318
|
-
if where_clause.contradiction?
|
|
322
|
+
if where_clause.contradiction? && !possible_aggregation?(column_names)
|
|
319
323
|
ActiveRecord::Result.empty(async: @async)
|
|
320
324
|
else
|
|
321
325
|
model.with_connection do |c|
|
|
@@ -392,7 +396,7 @@ module ActiveRecord
|
|
|
392
396
|
relation.select_values = columns
|
|
393
397
|
|
|
394
398
|
result = if relation.where_clause.contradiction?
|
|
395
|
-
ActiveRecord::Result.empty
|
|
399
|
+
ActiveRecord::Result.empty(async: @async)
|
|
396
400
|
else
|
|
397
401
|
skip_query_cache_if_necessary do
|
|
398
402
|
model.with_connection do |c|
|
|
@@ -410,6 +414,18 @@ module ActiveRecord
|
|
|
410
414
|
async.ids
|
|
411
415
|
end
|
|
412
416
|
|
|
417
|
+
protected
|
|
418
|
+
def aggregate_column(column_name)
|
|
419
|
+
case column_name
|
|
420
|
+
when Arel::Expressions
|
|
421
|
+
column_name
|
|
422
|
+
when :all
|
|
423
|
+
Arel.star
|
|
424
|
+
else
|
|
425
|
+
arel_column(column_name.to_s)
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
413
429
|
private
|
|
414
430
|
def all_attributes?(column_names)
|
|
415
431
|
(column_names.map(&:to_s) - model.attribute_names - model.attribute_aliases.keys).empty?
|
|
@@ -450,14 +466,13 @@ module ActiveRecord
|
|
|
450
466
|
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
|
451
467
|
end
|
|
452
468
|
|
|
453
|
-
def
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
arel_column(column_name)
|
|
469
|
+
def possible_aggregation?(column_names)
|
|
470
|
+
column_names.all? do |column_name|
|
|
471
|
+
if column_name.is_a?(String)
|
|
472
|
+
column_name.include?("(")
|
|
473
|
+
else
|
|
474
|
+
Arel.arel_node?(column_name)
|
|
475
|
+
end
|
|
461
476
|
end
|
|
462
477
|
end
|
|
463
478
|
|
|
@@ -476,9 +491,9 @@ module ActiveRecord
|
|
|
476
491
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
|
477
492
|
relation = unscope(:order).distinct!(false)
|
|
478
493
|
|
|
479
|
-
column = aggregate_column(column_name)
|
|
494
|
+
column = relation.aggregate_column(column_name)
|
|
480
495
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
481
|
-
select_value.distinct = true if operation == "sum"
|
|
496
|
+
select_value.distinct = true if distinct && (operation == "sum" || operation == "average")
|
|
482
497
|
|
|
483
498
|
relation.select_values = [select_value]
|
|
484
499
|
|
|
@@ -486,7 +501,11 @@ module ActiveRecord
|
|
|
486
501
|
end
|
|
487
502
|
|
|
488
503
|
query_result = if relation.where_clause.contradiction?
|
|
489
|
-
|
|
504
|
+
if @async
|
|
505
|
+
FutureResult.wrap(ActiveRecord::Result.empty)
|
|
506
|
+
else
|
|
507
|
+
ActiveRecord::Result.empty
|
|
508
|
+
end
|
|
490
509
|
else
|
|
491
510
|
skip_query_cache_if_necessary do
|
|
492
511
|
model.with_connection do |c|
|
|
@@ -508,14 +527,15 @@ module ActiveRecord
|
|
|
508
527
|
|
|
509
528
|
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
|
510
529
|
group_fields = group_values
|
|
511
|
-
group_fields = group_fields.uniq if group_fields.size > 1
|
|
512
530
|
|
|
513
531
|
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
|
514
532
|
association = model._reflect_on_association(group_fields.first)
|
|
515
533
|
associated = association && association.belongs_to? # only count belongs_to associations
|
|
516
534
|
group_fields = Array(association.foreign_key) if associated
|
|
517
535
|
end
|
|
518
|
-
|
|
536
|
+
|
|
537
|
+
relation = except(:group).distinct!(false)
|
|
538
|
+
group_fields = relation.arel_columns(group_fields)
|
|
519
539
|
|
|
520
540
|
model.with_connection do |connection|
|
|
521
541
|
column_alias_tracker = ColumnAliasTracker.new(connection)
|
|
@@ -526,10 +546,11 @@ module ActiveRecord
|
|
|
526
546
|
}
|
|
527
547
|
group_columns = group_aliases.zip(group_fields)
|
|
528
548
|
|
|
529
|
-
column = aggregate_column(column_name)
|
|
549
|
+
column = relation.aggregate_column(column_name)
|
|
530
550
|
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
|
531
551
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
532
|
-
select_value.
|
|
552
|
+
select_value.distinct = true if distinct && (operation == "sum" || operation == "average")
|
|
553
|
+
select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))
|
|
533
554
|
|
|
534
555
|
select_values = [select_value]
|
|
535
556
|
select_values += self.select_values unless having_clause.empty?
|
|
@@ -543,7 +564,6 @@ module ActiveRecord
|
|
|
543
564
|
end
|
|
544
565
|
}
|
|
545
566
|
|
|
546
|
-
relation = except(:group).distinct!(false)
|
|
547
567
|
relation.group_values = group_fields
|
|
548
568
|
relation.select_values = select_values
|
|
549
569
|
|
|
@@ -553,7 +573,11 @@ module ActiveRecord
|
|
|
553
573
|
|
|
554
574
|
result.then do |calculated_data|
|
|
555
575
|
if association
|
|
556
|
-
key_ids
|
|
576
|
+
key_ids = if association.klass.base_class.composite_primary_key?
|
|
577
|
+
calculated_data.collect { |row| group_aliases.map { |group_alias| row[group_alias] } }
|
|
578
|
+
else
|
|
579
|
+
calculated_data.collect { |row| row[group_aliases.first] }
|
|
580
|
+
end
|
|
557
581
|
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
|
558
582
|
key_records = key_records.index_by(&:id)
|
|
559
583
|
end
|
|
@@ -657,19 +681,16 @@ module ActiveRecord
|
|
|
657
681
|
if column_name == :all
|
|
658
682
|
column_alias = Arel.star
|
|
659
683
|
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
|
684
|
+
relation.unscope!(:order)
|
|
660
685
|
else
|
|
661
686
|
column_alias = Arel.sql("count_column")
|
|
662
|
-
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
|
687
|
+
relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
|
|
663
688
|
end
|
|
664
689
|
|
|
665
690
|
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
|
666
691
|
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
|
667
692
|
|
|
668
|
-
|
|
669
|
-
relation.unscope(:order).build_subquery(subquery_alias, select_value)
|
|
670
|
-
else
|
|
671
|
-
relation.build_subquery(subquery_alias, select_value)
|
|
672
|
-
end
|
|
693
|
+
relation.build_subquery(subquery_alias, select_value)
|
|
673
694
|
end
|
|
674
695
|
end
|
|
675
696
|
end
|
|
@@ -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,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
148
|
elsif undesired.nil?
|
|
149
149
|
found
|
|
150
150
|
else
|
|
151
|
-
raise ActiveRecord::SoleRecordExceeded.new(
|
|
151
|
+
raise ActiveRecord::SoleRecordExceeded.new(self)
|
|
152
152
|
end
|
|
153
153
|
end
|
|
154
154
|
|
|
@@ -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
|
|
@@ -461,15 +462,15 @@ module ActiveRecord
|
|
|
461
462
|
relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
|
|
462
463
|
|
|
463
464
|
if eager_loading && has_limit_or_offset? && !(
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
)
|
|
465
|
+
using_limitable_reflections?(join_dependency.reflections) &&
|
|
466
|
+
using_limitable_reflections?(
|
|
467
|
+
construct_join_dependency(
|
|
468
|
+
select_association_list(joins_values).concat(
|
|
469
|
+
select_association_list(left_outer_joins_values)
|
|
470
|
+
), nil
|
|
471
|
+
).reflections
|
|
472
472
|
)
|
|
473
|
+
)
|
|
473
474
|
relation = skip_query_cache_if_necessary do
|
|
474
475
|
model.with_connection do |c|
|
|
475
476
|
c.distinct_relation_for_primary_key(relation)
|
|
@@ -491,15 +492,16 @@ module ActiveRecord
|
|
|
491
492
|
def find_with_ids(*ids)
|
|
492
493
|
raise UnknownPrimaryKey.new(model) if primary_key.nil?
|
|
493
494
|
|
|
495
|
+
first_item = ids.first
|
|
496
|
+
return [] if first_item.is_a?(Array) && first_item.empty?
|
|
497
|
+
|
|
494
498
|
expects_array = if model.composite_primary_key?
|
|
495
|
-
|
|
499
|
+
first_item.first.is_a?(Array)
|
|
496
500
|
else
|
|
497
|
-
|
|
501
|
+
first_item.is_a?(Array)
|
|
498
502
|
end
|
|
499
503
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
ids = ids.first if expects_array
|
|
504
|
+
ids = first_item if expects_array
|
|
503
505
|
|
|
504
506
|
ids = ids.compact.uniq
|
|
505
507
|
|
|
@@ -541,10 +543,6 @@ module ActiveRecord
|
|
|
541
543
|
def find_some(ids)
|
|
542
544
|
return find_some_ordered(ids) unless order_values.present?
|
|
543
545
|
|
|
544
|
-
relation = where(primary_key => ids)
|
|
545
|
-
relation = relation.select(table[primary_key]) unless select_values.empty?
|
|
546
|
-
result = relation.to_a
|
|
547
|
-
|
|
548
546
|
expected_size =
|
|
549
547
|
if limit_value && ids.size > limit_value
|
|
550
548
|
limit_value
|
|
@@ -553,10 +551,18 @@ module ActiveRecord
|
|
|
553
551
|
end
|
|
554
552
|
|
|
555
553
|
# 11 ids with limit 3, offset 9 should give 2 results.
|
|
556
|
-
if offset_value
|
|
557
|
-
|
|
554
|
+
if offset_value
|
|
555
|
+
if ids.size <= offset_value
|
|
556
|
+
return []
|
|
557
|
+
elsif ids.size - offset_value < expected_size
|
|
558
|
+
expected_size = ids.size - offset_value
|
|
559
|
+
end
|
|
558
560
|
end
|
|
559
561
|
|
|
562
|
+
relation = where(primary_key => ids)
|
|
563
|
+
relation = relation.select(table[primary_key]) unless select_values.empty?
|
|
564
|
+
result = relation.to_a
|
|
565
|
+
|
|
560
566
|
if result.size == expected_size
|
|
561
567
|
result
|
|
562
568
|
else
|
|
@@ -566,6 +572,7 @@ module ActiveRecord
|
|
|
566
572
|
|
|
567
573
|
def find_some_ordered(ids)
|
|
568
574
|
ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
|
|
575
|
+
return [] if ids.empty?
|
|
569
576
|
|
|
570
577
|
relation = except(:limit, :offset)
|
|
571
578
|
relation = relation.where(primary_key => ids)
|
|
@@ -573,12 +580,20 @@ module ActiveRecord
|
|
|
573
580
|
result = relation.records
|
|
574
581
|
|
|
575
582
|
if result.size == ids.size
|
|
576
|
-
result.in_order_of(:id, ids.map { |id|
|
|
583
|
+
result.in_order_of(:id, ids.map { |id| cast_primary_key(id) })
|
|
577
584
|
else
|
|
578
585
|
raise_record_not_found_exception!(ids, result.size, ids.size)
|
|
579
586
|
end
|
|
580
587
|
end
|
|
581
588
|
|
|
589
|
+
def cast_primary_key(id)
|
|
590
|
+
if model.composite_primary_key?
|
|
591
|
+
primary_key.zip(id).map! { |attr, value| model.type_for_attribute(attr).cast(value) }
|
|
592
|
+
else
|
|
593
|
+
model.type_for_attribute(primary_key).cast(id)
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
|
|
582
597
|
def find_take
|
|
583
598
|
if loaded?
|
|
584
599
|
records.first
|
|
@@ -638,24 +653,40 @@ module ActiveRecord
|
|
|
638
653
|
end
|
|
639
654
|
|
|
640
655
|
def ordered_relation
|
|
641
|
-
if order_values.empty?
|
|
642
|
-
|
|
656
|
+
if order_values.empty?
|
|
657
|
+
if !_order_columns.empty?
|
|
658
|
+
return order(_order_columns.map { |column| table[column].asc })
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
if ActiveRecord.raise_on_missing_required_finder_order_columns
|
|
662
|
+
raise MissingRequiredOrderError, <<~MSG.squish
|
|
663
|
+
Relation has no order values, and #{model} has no order columns to use as a default.
|
|
664
|
+
Set at least one of `implicit_order_column`, `query_constraints` or `primary_key` on
|
|
665
|
+
the model when no `order `is specified on the relation.
|
|
666
|
+
MSG
|
|
667
|
+
else
|
|
668
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
|
669
|
+
Calling order dependent finder methods (e.g. `#first`, `#second`) without `order` values on the relation,
|
|
670
|
+
and on a model (#{model}) that does not have any order columns (`implicit_order_column`, `query_constraints`,
|
|
671
|
+
or `primary_key`) to fall back on is deprecated and will raise `ActiveRecord::MissingRequiredOrderError`
|
|
672
|
+
in Rails 8.2.
|
|
673
|
+
MSG
|
|
674
|
+
|
|
675
|
+
self
|
|
676
|
+
end
|
|
643
677
|
else
|
|
644
678
|
self
|
|
645
679
|
end
|
|
646
680
|
end
|
|
647
681
|
|
|
648
682
|
def _order_columns
|
|
649
|
-
|
|
683
|
+
columns = Array(model.implicit_order_column)
|
|
650
684
|
|
|
651
|
-
|
|
652
|
-
oc << model.query_constraints_list if model.query_constraints_list
|
|
685
|
+
return columns.compact if columns.length.positive? && columns.last.nil?
|
|
653
686
|
|
|
654
|
-
|
|
655
|
-
oc << model.primary_key
|
|
656
|
-
end
|
|
687
|
+
columns += Array(model.query_constraints_list || model.primary_key)
|
|
657
688
|
|
|
658
|
-
|
|
689
|
+
columns.uniq.compact
|
|
659
690
|
end
|
|
660
691
|
end
|
|
661
692
|
end
|
|
@@ -161,8 +161,10 @@ module ActiveRecord
|
|
|
161
161
|
relation.order!(*other.order_values)
|
|
162
162
|
end
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
unless other.extensions.empty?
|
|
165
|
+
extensions = other.extensions - relation.extensions
|
|
166
|
+
relation.extending!(*extensions) if extensions.any?
|
|
167
|
+
end
|
|
166
168
|
end
|
|
167
169
|
|
|
168
170
|
def merge_single_values
|
|
@@ -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?
|
|
@@ -59,6 +59,8 @@ module ActiveRecord
|
|
|
59
59
|
def convert_to_id(value)
|
|
60
60
|
if primary_key.is_a?(Array)
|
|
61
61
|
primary_key.map do |attribute|
|
|
62
|
+
next nil if value.nil?
|
|
63
|
+
|
|
62
64
|
if attribute == "id"
|
|
63
65
|
value.id_value
|
|
64
66
|
else
|
|
@@ -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,7 +30,7 @@ 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,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
|
|
@@ -93,30 +93,32 @@ module ActiveRecord
|
|
|
93
93
|
if key.is_a?(Array)
|
|
94
94
|
queries = Array(value).map do |ids_set|
|
|
95
95
|
raise ArgumentError, "Expected corresponding value for #{key} to be an Array" unless ids_set.is_a?(Array)
|
|
96
|
-
expand_from_hash(key.zip(ids_set).to_h)
|
|
96
|
+
expand_from_hash(convert_dot_notation_to_hash(key.zip(ids_set).to_h))
|
|
97
97
|
end
|
|
98
98
|
grouping_queries(queries)
|
|
99
99
|
elsif value.is_a?(Hash) && !table.has_column?(key)
|
|
100
100
|
table.associated_table(key, &block)
|
|
101
101
|
.predicate_builder.expand_from_hash(value.stringify_keys)
|
|
102
|
-
elsif table.associated_with
|
|
102
|
+
elsif (associated_reflection = table.associated_with(key))
|
|
103
103
|
# Find the foreign key when using queries such as:
|
|
104
104
|
# Post.where(author: author)
|
|
105
105
|
#
|
|
106
106
|
# For polymorphic relationships, find the foreign key and type:
|
|
107
107
|
# PriceEstimate.where(estimate_of: treasure)
|
|
108
|
-
|
|
109
|
-
if
|
|
108
|
+
|
|
109
|
+
if associated_reflection.polymorphic?
|
|
110
110
|
value = [value] unless value.is_a?(Array)
|
|
111
111
|
klass = PolymorphicArrayValue
|
|
112
|
-
elsif
|
|
112
|
+
elsif associated_reflection.through_reflection?
|
|
113
|
+
associated_table = table.associated_table(key)
|
|
114
|
+
|
|
113
115
|
next associated_table.predicate_builder.expand_from_hash(
|
|
114
116
|
associated_table.primary_key => value
|
|
115
117
|
)
|
|
116
118
|
end
|
|
117
119
|
|
|
118
120
|
klass ||= AssociationQueryValue
|
|
119
|
-
queries = klass.new(
|
|
121
|
+
queries = klass.new(associated_reflection, value).queries.map! do |query|
|
|
120
122
|
# If the query produced is identical to attributes don't go any deeper.
|
|
121
123
|
# Prevents stack level too deep errors when association and foreign_key are identical.
|
|
122
124
|
query == attributes ? self[key, value] : expand_from_hash(query)
|