activerecord 7.1.5.1 → 8.0.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 +369 -2484
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +21 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +92 -295
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +63 -49
- data/lib/active_record/autosave_association.rb +92 -57
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
- data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
- 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 +48 -12
- data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
- data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- 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 +36 -20
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
- data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +65 -0
- data/lib/active_record/connection_handling.rb +74 -37
- data/lib/active_record/core.rb +132 -51
- data/lib/active_record/counter_cache.rb +19 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +23 -4
- data/lib/active_record/database_configurations/hash_config.rb +46 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +41 -17
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +7 -7
- data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
- data/lib/active_record/encryption/encryptor.rb +28 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +20 -16
- data/lib/active_record/errors.rb +54 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -33
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +19 -16
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/log_subscriber.rb +5 -32
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +33 -14
- data/lib/active_record/migration/compatibility.rb +8 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +104 -98
- data/lib/active_record/model_schema.rb +32 -70
- data/lib/active_record/nested_attributes.rb +15 -9
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +127 -451
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +104 -37
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +24 -12
- data/lib/active_record/railtie.rb +26 -68
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +43 -61
- data/lib/active_record/reflection.rb +112 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +138 -72
- data/lib/active_record/relation/calculations.rb +122 -82
- data/lib/active_record/relation/delegation.rb +30 -22
- data/lib/active_record/relation/finder_methods.rb +32 -18
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
- 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 +16 -3
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +317 -101
- data/lib/active_record/relation/spawn_methods.rb +3 -19
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +561 -119
- data/lib/active_record/result.rb +95 -46
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +31 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +53 -20
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/signed_id.rb +24 -4
- data/lib/active_record/statement_cache.rb +19 -19
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +2 -13
- data/lib/active_record/tasks/database_tasks.rb +87 -58
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
- data/lib/active_record/test_fixtures.rb +98 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +72 -17
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +138 -57
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +5 -4
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +18 -16
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -234,7 +234,7 @@ module ActiveRecord
|
|
234
234
|
if operation == "count"
|
235
235
|
unless distinct_value || distinct_select?(column_name || select_for_count)
|
236
236
|
relation.distinct!
|
237
|
-
relation.select_values =
|
237
|
+
relation.select_values = Array(model.primary_key || table[Arel.star])
|
238
238
|
end
|
239
239
|
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
240
240
|
relation.order_values = [] if group_values.empty?
|
@@ -275,6 +275,14 @@ module ActiveRecord
|
|
275
275
|
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
|
276
276
|
# # => [2, 3]
|
277
277
|
#
|
278
|
+
# Comment.joins(:person).pluck(:id, person: :id)
|
279
|
+
# # SELECT comments.id, person.id FROM comments INNER JOIN people person ON person.id = comments.person_id
|
280
|
+
# # => [[1, 2], [2, 2]]
|
281
|
+
#
|
282
|
+
# Comment.joins(:person).pluck(:id, person: [:id, :name])
|
283
|
+
# # SELECT comments.id, person.id, person.name FROM comments INNER JOIN people person ON person.id = comments.person_id
|
284
|
+
# # => [[1, 2, 'David'], [2, 2, 'David']]
|
285
|
+
#
|
278
286
|
# Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
|
279
287
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
280
288
|
# # => ['0', '27761', '173']
|
@@ -302,15 +310,17 @@ module ActiveRecord
|
|
302
310
|
relation = apply_join_dependency
|
303
311
|
relation.pluck(*column_names)
|
304
312
|
else
|
305
|
-
|
306
|
-
columns = arel_columns(column_names)
|
313
|
+
model.disallow_raw_sql!(flattened_args(column_names))
|
307
314
|
relation = spawn
|
315
|
+
columns = relation.arel_columns(column_names)
|
308
316
|
relation.select_values = columns
|
309
317
|
result = skip_query_cache_if_necessary do
|
310
318
|
if where_clause.contradiction?
|
311
319
|
ActiveRecord::Result.empty(async: @async)
|
312
320
|
else
|
313
|
-
|
321
|
+
model.with_connection do |c|
|
322
|
+
c.select_all(relation.arel, "#{model.name} Pluck", async: @async)
|
323
|
+
end
|
314
324
|
end
|
315
325
|
end
|
316
326
|
result.then do |result|
|
@@ -357,7 +367,7 @@ module ActiveRecord
|
|
357
367
|
# Returns the base model's ID's for the relation using the table's primary key
|
358
368
|
#
|
359
369
|
# Person.ids # SELECT people.id FROM people
|
360
|
-
# Person.joins(:
|
370
|
+
# Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
|
361
371
|
def ids
|
362
372
|
primary_key_array = Array(primary_key)
|
363
373
|
|
@@ -385,7 +395,9 @@ module ActiveRecord
|
|
385
395
|
ActiveRecord::Result.empty
|
386
396
|
else
|
387
397
|
skip_query_cache_if_necessary do
|
388
|
-
|
398
|
+
model.with_connection do |c|
|
399
|
+
c.select_all(relation, "#{model.name} Ids", async: @async)
|
400
|
+
end
|
389
401
|
end
|
390
402
|
end
|
391
403
|
|
@@ -398,9 +410,21 @@ module ActiveRecord
|
|
398
410
|
async.ids
|
399
411
|
end
|
400
412
|
|
413
|
+
protected
|
414
|
+
def aggregate_column(column_name)
|
415
|
+
case column_name
|
416
|
+
when Arel::Expressions
|
417
|
+
column_name
|
418
|
+
when :all
|
419
|
+
Arel.star
|
420
|
+
else
|
421
|
+
arel_column(column_name)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
401
425
|
private
|
402
426
|
def all_attributes?(column_names)
|
403
|
-
(column_names.map(&:to_s) -
|
427
|
+
(column_names.map(&:to_s) - model.attribute_names - model.attribute_aliases.keys).empty?
|
404
428
|
end
|
405
429
|
|
406
430
|
def has_include?(column_name)
|
@@ -438,20 +462,12 @@ module ActiveRecord
|
|
438
462
|
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
439
463
|
end
|
440
464
|
|
441
|
-
def aggregate_column(column_name)
|
442
|
-
return column_name if Arel::Expressions === column_name
|
443
|
-
|
444
|
-
arel_column(column_name.to_s) do |name|
|
445
|
-
Arel.sql(column_name == :all ? "*" : name)
|
446
|
-
end
|
447
|
-
end
|
448
|
-
|
449
465
|
def operation_over_aggregate_column(column, operation, distinct)
|
450
466
|
operation == "count" ? column.count(distinct) : column.public_send(operation)
|
451
467
|
end
|
452
468
|
|
453
469
|
def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
|
454
|
-
if operation
|
470
|
+
if build_count_subquery?(operation, column_name, distinct)
|
455
471
|
# Shortcut when limit is zero.
|
456
472
|
return 0 if limit_value == 0
|
457
473
|
|
@@ -461,7 +477,7 @@ module ActiveRecord
|
|
461
477
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
462
478
|
relation = unscope(:order).distinct!(false)
|
463
479
|
|
464
|
-
column = aggregate_column(column_name)
|
480
|
+
column = relation.aggregate_column(column_name)
|
465
481
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
466
482
|
select_value.distinct = true if operation == "sum" && distinct
|
467
483
|
|
@@ -471,10 +487,16 @@ module ActiveRecord
|
|
471
487
|
end
|
472
488
|
|
473
489
|
query_result = if relation.where_clause.contradiction?
|
474
|
-
|
490
|
+
if @async
|
491
|
+
FutureResult.wrap(ActiveRecord::Result.empty)
|
492
|
+
else
|
493
|
+
ActiveRecord::Result.empty
|
494
|
+
end
|
475
495
|
else
|
476
496
|
skip_query_cache_if_necessary do
|
477
|
-
|
497
|
+
model.with_connection do |c|
|
498
|
+
c.select_all(query_builder, "#{model.name} #{operation.capitalize}", async: @async)
|
499
|
+
end
|
478
500
|
end
|
479
501
|
end
|
480
502
|
|
@@ -494,81 +516,87 @@ module ActiveRecord
|
|
494
516
|
group_fields = group_fields.uniq if group_fields.size > 1
|
495
517
|
|
496
518
|
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
497
|
-
association =
|
519
|
+
association = model._reflect_on_association(group_fields.first)
|
498
520
|
associated = association && association.belongs_to? # only count belongs_to associations
|
499
521
|
group_fields = Array(association.foreign_key) if associated
|
500
522
|
end
|
501
|
-
group_fields = arel_columns(group_fields)
|
502
523
|
|
503
|
-
|
524
|
+
relation = except(:group).distinct!(false)
|
525
|
+
group_fields = relation.arel_columns(group_fields)
|
504
526
|
|
505
|
-
|
506
|
-
|
507
|
-
column_alias_tracker.alias_for(field.to_s.downcase)
|
508
|
-
}
|
509
|
-
group_columns = group_aliases.zip(group_fields)
|
527
|
+
model.with_connection do |connection|
|
528
|
+
column_alias_tracker = ColumnAliasTracker.new(connection)
|
510
529
|
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
530
|
+
group_aliases = group_fields.map { |field|
|
531
|
+
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
532
|
+
column_alias_tracker.alias_for(field.to_s.downcase)
|
533
|
+
}
|
534
|
+
group_columns = group_aliases.zip(group_fields)
|
515
535
|
|
516
|
-
|
517
|
-
|
536
|
+
column = relation.aggregate_column(column_name)
|
537
|
+
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
538
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
539
|
+
select_value.as(model.adapter_class.quote_column_name(column_alias))
|
518
540
|
|
519
|
-
|
520
|
-
|
521
|
-
if field.respond_to?(:as)
|
522
|
-
field.as(aliaz)
|
523
|
-
else
|
524
|
-
"#{field} AS #{aliaz}"
|
525
|
-
end
|
526
|
-
}
|
541
|
+
select_values = [select_value]
|
542
|
+
select_values += self.select_values unless having_clause.empty?
|
527
543
|
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
537
|
-
key_records = key_records.index_by(&:id)
|
538
|
-
end
|
544
|
+
select_values.concat group_columns.map { |aliaz, field|
|
545
|
+
aliaz = model.adapter_class.quote_column_name(aliaz)
|
546
|
+
if field.respond_to?(:as)
|
547
|
+
field.as(aliaz)
|
548
|
+
else
|
549
|
+
"#{field} AS #{aliaz}"
|
550
|
+
end
|
551
|
+
}
|
539
552
|
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
553
|
+
relation.group_values = group_fields
|
554
|
+
relation.select_values = select_values
|
555
|
+
|
556
|
+
result = skip_query_cache_if_necessary do
|
557
|
+
connection.select_all(relation.arel, "#{model.name} #{operation.capitalize}", async: @async)
|
545
558
|
end
|
546
559
|
|
547
|
-
|
548
|
-
|
549
|
-
|
560
|
+
result.then do |calculated_data|
|
561
|
+
if association
|
562
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
563
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
564
|
+
key_records = key_records.index_by(&:id)
|
550
565
|
end
|
551
|
-
end
|
552
566
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
567
|
+
key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
|
568
|
+
types[aliaz] = col_name.try(:type_caster) ||
|
569
|
+
type_for(col_name) do
|
570
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
571
|
+
end
|
572
|
+
end
|
558
573
|
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
574
|
+
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
575
|
+
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
576
|
+
hash[col_name] = row[i]
|
577
|
+
end
|
578
|
+
end
|
563
579
|
|
564
|
-
|
580
|
+
if operation != "count"
|
581
|
+
type = column.try(:type_caster) ||
|
582
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
583
|
+
type = type.subtype if Enum::EnumType === type
|
584
|
+
end
|
585
|
+
|
586
|
+
hash_rows.each_with_object({}) do |row, result|
|
587
|
+
key = group_aliases.map { |aliaz| row[aliaz] }
|
588
|
+
key = key.first if key.size == 1
|
589
|
+
key = key_records[key] if associated
|
590
|
+
|
591
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation, type)
|
592
|
+
end
|
565
593
|
end
|
566
594
|
end
|
567
595
|
end
|
568
596
|
|
569
597
|
def type_for(field, &block)
|
570
598
|
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
571
|
-
|
599
|
+
model.type_for_attribute(field_name, &block)
|
572
600
|
end
|
573
601
|
|
574
602
|
def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
|
@@ -581,15 +609,15 @@ module ActiveRecord
|
|
581
609
|
|
582
610
|
def type_cast_pluck_values(result, columns)
|
583
611
|
cast_types = if result.columns.size != columns.size
|
584
|
-
|
612
|
+
model.attribute_types
|
585
613
|
else
|
586
614
|
join_dependencies = nil
|
587
615
|
columns.map.with_index do |column, i|
|
588
616
|
column.try(:type_caster) ||
|
589
|
-
|
617
|
+
model.attribute_types.fetch(name = result.columns[i]) do
|
590
618
|
join_dependencies ||= build_join_dependencies
|
591
619
|
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
|
592
|
-
result.column_types[
|
620
|
+
result.column_types[i] || Type.default_value
|
593
621
|
end
|
594
622
|
end
|
595
623
|
end
|
@@ -615,27 +643,39 @@ module ActiveRecord
|
|
615
643
|
end
|
616
644
|
|
617
645
|
def select_for_count
|
618
|
-
if select_values.
|
619
|
-
return select_values.first if select_values.one?
|
620
|
-
select_values.join(", ")
|
621
|
-
else
|
646
|
+
if select_values.empty?
|
622
647
|
:all
|
648
|
+
else
|
649
|
+
with_connection do |conn|
|
650
|
+
arel_columns(select_values).map { |column| conn.visitor.compile(column) }.join(", ")
|
651
|
+
end
|
623
652
|
end
|
624
653
|
end
|
625
654
|
|
655
|
+
def build_count_subquery?(operation, column_name, distinct)
|
656
|
+
# SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or
|
657
|
+
# multiple columns, so we need to use subquery for this.
|
658
|
+
operation == "count" &&
|
659
|
+
(((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?)
|
660
|
+
end
|
661
|
+
|
626
662
|
def build_count_subquery(relation, column_name, distinct)
|
627
663
|
if column_name == :all
|
628
664
|
column_alias = Arel.star
|
629
665
|
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
630
666
|
else
|
631
667
|
column_alias = Arel.sql("count_column")
|
632
|
-
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
668
|
+
relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
|
633
669
|
end
|
634
670
|
|
635
|
-
subquery_alias = Arel.sql("subquery_for_count")
|
671
|
+
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
636
672
|
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
637
673
|
|
638
|
-
|
674
|
+
if column_name == :all
|
675
|
+
relation.unscope(:order).build_subquery(subquery_alias, select_value)
|
676
|
+
else
|
677
|
+
relation.build_subquery(subquery_alias, select_value)
|
678
|
+
end
|
639
679
|
end
|
640
680
|
end
|
641
681
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "mutex_m"
|
4
3
|
require "active_support/core_ext/module/delegation"
|
5
4
|
|
6
5
|
module ActiveRecord
|
@@ -23,6 +22,9 @@ module ActiveRecord
|
|
23
22
|
end
|
24
23
|
|
25
24
|
module DelegateCache # :nodoc:
|
25
|
+
@delegate_base_methods = true
|
26
|
+
singleton_class.attr_accessor :delegate_base_methods
|
27
|
+
|
26
28
|
def relation_delegate_class(klass)
|
27
29
|
@relation_delegate_cache[klass]
|
28
30
|
end
|
@@ -67,23 +69,22 @@ module ActiveRecord
|
|
67
69
|
end
|
68
70
|
|
69
71
|
class GeneratedRelationMethods < Module # :nodoc:
|
70
|
-
|
72
|
+
MUTEX = Mutex.new
|
71
73
|
|
72
74
|
def generate_method(method)
|
73
|
-
synchronize do
|
75
|
+
MUTEX.synchronize do
|
74
76
|
return if method_defined?(method)
|
75
77
|
|
76
|
-
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) &&
|
78
|
+
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
|
77
79
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
78
80
|
def #{method}(...)
|
79
|
-
scoping {
|
81
|
+
scoping { model.#{method}(...) }
|
80
82
|
end
|
81
83
|
RUBY
|
82
84
|
else
|
83
|
-
define_method(method) do |*args, &block|
|
84
|
-
scoping {
|
85
|
+
define_method(method) do |*args, **kwargs, &block|
|
86
|
+
scoping { model.public_send(method, *args, **kwargs, &block) }
|
85
87
|
end
|
86
|
-
ruby2_keywords(method)
|
87
88
|
end
|
88
89
|
end
|
89
90
|
end
|
@@ -94,15 +95,15 @@ module ActiveRecord
|
|
94
95
|
|
95
96
|
# This module creates compiled delegation methods dynamically at runtime, which makes
|
96
97
|
# subsequent calls to that method faster by avoiding method_missing. The delegations
|
97
|
-
# may vary depending on the
|
98
|
-
# 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.
|
99
100
|
|
100
101
|
delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
|
101
102
|
:[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
|
102
103
|
:to_sentence, :to_fs, :to_formatted_s, :as_json,
|
103
104
|
:shuffle, :split, :slice, :index, :rindex, to: :records
|
104
105
|
|
105
|
-
delegate :primary_key, :connection, :transaction, to: :
|
106
|
+
delegate :primary_key, :with_connection, :connection, :table_name, :transaction, :sanitize_sql_like, :unscoped, :name, to: :model
|
106
107
|
|
107
108
|
module ClassSpecificRelation # :nodoc:
|
108
109
|
extend ActiveSupport::Concern
|
@@ -114,33 +115,40 @@ module ActiveRecord
|
|
114
115
|
end
|
115
116
|
|
116
117
|
private
|
117
|
-
def method_missing(method,
|
118
|
-
if
|
119
|
-
|
120
|
-
|
118
|
+
def method_missing(method, ...)
|
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)
|
121
129
|
end
|
122
|
-
|
130
|
+
|
131
|
+
scoping { model.public_send(method, ...) }
|
123
132
|
else
|
124
133
|
super
|
125
134
|
end
|
126
135
|
end
|
127
|
-
ruby2_keywords(:method_missing)
|
128
136
|
end
|
129
137
|
|
130
138
|
module ClassMethods # :nodoc:
|
131
|
-
def create(
|
132
|
-
relation_class_for(
|
139
|
+
def create(model, ...)
|
140
|
+
relation_class_for(model).new(model, ...)
|
133
141
|
end
|
134
142
|
|
135
143
|
private
|
136
|
-
def relation_class_for(
|
137
|
-
|
144
|
+
def relation_class_for(model)
|
145
|
+
model.relation_delegate_class(self)
|
138
146
|
end
|
139
147
|
end
|
140
148
|
|
141
149
|
private
|
142
150
|
def respond_to_missing?(method, _)
|
143
|
-
super ||
|
151
|
+
super || model.respond_to?(method)
|
144
152
|
end
|
145
153
|
end
|
146
154
|
end
|
@@ -87,6 +87,14 @@ module ActiveRecord
|
|
87
87
|
#
|
88
88
|
# Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
|
89
89
|
# # returns an Array of the required fields.
|
90
|
+
#
|
91
|
+
# ==== Edge Cases
|
92
|
+
#
|
93
|
+
# Person.find(37) # raises ActiveRecord::RecordNotFound exception if the record with the given ID does not exist.
|
94
|
+
# Person.find([37]) # raises ActiveRecord::RecordNotFound exception if the record with the given ID in the input array does not exist.
|
95
|
+
# Person.find(nil) # raises ActiveRecord::RecordNotFound exception if the argument is nil.
|
96
|
+
# Person.find([]) # returns an empty array if the argument is an empty array.
|
97
|
+
# Person.find # raises ActiveRecord::RecordNotFound exception if the argument is not provided.
|
90
98
|
def find(*args)
|
91
99
|
return super if block_given?
|
92
100
|
find_with_ids(*args)
|
@@ -137,10 +145,10 @@ module ActiveRecord
|
|
137
145
|
|
138
146
|
if found.nil?
|
139
147
|
raise_record_not_found_exception!
|
140
|
-
elsif undesired.
|
141
|
-
raise ActiveRecord::SoleRecordExceeded.new(self)
|
142
|
-
else
|
148
|
+
elsif undesired.nil?
|
143
149
|
found
|
150
|
+
else
|
151
|
+
raise ActiveRecord::SoleRecordExceeded.new(model)
|
144
152
|
end
|
145
153
|
end
|
146
154
|
|
@@ -366,7 +374,11 @@ module ActiveRecord
|
|
366
374
|
relation = construct_relation_for_exists(conditions)
|
367
375
|
return false if relation.where_clause.contradiction?
|
368
376
|
|
369
|
-
skip_query_cache_if_necessary
|
377
|
+
skip_query_cache_if_necessary do
|
378
|
+
with_connection do |c|
|
379
|
+
c.select_rows(relation.arel, "#{model.name} Exists?").size == 1
|
380
|
+
end
|
381
|
+
end
|
370
382
|
end
|
371
383
|
|
372
384
|
# Returns true if the relation contains the given record or false otherwise.
|
@@ -377,7 +389,7 @@ module ActiveRecord
|
|
377
389
|
def include?(record)
|
378
390
|
# The existing implementation relies on receiving an Active Record instance as the input parameter named record.
|
379
391
|
# Any non-Active Record object passed to this implementation is guaranteed to return `false`.
|
380
|
-
return false unless record.is_a?(
|
392
|
+
return false unless record.is_a?(model)
|
381
393
|
|
382
394
|
if loaded? || offset_value || limit_value || having_clause.any?
|
383
395
|
records.include?(record)
|
@@ -403,9 +415,9 @@ module ActiveRecord
|
|
403
415
|
# the expected number of results should be provided in the +expected_size+
|
404
416
|
# argument.
|
405
417
|
def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
|
406
|
-
conditions = " [#{arel.where_sql(
|
418
|
+
conditions = " [#{arel.where_sql(model)}]" unless where_clause.empty?
|
407
419
|
|
408
|
-
name =
|
420
|
+
name = model.name
|
409
421
|
|
410
422
|
if ids.nil?
|
411
423
|
error = +"Couldn't find #{name}"
|
@@ -459,7 +471,9 @@ module ActiveRecord
|
|
459
471
|
)
|
460
472
|
)
|
461
473
|
relation = skip_query_cache_if_necessary do
|
462
|
-
|
474
|
+
model.with_connection do |c|
|
475
|
+
c.distinct_relation_for_primary_key(relation)
|
476
|
+
end
|
463
477
|
end
|
464
478
|
end
|
465
479
|
|
@@ -475,9 +489,9 @@ module ActiveRecord
|
|
475
489
|
end
|
476
490
|
|
477
491
|
def find_with_ids(*ids)
|
478
|
-
raise UnknownPrimaryKey.new(
|
492
|
+
raise UnknownPrimaryKey.new(model) if primary_key.nil?
|
479
493
|
|
480
|
-
expects_array = if
|
494
|
+
expects_array = if model.composite_primary_key?
|
481
495
|
ids.first.first.is_a?(Array)
|
482
496
|
else
|
483
497
|
ids.first.is_a?(Array)
|
@@ -489,7 +503,7 @@ module ActiveRecord
|
|
489
503
|
|
490
504
|
ids = ids.compact.uniq
|
491
505
|
|
492
|
-
model_name =
|
506
|
+
model_name = model.name
|
493
507
|
|
494
508
|
case ids.size
|
495
509
|
when 0
|
@@ -511,7 +525,7 @@ module ActiveRecord
|
|
511
525
|
MSG
|
512
526
|
end
|
513
527
|
|
514
|
-
relation = if
|
528
|
+
relation = if model.composite_primary_key?
|
515
529
|
where(primary_key.zip(id).to_h)
|
516
530
|
else
|
517
531
|
where(primary_key => id)
|
@@ -559,7 +573,7 @@ module ActiveRecord
|
|
559
573
|
result = relation.records
|
560
574
|
|
561
575
|
if result.size == ids.size
|
562
|
-
result.in_order_of(:id, ids.map { |id|
|
576
|
+
result.in_order_of(:id, ids.map { |id| model.type_for_attribute(primary_key).cast(id) })
|
563
577
|
else
|
564
578
|
raise_record_not_found_exception!(ids, result.size, ids.size)
|
565
579
|
end
|
@@ -624,7 +638,7 @@ module ActiveRecord
|
|
624
638
|
end
|
625
639
|
|
626
640
|
def ordered_relation
|
627
|
-
if order_values.empty? && (implicit_order_column || !query_constraints_list.nil? || primary_key)
|
641
|
+
if order_values.empty? && (model.implicit_order_column || !model.query_constraints_list.nil? || primary_key)
|
628
642
|
order(_order_columns.map { |column| table[column].asc })
|
629
643
|
else
|
630
644
|
self
|
@@ -634,11 +648,11 @@ module ActiveRecord
|
|
634
648
|
def _order_columns
|
635
649
|
oc = []
|
636
650
|
|
637
|
-
oc << implicit_order_column if implicit_order_column
|
638
|
-
oc << query_constraints_list if query_constraints_list
|
651
|
+
oc << model.implicit_order_column if model.implicit_order_column
|
652
|
+
oc << model.query_constraints_list if model.query_constraints_list
|
639
653
|
|
640
|
-
if primary_key && query_constraints_list.nil?
|
641
|
-
oc << primary_key
|
654
|
+
if model.primary_key && model.query_constraints_list.nil?
|
655
|
+
oc << model.primary_key
|
642
656
|
end
|
643
657
|
|
644
658
|
oc.flatten.uniq.compact
|