activerecord 7.1.5.1 → 7.2.0.beta1
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 +515 -2445
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +14 -7
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- 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 +6 -4
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
- data/lib/active_record/associations/join_dependency.rb +5 -5
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- 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 +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +33 -16
- data/lib/active_record/attribute_assignment.rb +1 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +1 -1
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +4 -16
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -10
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +60 -71
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +13 -32
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- 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/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -13
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +53 -37
- data/lib/active_record/counter_cache.rb +18 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +15 -4
- data/lib/active_record/database_configurations/hash_config.rb +38 -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 +24 -0
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +2 -2
- data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
- data/lib/active_record/encryption/encryptor.rb +17 -2
- 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.rb +0 -2
- data/lib/active_record/enum.rb +10 -1
- data/lib/active_record/errors.rb +16 -11
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +8 -4
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +1 -4
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +5 -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 +85 -76
- data/lib/active_record/model_schema.rb +28 -68
- data/lib/active_record/nested_attributes.rb +13 -16
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +50 -62
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +41 -44
- data/lib/active_record/reflection.rb +90 -35
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +94 -61
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -57
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +496 -72
- data/lib/active_record/result.rb +31 -44
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/signed_id.rb +11 -1
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +76 -70
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +81 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +1 -1
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +68 -0
- data/lib/active_record/transactions.rb +43 -14
- 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 +14 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +149 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/nodes/binary.rb +0 -6
- 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 +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- 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/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/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- metadata +20 -15
@@ -11,6 +11,7 @@ module ActiveRecord
|
|
11
11
|
class_attribute :_reflections, instance_writer: false, default: {}
|
12
12
|
class_attribute :aggregate_reflections, instance_writer: false, default: {}
|
13
13
|
class_attribute :automatic_scope_inversing, instance_writer: false, default: false
|
14
|
+
class_attribute :automatically_invert_plural_associations, instance_writer: false, default: false
|
14
15
|
end
|
15
16
|
|
16
17
|
class << self
|
@@ -21,12 +22,12 @@ module ActiveRecord
|
|
21
22
|
|
22
23
|
def add_reflection(ar, name, reflection)
|
23
24
|
ar.clear_reflections_cache
|
24
|
-
name =
|
25
|
+
name = name.to_sym
|
25
26
|
ar._reflections = ar._reflections.except(name).merge!(name => reflection)
|
26
27
|
end
|
27
28
|
|
28
29
|
def add_aggregate_reflection(ar, name, reflection)
|
29
|
-
ar.aggregate_reflections = ar.aggregate_reflections.merge(
|
30
|
+
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_sym => reflection)
|
30
31
|
end
|
31
32
|
|
32
33
|
private
|
@@ -67,7 +68,7 @@ module ActiveRecord
|
|
67
68
|
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
|
68
69
|
#
|
69
70
|
def reflect_on_aggregation(aggregation)
|
70
|
-
aggregate_reflections[aggregation.
|
71
|
+
aggregate_reflections[aggregation.to_sym]
|
71
72
|
end
|
72
73
|
|
73
74
|
# Returns a Hash of name of the reflection as the key and an AssociationReflection as the value.
|
@@ -75,6 +76,10 @@ module ActiveRecord
|
|
75
76
|
# Account.reflections # => {"balance" => AggregateReflection}
|
76
77
|
#
|
77
78
|
def reflections
|
79
|
+
normalized_reflections.stringify_keys
|
80
|
+
end
|
81
|
+
|
82
|
+
def normalized_reflections # :nodoc
|
78
83
|
@__reflections ||= begin
|
79
84
|
ref = {}
|
80
85
|
|
@@ -83,13 +88,13 @@ module ActiveRecord
|
|
83
88
|
|
84
89
|
if parent_reflection
|
85
90
|
parent_name = parent_reflection.name
|
86
|
-
ref[parent_name
|
91
|
+
ref[parent_name] = parent_reflection
|
87
92
|
else
|
88
93
|
ref[name] = reflection
|
89
94
|
end
|
90
95
|
end
|
91
96
|
|
92
|
-
ref
|
97
|
+
ref.freeze
|
93
98
|
end
|
94
99
|
end
|
95
100
|
|
@@ -104,7 +109,7 @@ module ActiveRecord
|
|
104
109
|
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
105
110
|
#
|
106
111
|
def reflect_on_all_associations(macro = nil)
|
107
|
-
association_reflections =
|
112
|
+
association_reflections = normalized_reflections.values
|
108
113
|
association_reflections.select! { |reflection| reflection.macro == macro } if macro
|
109
114
|
association_reflections
|
110
115
|
end
|
@@ -115,16 +120,18 @@ module ActiveRecord
|
|
115
120
|
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
116
121
|
#
|
117
122
|
def reflect_on_association(association)
|
118
|
-
|
123
|
+
normalized_reflections[association.to_sym]
|
119
124
|
end
|
120
125
|
|
121
126
|
def _reflect_on_association(association) # :nodoc:
|
122
|
-
_reflections[association.
|
127
|
+
_reflections[association.to_sym]
|
123
128
|
end
|
124
129
|
|
125
130
|
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
126
131
|
def reflect_on_all_autosave_associations
|
127
|
-
reflections
|
132
|
+
reflections = normalized_reflections.values
|
133
|
+
reflections.select! { |reflection| reflection.options[:autosave] }
|
134
|
+
reflections
|
128
135
|
end
|
129
136
|
|
130
137
|
def clear_reflections_cache # :nodoc:
|
@@ -235,14 +242,16 @@ module ActiveRecord
|
|
235
242
|
end
|
236
243
|
|
237
244
|
def counter_cache_column
|
238
|
-
@counter_cache_column ||=
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
245
|
+
@counter_cache_column ||= begin
|
246
|
+
counter_cache = options[:counter_cache]
|
247
|
+
|
248
|
+
if belongs_to?
|
249
|
+
if counter_cache
|
250
|
+
counter_cache[:column] || -"#{active_record.name.demodulize.underscore.pluralize}_count"
|
251
|
+
end
|
252
|
+
else
|
253
|
+
-((counter_cache && -counter_cache[:column]) || "#{name}_count")
|
243
254
|
end
|
244
|
-
else
|
245
|
-
-(options[:counter_cache]&.to_s || "#{name}_count")
|
246
255
|
end
|
247
256
|
end
|
248
257
|
|
@@ -291,7 +300,7 @@ module ActiveRecord
|
|
291
300
|
inverse_of && inverse_which_updates_counter_cache == inverse_of
|
292
301
|
end
|
293
302
|
|
294
|
-
# Returns whether a counter cache
|
303
|
+
# Returns whether this association has a counter cache.
|
295
304
|
#
|
296
305
|
# The counter_cache option must be given on either the owner or inverse
|
297
306
|
# association, and the column must be present on the owner.
|
@@ -301,6 +310,17 @@ module ActiveRecord
|
|
301
310
|
active_record.has_attribute?(counter_cache_column)
|
302
311
|
end
|
303
312
|
|
313
|
+
# Returns whether this association has a counter cache and its column values were backfilled
|
314
|
+
# (and so it is used internally by methods like +size+/+any?+/etc).
|
315
|
+
def has_active_cached_counter?
|
316
|
+
return false unless has_cached_counter?
|
317
|
+
|
318
|
+
counter_cache = options[:counter_cache] ||
|
319
|
+
(inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache])
|
320
|
+
|
321
|
+
counter_cache[:active] != false
|
322
|
+
end
|
323
|
+
|
304
324
|
def counter_must_be_updated_by_has_many?
|
305
325
|
!inverse_updates_counter_in_memory? && has_cached_counter?
|
306
326
|
end
|
@@ -377,12 +397,11 @@ module ActiveRecord
|
|
377
397
|
super()
|
378
398
|
@name = name
|
379
399
|
@scope = scope
|
380
|
-
@options = options
|
400
|
+
@options = normalize_options(options)
|
381
401
|
@active_record = active_record
|
382
402
|
@klass = options[:anonymous_class]
|
383
403
|
@plural_name = active_record.pluralize_table_names ?
|
384
404
|
name.to_s.pluralize : name.to_s
|
385
|
-
validate_reflection!
|
386
405
|
end
|
387
406
|
|
388
407
|
def autosave=(autosave)
|
@@ -409,13 +428,17 @@ module ActiveRecord
|
|
409
428
|
# a new association object. Use +build_association+ or +create_association+
|
410
429
|
# instead. This allows plugins to hook into association object creation.
|
411
430
|
def klass
|
412
|
-
@klass ||= compute_class(class_name)
|
431
|
+
@klass ||= compute_class(compute_name(class_name))
|
413
432
|
end
|
414
433
|
|
415
434
|
def compute_class(name)
|
416
435
|
name.constantize
|
417
436
|
end
|
418
437
|
|
438
|
+
def compute_name(name) # :nodoc:
|
439
|
+
active_record.name.demodulize == name ? "::#{name}" : name
|
440
|
+
end
|
441
|
+
|
419
442
|
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
|
420
443
|
# and +other_aggregation+ has an options hash assigned to it.
|
421
444
|
def ==(other_aggregation)
|
@@ -435,15 +458,24 @@ module ActiveRecord
|
|
435
458
|
name.to_s.camelize
|
436
459
|
end
|
437
460
|
|
438
|
-
def
|
439
|
-
|
461
|
+
def normalize_options(options)
|
462
|
+
counter_cache = options.delete(:counter_cache)
|
440
463
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
464
|
+
if counter_cache
|
465
|
+
active = true
|
466
|
+
|
467
|
+
case counter_cache
|
468
|
+
when String, Symbol
|
469
|
+
column = -counter_cache.to_s
|
470
|
+
when Hash
|
471
|
+
active = counter_cache.fetch(:active, true)
|
472
|
+
column = counter_cache[:column]&.to_s
|
473
|
+
end
|
474
|
+
|
475
|
+
options[:counter_cache] = { active: active, column: column }
|
476
|
+
end
|
477
|
+
|
478
|
+
options
|
447
479
|
end
|
448
480
|
end
|
449
481
|
|
@@ -494,6 +526,17 @@ module ActiveRecord
|
|
494
526
|
@foreign_key = nil
|
495
527
|
@association_foreign_key = nil
|
496
528
|
@association_primary_key = nil
|
529
|
+
if options[:query_constraints]
|
530
|
+
ActiveRecord.deprecator.warn <<~MSG.squish
|
531
|
+
Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is deprecated.
|
532
|
+
To maintain current behavior, use the `foreign_key` option instead.
|
533
|
+
MSG
|
534
|
+
end
|
535
|
+
|
536
|
+
# If the foreign key is an array, set query constraints options and don't use the foreign key
|
537
|
+
if options[:foreign_key].is_a?(Array)
|
538
|
+
options[:query_constraints] = options.delete(:foreign_key)
|
539
|
+
end
|
497
540
|
|
498
541
|
ensure_option_not_given_as_class!(:class_name)
|
499
542
|
end
|
@@ -503,7 +546,9 @@ module ActiveRecord
|
|
503
546
|
if polymorphic?
|
504
547
|
key = [key, owner._read_attribute(@foreign_type)]
|
505
548
|
end
|
506
|
-
klass.
|
549
|
+
klass.with_connection do |connection|
|
550
|
+
klass.cached_find_by_statement(connection, key, &block)
|
551
|
+
end
|
507
552
|
end
|
508
553
|
|
509
554
|
def join_table
|
@@ -511,10 +556,14 @@ module ActiveRecord
|
|
511
556
|
end
|
512
557
|
|
513
558
|
def foreign_key(infer_from_inverse_of: true)
|
514
|
-
@foreign_key ||= if options[:
|
559
|
+
@foreign_key ||= if options[:foreign_key]
|
560
|
+
if options[:foreign_key].is_a?(Array)
|
561
|
+
options[:foreign_key].map { |fk| fk.to_s.freeze }.freeze
|
562
|
+
else
|
563
|
+
options[:foreign_key].to_s.freeze
|
564
|
+
end
|
565
|
+
elsif options[:query_constraints]
|
515
566
|
options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
|
516
|
-
elsif options[:foreign_key]
|
517
|
-
options[:foreign_key].to_s
|
518
567
|
else
|
519
568
|
derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
|
520
569
|
|
@@ -711,6 +760,10 @@ module ActiveRecord
|
|
711
760
|
|
712
761
|
begin
|
713
762
|
reflection = klass._reflect_on_association(inverse_name)
|
763
|
+
if !reflection && active_record.automatically_invert_plural_associations
|
764
|
+
plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
|
765
|
+
reflection = klass._reflect_on_association(plural_inverse_name)
|
766
|
+
end
|
714
767
|
rescue NameError => error
|
715
768
|
raise unless error.name.to_s == class_name
|
716
769
|
|
@@ -720,7 +773,7 @@ module ActiveRecord
|
|
720
773
|
end
|
721
774
|
|
722
775
|
if valid_inverse_reflection?(reflection)
|
723
|
-
|
776
|
+
reflection.name
|
724
777
|
end
|
725
778
|
end
|
726
779
|
end
|
@@ -933,7 +986,7 @@ module ActiveRecord
|
|
933
986
|
end
|
934
987
|
|
935
988
|
def klass
|
936
|
-
@klass ||= delegate_reflection.compute_class(class_name)
|
989
|
+
@klass ||= delegate_reflection.compute_class(compute_name(class_name))
|
937
990
|
end
|
938
991
|
|
939
992
|
# Returns the source of the through reflection. It checks both a singularized
|
@@ -954,6 +1007,8 @@ module ActiveRecord
|
|
954
1007
|
# # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
|
955
1008
|
#
|
956
1009
|
def source_reflection
|
1010
|
+
return unless source_reflection_name
|
1011
|
+
|
957
1012
|
through_reflection.klass._reflect_on_association(source_reflection_name)
|
958
1013
|
end
|
959
1014
|
|
@@ -1111,7 +1166,7 @@ module ActiveRecord
|
|
1111
1166
|
end
|
1112
1167
|
|
1113
1168
|
if parent_reflection.nil?
|
1114
|
-
reflections = active_record.
|
1169
|
+
reflections = active_record.normalized_reflections.keys
|
1115
1170
|
|
1116
1171
|
if reflections.index(through_reflection.name) > reflections.index(name)
|
1117
1172
|
raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
|
@@ -77,13 +77,26 @@ module ActiveRecord
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
-
#
|
80
|
+
# Touches records in batches. Returns the total number of rows affected.
|
81
|
+
#
|
82
|
+
# Person.in_batches.touch_all
|
83
|
+
#
|
84
|
+
# See Relation#touch_all for details of how each batch is touched.
|
85
|
+
def touch_all(...)
|
86
|
+
sum do |relation|
|
87
|
+
relation.touch_all(...)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Destroys records in batches. Returns the total number of rows affected.
|
81
92
|
#
|
82
93
|
# Person.where("age < 10").in_batches.destroy_all
|
83
94
|
#
|
84
95
|
# See Relation#destroy_all for details of how each batch is destroyed.
|
85
96
|
def destroy_all
|
86
|
-
|
97
|
+
sum do |relation|
|
98
|
+
relation.destroy_all.count(&:destroyed?)
|
99
|
+
end
|
87
100
|
end
|
88
101
|
|
89
102
|
# Yields an ActiveRecord::Relation object for each batch of records.
|
@@ -345,15 +345,15 @@ module ActiveRecord
|
|
345
345
|
end
|
346
346
|
end
|
347
347
|
|
348
|
-
records
|
348
|
+
records.sort_by!(&:id)
|
349
349
|
|
350
350
|
if order == :desc
|
351
351
|
records.reverse!
|
352
352
|
end
|
353
353
|
|
354
|
-
|
354
|
+
records.each_slice(batch_limit) do |subrecords|
|
355
355
|
subrelation = relation.spawn
|
356
|
-
subrelation.load_records(
|
356
|
+
subrelation.load_records(subrecords)
|
357
357
|
|
358
358
|
yield subrelation
|
359
359
|
end
|
@@ -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(klass.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,10 @@ 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, people.id FROM comments INNER JOIN people on comments.person_id = people.id
|
280
|
+
# # => [[1, 2], [2, 2]]
|
281
|
+
#
|
278
282
|
# Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
|
279
283
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
280
284
|
# # => ['0', '27761', '173']
|
@@ -302,7 +306,7 @@ module ActiveRecord
|
|
302
306
|
relation = apply_join_dependency
|
303
307
|
relation.pluck(*column_names)
|
304
308
|
else
|
305
|
-
klass.disallow_raw_sql!(column_names
|
309
|
+
klass.disallow_raw_sql!(flattened_args(column_names))
|
306
310
|
columns = arel_columns(column_names)
|
307
311
|
relation = spawn
|
308
312
|
relation.select_values = columns
|
@@ -310,7 +314,9 @@ module ActiveRecord
|
|
310
314
|
if where_clause.contradiction?
|
311
315
|
ActiveRecord::Result.empty(async: @async)
|
312
316
|
else
|
313
|
-
klass.
|
317
|
+
klass.with_connection do |c|
|
318
|
+
c.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
|
319
|
+
end
|
314
320
|
end
|
315
321
|
end
|
316
322
|
result.then do |result|
|
@@ -357,7 +363,7 @@ module ActiveRecord
|
|
357
363
|
# Returns the base model's ID's for the relation using the table's primary key
|
358
364
|
#
|
359
365
|
# Person.ids # SELECT people.id FROM people
|
360
|
-
# Person.joins(:
|
366
|
+
# Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
|
361
367
|
def ids
|
362
368
|
primary_key_array = Array(primary_key)
|
363
369
|
|
@@ -385,7 +391,9 @@ module ActiveRecord
|
|
385
391
|
ActiveRecord::Result.empty
|
386
392
|
else
|
387
393
|
skip_query_cache_if_necessary do
|
388
|
-
klass.
|
394
|
+
klass.with_connection do |c|
|
395
|
+
c.select_all(relation, "#{klass.name} Ids", async: @async)
|
396
|
+
end
|
389
397
|
end
|
390
398
|
end
|
391
399
|
|
@@ -442,7 +450,7 @@ module ActiveRecord
|
|
442
450
|
return column_name if Arel::Expressions === column_name
|
443
451
|
|
444
452
|
arel_column(column_name.to_s) do |name|
|
445
|
-
|
453
|
+
column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
|
446
454
|
end
|
447
455
|
end
|
448
456
|
|
@@ -451,7 +459,7 @@ module ActiveRecord
|
|
451
459
|
end
|
452
460
|
|
453
461
|
def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
|
454
|
-
if operation
|
462
|
+
if build_count_subquery?(operation, column_name, distinct)
|
455
463
|
# Shortcut when limit is zero.
|
456
464
|
return 0 if limit_value == 0
|
457
465
|
|
@@ -474,7 +482,9 @@ module ActiveRecord
|
|
474
482
|
ActiveRecord::Result.empty
|
475
483
|
else
|
476
484
|
skip_query_cache_if_necessary do
|
477
|
-
@klass.
|
485
|
+
@klass.with_connection do |c|
|
486
|
+
c.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
|
487
|
+
end
|
478
488
|
end
|
479
489
|
end
|
480
490
|
|
@@ -500,68 +510,73 @@ module ActiveRecord
|
|
500
510
|
end
|
501
511
|
group_fields = arel_columns(group_fields)
|
502
512
|
|
503
|
-
|
513
|
+
@klass.with_connection do |connection|
|
514
|
+
column_alias_tracker = ColumnAliasTracker.new(connection)
|
504
515
|
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
516
|
+
group_aliases = group_fields.map { |field|
|
517
|
+
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
518
|
+
column_alias_tracker.alias_for(field.to_s.downcase)
|
519
|
+
}
|
520
|
+
group_columns = group_aliases.zip(group_fields)
|
510
521
|
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
522
|
+
column = aggregate_column(column_name)
|
523
|
+
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
524
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
525
|
+
select_value.as(adapter_class.quote_column_name(column_alias))
|
515
526
|
|
516
|
-
|
517
|
-
|
527
|
+
select_values = [select_value]
|
528
|
+
select_values += self.select_values unless having_clause.empty?
|
518
529
|
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
relation = except(:group).distinct!(false)
|
529
|
-
relation.group_values = group_fields
|
530
|
-
relation.select_values = select_values
|
531
|
-
|
532
|
-
result = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async) }
|
533
|
-
result.then do |calculated_data|
|
534
|
-
if association
|
535
|
-
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
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
|
530
|
+
select_values.concat group_columns.map { |aliaz, field|
|
531
|
+
aliaz = adapter_class.quote_column_name(aliaz)
|
532
|
+
if field.respond_to?(:as)
|
533
|
+
field.as(aliaz)
|
534
|
+
else
|
535
|
+
"#{field} AS #{aliaz}"
|
536
|
+
end
|
537
|
+
}
|
539
538
|
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
539
|
+
relation = except(:group).distinct!(false)
|
540
|
+
relation.group_values = group_fields
|
541
|
+
relation.select_values = select_values
|
542
|
+
|
543
|
+
result = skip_query_cache_if_necessary do
|
544
|
+
connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async)
|
545
545
|
end
|
546
546
|
|
547
|
-
|
548
|
-
|
549
|
-
|
547
|
+
result.then do |calculated_data|
|
548
|
+
if association
|
549
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
550
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
551
|
+
key_records = key_records.index_by(&:id)
|
550
552
|
end
|
551
|
-
end
|
552
553
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
554
|
+
key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
|
555
|
+
types[aliaz] = col_name.try(:type_caster) ||
|
556
|
+
type_for(col_name) do
|
557
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
562
|
+
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
563
|
+
hash[col_name] = row[i]
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
if operation != "count"
|
568
|
+
type = column.try(:type_caster) ||
|
569
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
570
|
+
type = type.subtype if Enum::EnumType === type
|
571
|
+
end
|
558
572
|
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
573
|
+
hash_rows.each_with_object({}) do |row, result|
|
574
|
+
key = group_aliases.map { |aliaz| row[aliaz] }
|
575
|
+
key = key.first if key.size == 1
|
576
|
+
key = key_records[key] if associated
|
563
577
|
|
564
|
-
|
578
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation, type)
|
579
|
+
end
|
565
580
|
end
|
566
581
|
end
|
567
582
|
end
|
@@ -617,12 +632,30 @@ module ActiveRecord
|
|
617
632
|
def select_for_count
|
618
633
|
if select_values.present?
|
619
634
|
return select_values.first if select_values.one?
|
620
|
-
|
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(", ")
|
621
647
|
else
|
622
648
|
:all
|
623
649
|
end
|
624
650
|
end
|
625
651
|
|
652
|
+
def build_count_subquery?(operation, column_name, distinct)
|
653
|
+
# SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or
|
654
|
+
# multiple columns, so we need to use subquery for this.
|
655
|
+
operation == "count" &&
|
656
|
+
(((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?)
|
657
|
+
end
|
658
|
+
|
626
659
|
def build_count_subquery(relation, column_name, distinct)
|
627
660
|
if column_name == :all
|
628
661
|
column_alias = Arel.star
|
@@ -632,7 +665,7 @@ module ActiveRecord
|
|
632
665
|
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
633
666
|
end
|
634
667
|
|
635
|
-
subquery_alias = Arel.sql("subquery_for_count")
|
668
|
+
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
636
669
|
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
637
670
|
|
638
671
|
relation.build_subquery(subquery_alias, select_value)
|
@@ -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
|
@@ -67,23 +66,22 @@ module ActiveRecord
|
|
67
66
|
end
|
68
67
|
|
69
68
|
class GeneratedRelationMethods < Module # :nodoc:
|
70
|
-
|
69
|
+
MUTEX = Mutex.new
|
71
70
|
|
72
71
|
def generate_method(method)
|
73
|
-
synchronize do
|
72
|
+
MUTEX.synchronize do
|
74
73
|
return if method_defined?(method)
|
75
74
|
|
76
|
-
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) &&
|
75
|
+
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
|
77
76
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
78
77
|
def #{method}(...)
|
79
78
|
scoping { klass.#{method}(...) }
|
80
79
|
end
|
81
80
|
RUBY
|
82
81
|
else
|
83
|
-
define_method(method) do |*args, &block|
|
84
|
-
scoping { klass.public_send(method, *args, &block) }
|
82
|
+
define_method(method) do |*args, **kwargs, &block|
|
83
|
+
scoping { klass.public_send(method, *args, **kwargs, &block) }
|
85
84
|
end
|
86
|
-
ruby2_keywords(method)
|
87
85
|
end
|
88
86
|
end
|
89
87
|
end
|
@@ -102,7 +100,7 @@ module ActiveRecord
|
|
102
100
|
:to_sentence, :to_fs, :to_formatted_s, :as_json,
|
103
101
|
:shuffle, :split, :slice, :index, :rindex, to: :records
|
104
102
|
|
105
|
-
delegate :primary_key, :connection, :transaction, to: :klass
|
103
|
+
delegate :primary_key, :lease_connection, :connection, :with_connection, :transaction, to: :klass
|
106
104
|
|
107
105
|
module ClassSpecificRelation # :nodoc:
|
108
106
|
extend ActiveSupport::Concern
|
@@ -114,17 +112,16 @@ module ActiveRecord
|
|
114
112
|
end
|
115
113
|
|
116
114
|
private
|
117
|
-
def method_missing(method,
|
115
|
+
def method_missing(method, ...)
|
118
116
|
if @klass.respond_to?(method)
|
119
117
|
unless Delegation.uncacheable_methods.include?(method)
|
120
118
|
@klass.generate_relation_method(method)
|
121
119
|
end
|
122
|
-
scoping { @klass.public_send(method,
|
120
|
+
scoping { @klass.public_send(method, ...) }
|
123
121
|
else
|
124
122
|
super
|
125
123
|
end
|
126
124
|
end
|
127
|
-
ruby2_keywords(:method_missing)
|
128
125
|
end
|
129
126
|
|
130
127
|
module ClassMethods # :nodoc:
|