activerecord 7.1.4.1 → 7.2.2.1
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 +643 -2274
- 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 +15 -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 +7 -1
- data/lib/active_record/associations/collection_proxy.rb +14 -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 +7 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +4 -4
- 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 +59 -292
- 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 +23 -55
- 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 +11 -6
- data/lib/active_record/attribute_methods.rb +54 -63
- data/lib/active_record/attributes.rb +61 -47
- data/lib/active_record/autosave_association.rb +12 -29
- 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 +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 +270 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +189 -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 +15 -6
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +40 -10
- 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 +6 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
- 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 +17 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
- 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 +125 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- 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 +86 -38
- 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 +19 -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 +3 -3
- data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
- data/lib/active_record/encryption/encryptor.rb +18 -3
- 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.rb +2 -0
- data/lib/active_record/enum.rb +19 -2
- data/lib/active_record/errors.rb +46 -20
- 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 +2 -2
- 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 +4 -1
- 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 +32 -68
- data/lib/active_record/nested_attributes.rb +24 -5
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +42 -57
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +40 -43
- data/lib/active_record/reflection.rb +98 -36
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +96 -63
- 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/association_query_value.rb +9 -3
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +224 -58
- 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/scoping/named.rb +1 -0
- data/lib/active_record/signed_id.rb +20 -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 +81 -42
- 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 +86 -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 +70 -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 +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +148 -39
- 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/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 -12
@@ -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,7 +428,15 @@ 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 ||=
|
431
|
+
@klass ||= _klass(class_name)
|
432
|
+
end
|
433
|
+
|
434
|
+
def _klass(class_name) # :nodoc:
|
435
|
+
if active_record.name.demodulize == class_name
|
436
|
+
return compute_class("::#{class_name}") rescue NameError
|
437
|
+
end
|
438
|
+
|
439
|
+
compute_class(class_name)
|
413
440
|
end
|
414
441
|
|
415
442
|
def compute_class(name)
|
@@ -435,15 +462,24 @@ module ActiveRecord
|
|
435
462
|
name.to_s.camelize
|
436
463
|
end
|
437
464
|
|
438
|
-
def
|
439
|
-
|
465
|
+
def normalize_options(options)
|
466
|
+
counter_cache = options.delete(:counter_cache)
|
440
467
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
468
|
+
if counter_cache
|
469
|
+
active = true
|
470
|
+
|
471
|
+
case counter_cache
|
472
|
+
when String, Symbol
|
473
|
+
column = -counter_cache.to_s
|
474
|
+
when Hash
|
475
|
+
active = counter_cache.fetch(:active, true)
|
476
|
+
column = counter_cache[:column]&.to_s
|
477
|
+
end
|
478
|
+
|
479
|
+
options[:counter_cache] = { active: active, column: column }
|
480
|
+
end
|
481
|
+
|
482
|
+
options
|
447
483
|
end
|
448
484
|
end
|
449
485
|
|
@@ -494,6 +530,17 @@ module ActiveRecord
|
|
494
530
|
@foreign_key = nil
|
495
531
|
@association_foreign_key = nil
|
496
532
|
@association_primary_key = nil
|
533
|
+
if options[:query_constraints]
|
534
|
+
ActiveRecord.deprecator.warn <<~MSG.squish
|
535
|
+
Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is deprecated.
|
536
|
+
To maintain current behavior, use the `foreign_key` option instead.
|
537
|
+
MSG
|
538
|
+
end
|
539
|
+
|
540
|
+
# If the foreign key is an array, set query constraints options and don't use the foreign key
|
541
|
+
if options[:foreign_key].is_a?(Array)
|
542
|
+
options[:query_constraints] = options.delete(:foreign_key)
|
543
|
+
end
|
497
544
|
|
498
545
|
ensure_option_not_given_as_class!(:class_name)
|
499
546
|
end
|
@@ -503,7 +550,9 @@ module ActiveRecord
|
|
503
550
|
if polymorphic?
|
504
551
|
key = [key, owner._read_attribute(@foreign_type)]
|
505
552
|
end
|
506
|
-
klass.
|
553
|
+
klass.with_connection do |connection|
|
554
|
+
klass.cached_find_by_statement(connection, key, &block)
|
555
|
+
end
|
507
556
|
end
|
508
557
|
|
509
558
|
def join_table
|
@@ -511,10 +560,14 @@ module ActiveRecord
|
|
511
560
|
end
|
512
561
|
|
513
562
|
def foreign_key(infer_from_inverse_of: true)
|
514
|
-
@foreign_key ||= if options[:
|
563
|
+
@foreign_key ||= if options[:foreign_key]
|
564
|
+
if options[:foreign_key].is_a?(Array)
|
565
|
+
options[:foreign_key].map { |fk| fk.to_s.freeze }.freeze
|
566
|
+
else
|
567
|
+
options[:foreign_key].to_s.freeze
|
568
|
+
end
|
569
|
+
elsif options[:query_constraints]
|
515
570
|
options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
|
516
|
-
elsif options[:foreign_key]
|
517
|
-
options[:foreign_key].to_s
|
518
571
|
else
|
519
572
|
derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
|
520
573
|
|
@@ -711,6 +764,10 @@ module ActiveRecord
|
|
711
764
|
|
712
765
|
begin
|
713
766
|
reflection = klass._reflect_on_association(inverse_name)
|
767
|
+
if !reflection && active_record.automatically_invert_plural_associations
|
768
|
+
plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
|
769
|
+
reflection = klass._reflect_on_association(plural_inverse_name)
|
770
|
+
end
|
714
771
|
rescue NameError => error
|
715
772
|
raise unless error.name.to_s == class_name
|
716
773
|
|
@@ -720,7 +777,7 @@ module ActiveRecord
|
|
720
777
|
end
|
721
778
|
|
722
779
|
if valid_inverse_reflection?(reflection)
|
723
|
-
|
780
|
+
reflection.name
|
724
781
|
end
|
725
782
|
end
|
726
783
|
end
|
@@ -933,7 +990,7 @@ module ActiveRecord
|
|
933
990
|
end
|
934
991
|
|
935
992
|
def klass
|
936
|
-
@klass ||= delegate_reflection.
|
993
|
+
@klass ||= delegate_reflection._klass(class_name)
|
937
994
|
end
|
938
995
|
|
939
996
|
# Returns the source of the through reflection. It checks both a singularized
|
@@ -954,6 +1011,8 @@ module ActiveRecord
|
|
954
1011
|
# # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
|
955
1012
|
#
|
956
1013
|
def source_reflection
|
1014
|
+
return unless source_reflection_name
|
1015
|
+
|
957
1016
|
through_reflection.klass._reflect_on_association(source_reflection_name)
|
958
1017
|
end
|
959
1018
|
|
@@ -1111,7 +1170,7 @@ module ActiveRecord
|
|
1111
1170
|
end
|
1112
1171
|
|
1113
1172
|
if parent_reflection.nil?
|
1114
|
-
reflections = active_record.
|
1173
|
+
reflections = active_record.normalized_reflections.keys
|
1115
1174
|
|
1116
1175
|
if reflections.index(through_reflection.name) > reflections.index(name)
|
1117
1176
|
raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
|
@@ -1180,7 +1239,10 @@ module ActiveRecord
|
|
1180
1239
|
end
|
1181
1240
|
|
1182
1241
|
def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
|
1183
|
-
scopes =
|
1242
|
+
scopes = super
|
1243
|
+
unless @previous_reflection.through_reflection?
|
1244
|
+
scopes += @previous_reflection.join_scopes(table, predicate_builder, klass, record)
|
1245
|
+
end
|
1184
1246
|
scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
|
1185
1247
|
end
|
1186
1248
|
|
@@ -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.
|
@@ -241,14 +241,14 @@ module ActiveRecord
|
|
241
241
|
raise ArgumentError, ":order must be :asc or :desc or an array consisting of :asc or :desc, got #{order.inspect}"
|
242
242
|
end
|
243
243
|
|
244
|
-
unless block
|
245
|
-
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, order: order, use_ranges: use_ranges)
|
246
|
-
end
|
247
|
-
|
248
244
|
if arel.orders.present?
|
249
245
|
act_on_ignored_order(error_on_ignore)
|
250
246
|
end
|
251
247
|
|
248
|
+
unless block
|
249
|
+
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, order: order, use_ranges: use_ranges)
|
250
|
+
end
|
251
|
+
|
252
252
|
batch_limit = of
|
253
253
|
|
254
254
|
if limit_value
|
@@ -341,19 +341,25 @@ module ActiveRecord
|
|
341
341
|
|
342
342
|
if start || finish
|
343
343
|
records = records.filter do |record|
|
344
|
-
|
344
|
+
id = record.id
|
345
|
+
|
346
|
+
if order == :asc
|
347
|
+
(start.nil? || id >= start) && (finish.nil? || id <= finish)
|
348
|
+
else
|
349
|
+
(start.nil? || id <= start) && (finish.nil? || id >= finish)
|
350
|
+
end
|
345
351
|
end
|
346
352
|
end
|
347
353
|
|
348
|
-
records
|
354
|
+
records.sort_by!(&:id)
|
349
355
|
|
350
356
|
if order == :desc
|
351
357
|
records.reverse!
|
352
358
|
end
|
353
359
|
|
354
|
-
|
360
|
+
records.each_slice(batch_limit) do |subrecords|
|
355
361
|
subrelation = relation.spawn
|
356
|
-
subrelation.load_records(
|
362
|
+
subrelation.load_records(subrecords)
|
357
363
|
|
358
364
|
yield subrelation
|
359
365
|
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,15 +306,17 @@ module ActiveRecord
|
|
302
306
|
relation = apply_join_dependency
|
303
307
|
relation.pluck(*column_names)
|
304
308
|
else
|
305
|
-
klass.disallow_raw_sql!(column_names
|
306
|
-
columns = arel_columns(column_names)
|
309
|
+
klass.disallow_raw_sql!(flattened_args(column_names))
|
307
310
|
relation = spawn
|
311
|
+
columns = relation.arel_columns(column_names)
|
308
312
|
relation.select_values = columns
|
309
313
|
result = skip_query_cache_if_necessary do
|
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
|
@@ -589,7 +604,7 @@ module ActiveRecord
|
|
589
604
|
klass.attribute_types.fetch(name = result.columns[i]) do
|
590
605
|
join_dependencies ||= build_join_dependencies
|
591
606
|
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
|
592
|
-
result.column_types[
|
607
|
+
result.column_types[i] || Type.default_value
|
593
608
|
end
|
594
609
|
end
|
595
610
|
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)
|