activerecord 7.2.2.1 → 8.0.5
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 +523 -677
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -0
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +34 -10
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +10 -8
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- 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 +2 -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/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +34 -4
- 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 +1 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +37 -26
- data/lib/active_record/autosave_association.rb +91 -39
- data/lib/active_record/base.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +55 -47
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -8
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +40 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +92 -70
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +35 -46
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -9
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -24
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +60 -17
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +61 -99
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
- 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 +10 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +63 -17
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
- data/lib/active_record/connection_adapters.rb +0 -56
- data/lib/active_record/connection_handling.rb +35 -9
- data/lib/active_record/core.rb +57 -22
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +4 -0
- data/lib/active_record/database_configurations/hash_config.rb +16 -2
- data/lib/active_record/delegated_type.rb +18 -18
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encryptor.rb +37 -28
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +22 -34
- data/lib/active_record/errors.rb +16 -8
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/insert_all.rb +3 -3
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -11
- data/lib/active_record/migration/command_recorder.rb +32 -12
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +40 -43
- data/lib/active_record/model_schema.rb +3 -4
- data/lib/active_record/nested_attributes.rb +4 -6
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +106 -50
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +12 -12
- data/lib/active_record/railtie.rb +4 -28
- data/lib/active_record/railties/databases.rake +11 -34
- data/lib/active_record/reflection.rb +18 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +132 -72
- data/lib/active_record/relation/calculations.rb +71 -65
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +32 -31
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +13 -0
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +121 -70
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +9 -3
- data/lib/active_record/relation.rb +95 -67
- data/lib/active_record/result.rb +66 -4
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +34 -11
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +7 -6
- data/lib/active_record/statement_cache.rb +14 -14
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +1 -3
- data/lib/active_record/tasks/database_tasks.rb +69 -60
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +9 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transactions.rb +8 -7
- data/lib/active_record/type/serialized.rb +5 -0
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +22 -49
- 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 +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +6 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -0
- data/lib/arel/visitors/to_sql.rb +3 -1
- metadata +11 -15
- 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,10 +274,14 @@ 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']
|
|
@@ -306,7 +309,7 @@ module ActiveRecord
|
|
|
306
309
|
relation = apply_join_dependency
|
|
307
310
|
relation.pluck(*column_names)
|
|
308
311
|
else
|
|
309
|
-
|
|
312
|
+
model.disallow_raw_sql!(flattened_args(column_names))
|
|
310
313
|
relation = spawn
|
|
311
314
|
columns = relation.arel_columns(column_names)
|
|
312
315
|
relation.select_values = columns
|
|
@@ -314,8 +317,8 @@ module ActiveRecord
|
|
|
314
317
|
if where_clause.contradiction?
|
|
315
318
|
ActiveRecord::Result.empty(async: @async)
|
|
316
319
|
else
|
|
317
|
-
|
|
318
|
-
c.select_all(relation.arel, "#{
|
|
320
|
+
model.with_connection do |c|
|
|
321
|
+
c.select_all(relation.arel, "#{model.name} Pluck", async: @async)
|
|
319
322
|
end
|
|
320
323
|
end
|
|
321
324
|
end
|
|
@@ -391,8 +394,8 @@ module ActiveRecord
|
|
|
391
394
|
ActiveRecord::Result.empty
|
|
392
395
|
else
|
|
393
396
|
skip_query_cache_if_necessary do
|
|
394
|
-
|
|
395
|
-
c.select_all(relation, "#{
|
|
397
|
+
model.with_connection do |c|
|
|
398
|
+
c.select_all(relation, "#{model.name} Ids", async: @async)
|
|
396
399
|
end
|
|
397
400
|
end
|
|
398
401
|
end
|
|
@@ -406,9 +409,21 @@ module ActiveRecord
|
|
|
406
409
|
async.ids
|
|
407
410
|
end
|
|
408
411
|
|
|
412
|
+
protected
|
|
413
|
+
def aggregate_column(column_name)
|
|
414
|
+
case column_name
|
|
415
|
+
when Arel::Expressions
|
|
416
|
+
column_name
|
|
417
|
+
when :all
|
|
418
|
+
Arel.star
|
|
419
|
+
else
|
|
420
|
+
arel_column(column_name)
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
409
424
|
private
|
|
410
425
|
def all_attributes?(column_names)
|
|
411
|
-
(column_names.map(&:to_s) -
|
|
426
|
+
(column_names.map(&:to_s) - model.attribute_names - model.attribute_aliases.keys).empty?
|
|
412
427
|
end
|
|
413
428
|
|
|
414
429
|
def has_include?(column_name)
|
|
@@ -446,14 +461,6 @@ module ActiveRecord
|
|
|
446
461
|
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
|
447
462
|
end
|
|
448
463
|
|
|
449
|
-
def aggregate_column(column_name)
|
|
450
|
-
return column_name if Arel::Expressions === column_name
|
|
451
|
-
|
|
452
|
-
arel_column(column_name.to_s) do |name|
|
|
453
|
-
column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
|
|
454
|
-
end
|
|
455
|
-
end
|
|
456
|
-
|
|
457
464
|
def operation_over_aggregate_column(column, operation, distinct)
|
|
458
465
|
operation == "count" ? column.count(distinct) : column.public_send(operation)
|
|
459
466
|
end
|
|
@@ -469,7 +476,7 @@ module ActiveRecord
|
|
|
469
476
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
|
470
477
|
relation = unscope(:order).distinct!(false)
|
|
471
478
|
|
|
472
|
-
column = aggregate_column(column_name)
|
|
479
|
+
column = relation.aggregate_column(column_name)
|
|
473
480
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
474
481
|
select_value.distinct = true if operation == "sum" && distinct
|
|
475
482
|
|
|
@@ -479,11 +486,15 @@ module ActiveRecord
|
|
|
479
486
|
end
|
|
480
487
|
|
|
481
488
|
query_result = if relation.where_clause.contradiction?
|
|
482
|
-
|
|
489
|
+
if @async
|
|
490
|
+
FutureResult.wrap(ActiveRecord::Result.empty)
|
|
491
|
+
else
|
|
492
|
+
ActiveRecord::Result.empty
|
|
493
|
+
end
|
|
483
494
|
else
|
|
484
495
|
skip_query_cache_if_necessary do
|
|
485
|
-
|
|
486
|
-
c.select_all(query_builder, "#{
|
|
496
|
+
model.with_connection do |c|
|
|
497
|
+
c.select_all(query_builder, "#{model.name} #{operation.capitalize}", async: @async)
|
|
487
498
|
end
|
|
488
499
|
end
|
|
489
500
|
end
|
|
@@ -504,13 +515,15 @@ module ActiveRecord
|
|
|
504
515
|
group_fields = group_fields.uniq if group_fields.size > 1
|
|
505
516
|
|
|
506
517
|
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
|
507
|
-
association =
|
|
518
|
+
association = model._reflect_on_association(group_fields.first)
|
|
508
519
|
associated = association && association.belongs_to? # only count belongs_to associations
|
|
509
520
|
group_fields = Array(association.foreign_key) if associated
|
|
510
521
|
end
|
|
511
|
-
group_fields = arel_columns(group_fields)
|
|
512
522
|
|
|
513
|
-
|
|
523
|
+
relation = except(:group).distinct!(false)
|
|
524
|
+
group_fields = relation.arel_columns(group_fields)
|
|
525
|
+
|
|
526
|
+
model.with_connection do |connection|
|
|
514
527
|
column_alias_tracker = ColumnAliasTracker.new(connection)
|
|
515
528
|
|
|
516
529
|
group_aliases = group_fields.map { |field|
|
|
@@ -519,16 +532,16 @@ module ActiveRecord
|
|
|
519
532
|
}
|
|
520
533
|
group_columns = group_aliases.zip(group_fields)
|
|
521
534
|
|
|
522
|
-
column = aggregate_column(column_name)
|
|
535
|
+
column = relation.aggregate_column(column_name)
|
|
523
536
|
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
|
524
537
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
525
|
-
select_value.as(adapter_class.quote_column_name(column_alias))
|
|
538
|
+
select_value.as(model.adapter_class.quote_column_name(column_alias))
|
|
526
539
|
|
|
527
540
|
select_values = [select_value]
|
|
528
541
|
select_values += self.select_values unless having_clause.empty?
|
|
529
542
|
|
|
530
543
|
select_values.concat group_columns.map { |aliaz, field|
|
|
531
|
-
aliaz = adapter_class.quote_column_name(aliaz)
|
|
544
|
+
aliaz = model.adapter_class.quote_column_name(aliaz)
|
|
532
545
|
if field.respond_to?(:as)
|
|
533
546
|
field.as(aliaz)
|
|
534
547
|
else
|
|
@@ -536,12 +549,11 @@ module ActiveRecord
|
|
|
536
549
|
end
|
|
537
550
|
}
|
|
538
551
|
|
|
539
|
-
relation = except(:group).distinct!(false)
|
|
540
552
|
relation.group_values = group_fields
|
|
541
553
|
relation.select_values = select_values
|
|
542
554
|
|
|
543
555
|
result = skip_query_cache_if_necessary do
|
|
544
|
-
connection.select_all(relation.arel, "#{
|
|
556
|
+
connection.select_all(relation.arel, "#{model.name} #{operation.capitalize}", async: @async)
|
|
545
557
|
end
|
|
546
558
|
|
|
547
559
|
result.then do |calculated_data|
|
|
@@ -583,7 +595,7 @@ module ActiveRecord
|
|
|
583
595
|
|
|
584
596
|
def type_for(field, &block)
|
|
585
597
|
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
|
586
|
-
|
|
598
|
+
model.type_for_attribute(field_name, &block)
|
|
587
599
|
end
|
|
588
600
|
|
|
589
601
|
def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
|
|
@@ -596,12 +608,12 @@ module ActiveRecord
|
|
|
596
608
|
|
|
597
609
|
def type_cast_pluck_values(result, columns)
|
|
598
610
|
cast_types = if result.columns.size != columns.size
|
|
599
|
-
|
|
611
|
+
model.attribute_types
|
|
600
612
|
else
|
|
601
613
|
join_dependencies = nil
|
|
602
614
|
columns.map.with_index do |column, i|
|
|
603
615
|
column.try(:type_caster) ||
|
|
604
|
-
|
|
616
|
+
model.attribute_types.fetch(name = result.columns[i]) do
|
|
605
617
|
join_dependencies ||= build_join_dependencies
|
|
606
618
|
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
|
|
607
619
|
result.column_types[i] || Type.default_value
|
|
@@ -630,22 +642,12 @@ module ActiveRecord
|
|
|
630
642
|
end
|
|
631
643
|
|
|
632
644
|
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
|
|
645
|
+
if select_values.empty?
|
|
648
646
|
:all
|
|
647
|
+
else
|
|
648
|
+
with_connection do |conn|
|
|
649
|
+
arel_columns(select_values).map { |column| conn.visitor.compile(column) }.join(", ")
|
|
650
|
+
end
|
|
649
651
|
end
|
|
650
652
|
end
|
|
651
653
|
|
|
@@ -662,13 +664,17 @@ module ActiveRecord
|
|
|
662
664
|
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
|
663
665
|
else
|
|
664
666
|
column_alias = Arel.sql("count_column")
|
|
665
|
-
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
|
667
|
+
relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
|
|
666
668
|
end
|
|
667
669
|
|
|
668
670
|
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
|
669
671
|
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
|
670
672
|
|
|
671
|
-
|
|
673
|
+
if column_name == :all
|
|
674
|
+
relation.unscope(:order).build_subquery(subquery_alias, select_value)
|
|
675
|
+
else
|
|
676
|
+
relation.build_subquery(subquery_alias, select_value)
|
|
677
|
+
end
|
|
672
678
|
end
|
|
673
679
|
end
|
|
674
680
|
end
|
|
@@ -22,6 +22,9 @@ module ActiveRecord
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
module DelegateCache # :nodoc:
|
|
25
|
+
@delegate_base_methods = true
|
|
26
|
+
singleton_class.attr_accessor :delegate_base_methods
|
|
27
|
+
|
|
25
28
|
def relation_delegate_class(klass)
|
|
26
29
|
@relation_delegate_cache[klass]
|
|
27
30
|
end
|
|
@@ -75,12 +78,12 @@ module ActiveRecord
|
|
|
75
78
|
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
|
|
76
79
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
77
80
|
def #{method}(...)
|
|
78
|
-
scoping {
|
|
81
|
+
scoping { model.#{method}(...) }
|
|
79
82
|
end
|
|
80
83
|
RUBY
|
|
81
84
|
else
|
|
82
85
|
define_method(method) do |*args, **kwargs, &block|
|
|
83
|
-
scoping {
|
|
86
|
+
scoping { model.public_send(method, *args, **kwargs, &block) }
|
|
84
87
|
end
|
|
85
88
|
end
|
|
86
89
|
end
|
|
@@ -92,15 +95,15 @@ module ActiveRecord
|
|
|
92
95
|
|
|
93
96
|
# This module creates compiled delegation methods dynamically at runtime, which makes
|
|
94
97
|
# subsequent calls to that method faster by avoiding method_missing. The delegations
|
|
95
|
-
# may vary depending on the
|
|
96
|
-
# for each different
|
|
98
|
+
# may vary depending on the model of a relation, so we create a subclass of Relation
|
|
99
|
+
# for each different model, and the delegations are compiled into that subclass only.
|
|
97
100
|
|
|
98
101
|
delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
|
|
99
102
|
:[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
|
|
100
103
|
:to_sentence, :to_fs, :to_formatted_s, :as_json,
|
|
101
104
|
:shuffle, :split, :slice, :index, :rindex, to: :records
|
|
102
105
|
|
|
103
|
-
delegate :primary_key, :
|
|
106
|
+
delegate :primary_key, :with_connection, :connection, :table_name, :transaction, :sanitize_sql_like, :unscoped, :name, to: :model
|
|
104
107
|
|
|
105
108
|
module ClassSpecificRelation # :nodoc:
|
|
106
109
|
extend ActiveSupport::Concern
|
|
@@ -113,11 +116,19 @@ module ActiveRecord
|
|
|
113
116
|
|
|
114
117
|
private
|
|
115
118
|
def method_missing(method, ...)
|
|
116
|
-
if
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
if model.respond_to?(method)
|
|
120
|
+
if !DelegateCache.delegate_base_methods && Base.respond_to?(method)
|
|
121
|
+
# A common mistake in Active Record's own code is to call `ActiveRecord::Base`
|
|
122
|
+
# class methods on Association. It works because it's automatically delegated, but
|
|
123
|
+
# can introduce subtle bugs because it sets the global scope.
|
|
124
|
+
# We can't deprecate this behavior because gems might depend on it, however we
|
|
125
|
+
# can ban it from Active Record's own test suite to avoid regressions.
|
|
126
|
+
raise NotImplementedError, "Active Record code shouldn't rely on association delegation into ActiveRecord::Base methods"
|
|
127
|
+
elsif !Delegation.uncacheable_methods.include?(method)
|
|
128
|
+
model.generate_relation_method(method)
|
|
119
129
|
end
|
|
120
|
-
|
|
130
|
+
|
|
131
|
+
scoping { model.public_send(method, ...) }
|
|
121
132
|
else
|
|
122
133
|
super
|
|
123
134
|
end
|
|
@@ -125,19 +136,19 @@ module ActiveRecord
|
|
|
125
136
|
end
|
|
126
137
|
|
|
127
138
|
module ClassMethods # :nodoc:
|
|
128
|
-
def create(
|
|
129
|
-
relation_class_for(
|
|
139
|
+
def create(model, ...)
|
|
140
|
+
relation_class_for(model).new(model, ...)
|
|
130
141
|
end
|
|
131
142
|
|
|
132
143
|
private
|
|
133
|
-
def relation_class_for(
|
|
134
|
-
|
|
144
|
+
def relation_class_for(model)
|
|
145
|
+
model.relation_delegate_class(self)
|
|
135
146
|
end
|
|
136
147
|
end
|
|
137
148
|
|
|
138
149
|
private
|
|
139
150
|
def respond_to_missing?(method, _)
|
|
140
|
-
super ||
|
|
151
|
+
super || model.respond_to?(method)
|
|
141
152
|
end
|
|
142
153
|
end
|
|
143
154
|
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
|
|
@@ -145,10 +145,10 @@ module ActiveRecord
|
|
|
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
|
|
@@ -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,7 +639,7 @@ module ActiveRecord
|
|
|
638
639
|
end
|
|
639
640
|
|
|
640
641
|
def ordered_relation
|
|
641
|
-
if order_values.empty? && (implicit_order_column || !query_constraints_list.nil? || primary_key)
|
|
642
|
+
if order_values.empty? && (model.implicit_order_column || !model.query_constraints_list.nil? || primary_key)
|
|
642
643
|
order(_order_columns.map { |column| table[column].asc })
|
|
643
644
|
else
|
|
644
645
|
self
|
|
@@ -648,11 +649,11 @@ module ActiveRecord
|
|
|
648
649
|
def _order_columns
|
|
649
650
|
oc = []
|
|
650
651
|
|
|
651
|
-
oc << implicit_order_column if implicit_order_column
|
|
652
|
-
oc << query_constraints_list if query_constraints_list
|
|
652
|
+
oc << model.implicit_order_column if model.implicit_order_column
|
|
653
|
+
oc << model.query_constraints_list if model.query_constraints_list
|
|
653
654
|
|
|
654
|
-
if primary_key && query_constraints_list.nil?
|
|
655
|
-
oc << primary_key
|
|
655
|
+
if model.primary_key && model.query_constraints_list.nil?
|
|
656
|
+
oc << model.primary_key
|
|
656
657
|
end
|
|
657
658
|
|
|
658
659
|
oc.flatten.uniq.compact
|
|
@@ -24,7 +24,7 @@ module ActiveRecord
|
|
|
24
24
|
# the values.
|
|
25
25
|
def other
|
|
26
26
|
other = Relation.create(
|
|
27
|
-
relation.
|
|
27
|
+
relation.model,
|
|
28
28
|
table: relation.table,
|
|
29
29
|
predicate_builder: relation.predicate_builder
|
|
30
30
|
)
|
|
@@ -84,7 +84,7 @@ module ActiveRecord
|
|
|
84
84
|
def merge_select_values
|
|
85
85
|
return if other.select_values.empty?
|
|
86
86
|
|
|
87
|
-
if other.
|
|
87
|
+
if other.model == relation.model
|
|
88
88
|
relation.select_values |= other.select_values
|
|
89
89
|
else
|
|
90
90
|
relation.select_values |= other.instance_eval do
|
|
@@ -96,12 +96,12 @@ module ActiveRecord
|
|
|
96
96
|
def merge_preloads
|
|
97
97
|
return if other.preload_values.empty? && other.includes_values.empty?
|
|
98
98
|
|
|
99
|
-
if other.
|
|
99
|
+
if other.model == relation.model
|
|
100
100
|
relation.preload_values |= other.preload_values unless other.preload_values.empty?
|
|
101
101
|
relation.includes_values |= other.includes_values unless other.includes_values.empty?
|
|
102
102
|
else
|
|
103
|
-
reflection = relation.
|
|
104
|
-
r.class_name == other.
|
|
103
|
+
reflection = relation.model.reflect_on_all_associations.find do |r|
|
|
104
|
+
r.class_name == other.model.name
|
|
105
105
|
end || return
|
|
106
106
|
|
|
107
107
|
unless other.preload_values.empty?
|
|
@@ -117,7 +117,7 @@ module ActiveRecord
|
|
|
117
117
|
def merge_joins
|
|
118
118
|
return if other.joins_values.empty?
|
|
119
119
|
|
|
120
|
-
if other.
|
|
120
|
+
if other.model == relation.model
|
|
121
121
|
relation.joins_values |= other.joins_values
|
|
122
122
|
else
|
|
123
123
|
associations, others = other.joins_values.partition do |join|
|
|
@@ -136,7 +136,7 @@ module ActiveRecord
|
|
|
136
136
|
def merge_outer_joins
|
|
137
137
|
return if other.left_outer_joins_values.empty?
|
|
138
138
|
|
|
139
|
-
if other.
|
|
139
|
+
if other.model == relation.model
|
|
140
140
|
relation.left_outer_joins_values |= other.left_outer_joins_values
|
|
141
141
|
else
|
|
142
142
|
associations, others = other.left_outer_joins_values.partition do |join|
|
|
@@ -185,7 +185,7 @@ module ActiveRecord
|
|
|
185
185
|
|
|
186
186
|
def replace_from_clause?
|
|
187
187
|
relation.from_clause.empty? && !other.from_clause.empty? &&
|
|
188
|
-
relation.
|
|
188
|
+
relation.model.base_class == other.model.base_class
|
|
189
189
|
end
|
|
190
190
|
end
|
|
191
191
|
end
|
|
@@ -31,7 +31,9 @@ module ActiveRecord
|
|
|
31
31
|
values_predicate
|
|
32
32
|
else
|
|
33
33
|
array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
|
|
34
|
-
|
|
34
|
+
values_predicate.or(
|
|
35
|
+
Arel::Nodes::Grouping.new Arel::Nodes::Or.new(array_predicates)
|
|
36
|
+
)
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
39
|
|