activerecord 7.2.2.1 → 8.1.2
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 +564 -753
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +35 -11
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- 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 +10 -8
- 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/join_association.rb +25 -27
- 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 +17 -4
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +40 -26
- data/lib/active_record/autosave_association.rb +91 -39
- data/lib/active_record/base.rb +3 -4
- 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 +458 -117
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
- 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 +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
- 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 +7 -9
- 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 +10 -11
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- 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 +28 -45
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -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 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
- 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 +2 -19
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +37 -10
- data/lib/active_record/core.rb +61 -25
- data/lib/active_record/counter_cache.rb +34 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- 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 +19 -19
- 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 +9 -9
- data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
- data/lib/active_record/encryption/encryptor.rb +49 -28
- 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 +46 -42
- data/lib/active_record/errors.rb +36 -12
- 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/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +13 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- data/lib/active_record/locking/optimistic.rb +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 +44 -11
- 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 +50 -43
- 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 +104 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +12 -12
- data/lib/active_record/railtie.rb +37 -32
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -37
- 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 +80 -63
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +54 -37
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -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 +4 -2
- data/lib/active_record/relation/query_methods.rb +156 -95
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +10 -11
- data/lib/active_record/relation.rb +122 -80
- 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 +47 -22
- 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/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +47 -18
- 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 +14 -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 +39 -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 +15 -2
- data/lib/active_record/type/serialized.rb +11 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +85 -50
- 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 +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/select_manager.rb +13 -4
- data/lib/arel/table.rb +3 -7
- 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 +6 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +17 -17
- 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
|
@@ -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)
|
|
@@ -234,7 +233,7 @@ module ActiveRecord
|
|
|
234
233
|
if operation == "count"
|
|
235
234
|
unless distinct_value || distinct_select?(column_name || select_for_count)
|
|
236
235
|
relation.distinct!
|
|
237
|
-
relation.select_values = Array(
|
|
236
|
+
relation.select_values = Array(model.primary_key || table[Arel.star])
|
|
238
237
|
end
|
|
239
238
|
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
|
240
239
|
relation.order_values = [] if group_values.empty?
|
|
@@ -275,14 +274,23 @@ module ActiveRecord
|
|
|
275
274
|
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
|
|
276
275
|
# # => [2, 3]
|
|
277
276
|
#
|
|
278
|
-
# Comment.joins(:person).pluck(:id, person:
|
|
279
|
-
# # SELECT comments.id,
|
|
277
|
+
# Comment.joins(:person).pluck(:id, person: :id)
|
|
278
|
+
# # SELECT comments.id, person.id FROM comments INNER JOIN people person ON person.id = comments.person_id
|
|
280
279
|
# # => [[1, 2], [2, 2]]
|
|
281
280
|
#
|
|
281
|
+
# Comment.joins(:person).pluck(:id, person: [:id, :name])
|
|
282
|
+
# # SELECT comments.id, person.id, person.name FROM comments INNER JOIN people person ON person.id = comments.person_id
|
|
283
|
+
# # => [[1, 2, 'David'], [2, 2, 'David']]
|
|
284
|
+
#
|
|
282
285
|
# Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
|
|
283
286
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
|
284
287
|
# # => ['0', '27761', '173']
|
|
285
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
|
+
#
|
|
286
294
|
# See also #ids.
|
|
287
295
|
def pluck(*column_names)
|
|
288
296
|
if @none
|
|
@@ -306,16 +314,16 @@ module ActiveRecord
|
|
|
306
314
|
relation = apply_join_dependency
|
|
307
315
|
relation.pluck(*column_names)
|
|
308
316
|
else
|
|
309
|
-
|
|
317
|
+
model.disallow_raw_sql!(flattened_args(column_names))
|
|
310
318
|
relation = spawn
|
|
311
319
|
columns = relation.arel_columns(column_names)
|
|
312
320
|
relation.select_values = columns
|
|
313
321
|
result = skip_query_cache_if_necessary do
|
|
314
|
-
if where_clause.contradiction?
|
|
322
|
+
if where_clause.contradiction? && !possible_aggregation?(column_names)
|
|
315
323
|
ActiveRecord::Result.empty(async: @async)
|
|
316
324
|
else
|
|
317
|
-
|
|
318
|
-
c.select_all(relation.arel, "#{
|
|
325
|
+
model.with_connection do |c|
|
|
326
|
+
c.select_all(relation.arel, "#{model.name} Pluck", async: @async)
|
|
319
327
|
end
|
|
320
328
|
end
|
|
321
329
|
end
|
|
@@ -391,8 +399,8 @@ module ActiveRecord
|
|
|
391
399
|
ActiveRecord::Result.empty
|
|
392
400
|
else
|
|
393
401
|
skip_query_cache_if_necessary do
|
|
394
|
-
|
|
395
|
-
c.select_all(relation, "#{
|
|
402
|
+
model.with_connection do |c|
|
|
403
|
+
c.select_all(relation, "#{model.name} Ids", async: @async)
|
|
396
404
|
end
|
|
397
405
|
end
|
|
398
406
|
end
|
|
@@ -406,9 +414,21 @@ module ActiveRecord
|
|
|
406
414
|
async.ids
|
|
407
415
|
end
|
|
408
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
|
+
|
|
409
429
|
private
|
|
410
430
|
def all_attributes?(column_names)
|
|
411
|
-
(column_names.map(&:to_s) -
|
|
431
|
+
(column_names.map(&:to_s) - model.attribute_names - model.attribute_aliases.keys).empty?
|
|
412
432
|
end
|
|
413
433
|
|
|
414
434
|
def has_include?(column_name)
|
|
@@ -446,11 +466,13 @@ module ActiveRecord
|
|
|
446
466
|
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
|
447
467
|
end
|
|
448
468
|
|
|
449
|
-
def
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
|
454
476
|
end
|
|
455
477
|
end
|
|
456
478
|
|
|
@@ -469,7 +491,7 @@ module ActiveRecord
|
|
|
469
491
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
|
470
492
|
relation = unscope(:order).distinct!(false)
|
|
471
493
|
|
|
472
|
-
column = aggregate_column(column_name)
|
|
494
|
+
column = relation.aggregate_column(column_name)
|
|
473
495
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
474
496
|
select_value.distinct = true if operation == "sum" && distinct
|
|
475
497
|
|
|
@@ -479,11 +501,15 @@ module ActiveRecord
|
|
|
479
501
|
end
|
|
480
502
|
|
|
481
503
|
query_result = if relation.where_clause.contradiction?
|
|
482
|
-
|
|
504
|
+
if @async
|
|
505
|
+
FutureResult.wrap(ActiveRecord::Result.empty)
|
|
506
|
+
else
|
|
507
|
+
ActiveRecord::Result.empty
|
|
508
|
+
end
|
|
483
509
|
else
|
|
484
510
|
skip_query_cache_if_necessary do
|
|
485
|
-
|
|
486
|
-
c.select_all(query_builder, "#{
|
|
511
|
+
model.with_connection do |c|
|
|
512
|
+
c.select_all(query_builder, "#{model.name} #{operation.capitalize}", async: @async)
|
|
487
513
|
end
|
|
488
514
|
end
|
|
489
515
|
end
|
|
@@ -501,16 +527,17 @@ module ActiveRecord
|
|
|
501
527
|
|
|
502
528
|
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
|
503
529
|
group_fields = group_values
|
|
504
|
-
group_fields = group_fields.uniq if group_fields.size > 1
|
|
505
530
|
|
|
506
531
|
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
|
507
|
-
association =
|
|
532
|
+
association = model._reflect_on_association(group_fields.first)
|
|
508
533
|
associated = association && association.belongs_to? # only count belongs_to associations
|
|
509
534
|
group_fields = Array(association.foreign_key) if associated
|
|
510
535
|
end
|
|
511
|
-
group_fields = arel_columns(group_fields)
|
|
512
536
|
|
|
513
|
-
|
|
537
|
+
relation = except(:group).distinct!(false)
|
|
538
|
+
group_fields = relation.arel_columns(group_fields)
|
|
539
|
+
|
|
540
|
+
model.with_connection do |connection|
|
|
514
541
|
column_alias_tracker = ColumnAliasTracker.new(connection)
|
|
515
542
|
|
|
516
543
|
group_aliases = group_fields.map { |field|
|
|
@@ -519,16 +546,16 @@ module ActiveRecord
|
|
|
519
546
|
}
|
|
520
547
|
group_columns = group_aliases.zip(group_fields)
|
|
521
548
|
|
|
522
|
-
column = aggregate_column(column_name)
|
|
549
|
+
column = relation.aggregate_column(column_name)
|
|
523
550
|
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
|
524
551
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
525
|
-
select_value.as(adapter_class.quote_column_name(column_alias))
|
|
552
|
+
select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))
|
|
526
553
|
|
|
527
554
|
select_values = [select_value]
|
|
528
555
|
select_values += self.select_values unless having_clause.empty?
|
|
529
556
|
|
|
530
557
|
select_values.concat group_columns.map { |aliaz, field|
|
|
531
|
-
aliaz = adapter_class.quote_column_name(aliaz)
|
|
558
|
+
aliaz = model.adapter_class.quote_column_name(aliaz)
|
|
532
559
|
if field.respond_to?(:as)
|
|
533
560
|
field.as(aliaz)
|
|
534
561
|
else
|
|
@@ -536,12 +563,11 @@ module ActiveRecord
|
|
|
536
563
|
end
|
|
537
564
|
}
|
|
538
565
|
|
|
539
|
-
relation = except(:group).distinct!(false)
|
|
540
566
|
relation.group_values = group_fields
|
|
541
567
|
relation.select_values = select_values
|
|
542
568
|
|
|
543
569
|
result = skip_query_cache_if_necessary do
|
|
544
|
-
connection.select_all(relation.arel, "#{
|
|
570
|
+
connection.select_all(relation.arel, "#{model.name} #{operation.capitalize}", async: @async)
|
|
545
571
|
end
|
|
546
572
|
|
|
547
573
|
result.then do |calculated_data|
|
|
@@ -583,7 +609,7 @@ module ActiveRecord
|
|
|
583
609
|
|
|
584
610
|
def type_for(field, &block)
|
|
585
611
|
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
|
586
|
-
|
|
612
|
+
model.type_for_attribute(field_name, &block)
|
|
587
613
|
end
|
|
588
614
|
|
|
589
615
|
def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
|
|
@@ -596,12 +622,12 @@ module ActiveRecord
|
|
|
596
622
|
|
|
597
623
|
def type_cast_pluck_values(result, columns)
|
|
598
624
|
cast_types = if result.columns.size != columns.size
|
|
599
|
-
|
|
625
|
+
model.attribute_types
|
|
600
626
|
else
|
|
601
627
|
join_dependencies = nil
|
|
602
628
|
columns.map.with_index do |column, i|
|
|
603
629
|
column.try(:type_caster) ||
|
|
604
|
-
|
|
630
|
+
model.attribute_types.fetch(name = result.columns[i]) do
|
|
605
631
|
join_dependencies ||= build_join_dependencies
|
|
606
632
|
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
|
|
607
633
|
result.column_types[i] || Type.default_value
|
|
@@ -630,22 +656,12 @@ module ActiveRecord
|
|
|
630
656
|
end
|
|
631
657
|
|
|
632
658
|
def select_for_count
|
|
633
|
-
if select_values.
|
|
634
|
-
return select_values.first if select_values.one?
|
|
635
|
-
|
|
636
|
-
select_values.map do |field|
|
|
637
|
-
column = arel_column(field.to_s) do |attr_name|
|
|
638
|
-
Arel.sql(attr_name)
|
|
639
|
-
end
|
|
640
|
-
|
|
641
|
-
if column.is_a?(Arel::Nodes::SqlLiteral)
|
|
642
|
-
column
|
|
643
|
-
else
|
|
644
|
-
"#{adapter_class.quote_table_name(column.relation.name)}.#{adapter_class.quote_column_name(column.name)}"
|
|
645
|
-
end
|
|
646
|
-
end.join(", ")
|
|
647
|
-
else
|
|
659
|
+
if select_values.empty?
|
|
648
660
|
:all
|
|
661
|
+
else
|
|
662
|
+
with_connection do |conn|
|
|
663
|
+
arel_columns(select_values).map { |column| conn.visitor.compile(column) }.join(", ")
|
|
664
|
+
end
|
|
649
665
|
end
|
|
650
666
|
end
|
|
651
667
|
|
|
@@ -660,9 +676,10 @@ module ActiveRecord
|
|
|
660
676
|
if column_name == :all
|
|
661
677
|
column_alias = Arel.star
|
|
662
678
|
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
|
679
|
+
relation.unscope!(:order)
|
|
663
680
|
else
|
|
664
681
|
column_alias = Arel.sql("count_column")
|
|
665
|
-
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
|
682
|
+
relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
|
|
666
683
|
end
|
|
667
684
|
|
|
668
685
|
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
|
@@ -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
|
|
@@ -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
|
-
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,21 +415,22 @@ 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}"
|
|
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
|
|
@@ -471,7 +472,7 @@ module ActiveRecord
|
|
|
471
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)
|
|
476
477
|
end
|
|
477
478
|
end
|
|
@@ -489,9 +490,9 @@ module ActiveRecord
|
|
|
489
490
|
end
|
|
490
491
|
|
|
491
492
|
def find_with_ids(*ids)
|
|
492
|
-
raise UnknownPrimaryKey.new(
|
|
493
|
+
raise UnknownPrimaryKey.new(model) if primary_key.nil?
|
|
493
494
|
|
|
494
|
-
expects_array = if
|
|
495
|
+
expects_array = if model.composite_primary_key?
|
|
495
496
|
ids.first.first.is_a?(Array)
|
|
496
497
|
else
|
|
497
498
|
ids.first.is_a?(Array)
|
|
@@ -503,7 +504,7 @@ module ActiveRecord
|
|
|
503
504
|
|
|
504
505
|
ids = ids.compact.uniq
|
|
505
506
|
|
|
506
|
-
model_name =
|
|
507
|
+
model_name = model.name
|
|
507
508
|
|
|
508
509
|
case ids.size
|
|
509
510
|
when 0
|
|
@@ -525,7 +526,7 @@ module ActiveRecord
|
|
|
525
526
|
MSG
|
|
526
527
|
end
|
|
527
528
|
|
|
528
|
-
relation = if
|
|
529
|
+
relation = if model.composite_primary_key?
|
|
529
530
|
where(primary_key.zip(id).to_h)
|
|
530
531
|
else
|
|
531
532
|
where(primary_key => id)
|
|
@@ -573,7 +574,7 @@ module ActiveRecord
|
|
|
573
574
|
result = relation.records
|
|
574
575
|
|
|
575
576
|
if result.size == ids.size
|
|
576
|
-
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) })
|
|
577
578
|
else
|
|
578
579
|
raise_record_not_found_exception!(ids, result.size, ids.size)
|
|
579
580
|
end
|
|
@@ -638,24 +639,40 @@ module ActiveRecord
|
|
|
638
639
|
end
|
|
639
640
|
|
|
640
641
|
def ordered_relation
|
|
641
|
-
if order_values.empty?
|
|
642
|
-
|
|
642
|
+
if order_values.empty?
|
|
643
|
+
if !_order_columns.empty?
|
|
644
|
+
return order(_order_columns.map { |column| table[column].asc })
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
if ActiveRecord.raise_on_missing_required_finder_order_columns
|
|
648
|
+
raise MissingRequiredOrderError, <<~MSG.squish
|
|
649
|
+
Relation has no order values, and #{model} has no order columns to use as a default.
|
|
650
|
+
Set at least one of `implicit_order_column`, `query_constraints` or `primary_key` on
|
|
651
|
+
the model when no `order `is specified on the relation.
|
|
652
|
+
MSG
|
|
653
|
+
else
|
|
654
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
|
655
|
+
Calling order dependent finder methods (e.g. `#first`, `#second`) without `order` values on the relation,
|
|
656
|
+
and on a model (#{model}) that does not have any order columns (`implicit_order_column`, `query_constraints`,
|
|
657
|
+
or `primary_key`) to fall back on is deprecated and will raise `ActiveRecord::MissingRequiredOrderError`
|
|
658
|
+
in Rails 8.2.
|
|
659
|
+
MSG
|
|
660
|
+
|
|
661
|
+
self
|
|
662
|
+
end
|
|
643
663
|
else
|
|
644
664
|
self
|
|
645
665
|
end
|
|
646
666
|
end
|
|
647
667
|
|
|
648
668
|
def _order_columns
|
|
649
|
-
|
|
669
|
+
columns = Array(model.implicit_order_column)
|
|
650
670
|
|
|
651
|
-
|
|
652
|
-
oc << query_constraints_list if query_constraints_list
|
|
671
|
+
return columns.compact if columns.length.positive? && columns.last.nil?
|
|
653
672
|
|
|
654
|
-
|
|
655
|
-
oc << primary_key
|
|
656
|
-
end
|
|
673
|
+
columns += Array(model.query_constraints_list || model.primary_key)
|
|
657
674
|
|
|
658
|
-
|
|
675
|
+
columns.uniq.compact
|
|
659
676
|
end
|
|
660
677
|
end
|
|
661
678
|
end
|