activerecord 8.1.3.1 → 8.1.4
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 +617 -0
- data/lib/active_record/associations/association.rb +34 -16
- data/lib/active_record/associations/association_scope.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +2 -2
- data/lib/active_record/associations/builder/belongs_to.rb +19 -4
- data/lib/active_record/associations/collection_association.rb +6 -2
- data/lib/active_record/associations/collection_proxy.rb +4 -0
- data/lib/active_record/associations/foreign_association.rb +3 -1
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/join_dependency.rb +1 -2
- data/lib/active_record/associations/preloader/association.rb +1 -1
- data/lib/active_record/attribute_methods/serialization.rb +2 -2
- data/lib/active_record/attribute_methods.rb +6 -4
- data/lib/active_record/callbacks.rb +1 -2
- data/lib/active_record/coders/column_serializer.rb +1 -1
- data/lib/active_record/coders/json.rb +7 -6
- data/lib/active_record/coders/yaml_column.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +17 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +32 -20
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +2 -2
- data/lib/active_record/connection_adapters/abstract_adapter.rb +12 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +79 -23
- data/lib/active_record/connection_adapters/column.rb +2 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -21
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +3 -16
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +17 -5
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +14 -0
- data/lib/active_record/counter_cache.rb +8 -6
- data/lib/active_record/database_configurations/hash_config.rb +9 -2
- data/lib/active_record/database_configurations/url_config.rb +2 -0
- data/lib/active_record/disable_joins_association_relation.rb +5 -1
- data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
- data/lib/active_record/filter_attribute_handler.rb +6 -3
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +8 -3
- data/lib/active_record/log_subscriber.rb +28 -16
- data/lib/active_record/message_pack.rb +13 -0
- data/lib/active_record/migration/command_recorder.rb +12 -2
- data/lib/active_record/model_schema.rb +2 -2
- data/lib/active_record/nested_attributes.rb +8 -6
- data/lib/active_record/persistence.rb +1 -0
- data/lib/active_record/querying.rb +13 -1
- data/lib/active_record/reflection.rb +1 -1
- data/lib/active_record/relation/calculations.rb +8 -3
- data/lib/active_record/relation/finder_methods.rb +34 -20
- data/lib/active_record/relation/merger.rb +4 -2
- data/lib/active_record/relation/predicate_builder.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +63 -13
- data/lib/active_record/relation.rb +20 -12
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +1 -1
- data/lib/active_record/signed_id.rb +1 -1
- data/lib/active_record/statement_cache.rb +1 -1
- data/lib/active_record/store.rb +4 -2
- data/lib/active_record/tasks/abstract_tasks.rb +8 -1
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -16
- data/lib/active_record/test_databases.rb +4 -0
- data/lib/active_record/test_fixtures.rb +13 -4
- data/lib/active_record/transactions.rb +19 -16
- data/lib/active_record/type_caster/connection.rb +1 -1
- data/lib/active_record.rb +1 -1
- data/lib/arel/visitors/to_sql.rb +1 -1
- metadata +9 -9
|
@@ -103,7 +103,7 @@ module ActiveRecord
|
|
|
103
103
|
#
|
|
104
104
|
# class Member < ActiveRecord::Base
|
|
105
105
|
# has_many :posts
|
|
106
|
-
# accepts_nested_attributes_for :posts
|
|
106
|
+
# accepts_nested_attributes_for :posts, allow_destroy: true
|
|
107
107
|
# end
|
|
108
108
|
#
|
|
109
109
|
# You can now set or update attributes on the associated posts through
|
|
@@ -112,7 +112,8 @@ module ActiveRecord
|
|
|
112
112
|
#
|
|
113
113
|
# For each hash that does _not_ have an <tt>id</tt> key a new record will
|
|
114
114
|
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
|
|
115
|
-
# that evaluates to +true
|
|
115
|
+
# that evaluates to +true+ and the <tt>:allow_destroy</tt> option is
|
|
116
|
+
# enabled.
|
|
116
117
|
#
|
|
117
118
|
# params = { member: {
|
|
118
119
|
# name: 'joe', posts_attributes: [
|
|
@@ -313,8 +314,9 @@ module ActiveRecord
|
|
|
313
314
|
# that checks whether a record should be built for a certain attribute
|
|
314
315
|
# hash. The hash is passed to the supplied Proc or the method
|
|
315
316
|
# and it should return either +true+ or +false+. When no +:reject_if+
|
|
316
|
-
# is specified, a record will be built for all attribute hashes
|
|
317
|
-
#
|
|
317
|
+
# is specified, a record will be built for all attribute hashes, unless
|
|
318
|
+
# the <tt>:allow_destroy</tt> option is enabled and the hash has a
|
|
319
|
+
# <tt>_destroy</tt> value that evaluates to true.
|
|
318
320
|
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc
|
|
319
321
|
# that will reject a record where all the attributes are blank excluding
|
|
320
322
|
# any value for +_destroy+.
|
|
@@ -496,8 +498,6 @@ module ActiveRecord
|
|
|
496
498
|
raise ArgumentError, "Hash or Array expected for `#{association_name}` attributes, got #{attributes_collection.class.name}"
|
|
497
499
|
end
|
|
498
500
|
|
|
499
|
-
check_record_limit!(options[:limit], attributes_collection)
|
|
500
|
-
|
|
501
501
|
if attributes_collection.is_a? Hash
|
|
502
502
|
keys = attributes_collection.keys
|
|
503
503
|
attributes_collection = if keys.include?("id") || keys.include?(:id)
|
|
@@ -507,6 +507,8 @@ module ActiveRecord
|
|
|
507
507
|
end
|
|
508
508
|
end
|
|
509
509
|
|
|
510
|
+
check_record_limit!(options[:limit], attributes_collection)
|
|
511
|
+
|
|
510
512
|
association = association(association_name)
|
|
511
513
|
|
|
512
514
|
existing_records = if association.loaded?
|
|
@@ -494,6 +494,7 @@ module ActiveRecord
|
|
|
494
494
|
becoming.instance_variable_set(:@new_record, new_record?)
|
|
495
495
|
becoming.instance_variable_set(:@previously_new_record, previously_new_record?)
|
|
496
496
|
becoming.instance_variable_set(:@destroyed, destroyed?)
|
|
497
|
+
becoming.instance_variable_set(:@marked_for_destruction, marked_for_destruction?)
|
|
497
498
|
becoming.errors.copy!(errors)
|
|
498
499
|
end
|
|
499
500
|
|
|
@@ -17,12 +17,24 @@ module ActiveRecord
|
|
|
17
17
|
:and, :or, :annotate, :optimizer_hints, :extending,
|
|
18
18
|
:having, :create_with, :distinct, :references, :none, :unscope, :merge, :except, :only,
|
|
19
19
|
:count, :average, :minimum, :maximum, :sum, :calculate,
|
|
20
|
-
:pluck, :pick, :ids, :async_ids, :strict_loading, :excluding, :without, :
|
|
20
|
+
:pluck, :pick, :ids, :async_ids, :strict_loading, :excluding, :without, :with_recursive,
|
|
21
21
|
:async_count, :async_average, :async_minimum, :async_maximum, :async_sum, :async_pluck, :async_pick,
|
|
22
22
|
:insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
|
|
23
23
|
].freeze # :nodoc:
|
|
24
24
|
delegate(*QUERYING_METHODS, to: :all)
|
|
25
25
|
|
|
26
|
+
# Add a Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
|
27
|
+
#
|
|
28
|
+
# See ActiveRecord::QueryMethods#with for more information.
|
|
29
|
+
#
|
|
30
|
+
# When given a block, and the Object#with core extension is loaded, this
|
|
31
|
+
# delegates to it instead, temporarily setting the given attributes on the
|
|
32
|
+
# class for the duration of the block and restoring them afterwards.
|
|
33
|
+
def with(...)
|
|
34
|
+
return super if block_given? && defined?(super)
|
|
35
|
+
all.with(...)
|
|
36
|
+
end
|
|
37
|
+
|
|
26
38
|
# Executes a custom SQL query against your database and returns all the results. The results will
|
|
27
39
|
# be returned as an array, with the requested columns encapsulated as attributes of the model you call
|
|
28
40
|
# this method from. For example, if you call <tt>Product.find_by_sql</tt>, then the results will be returned in
|
|
@@ -250,7 +250,7 @@ module ActiveRecord
|
|
|
250
250
|
counter_cache[:column] || -"#{active_record.name.demodulize.underscore.pluralize}_count"
|
|
251
251
|
end
|
|
252
252
|
else
|
|
253
|
-
-((counter_cache &&
|
|
253
|
+
-((counter_cache && counter_cache[:column]) || "#{name}_count")
|
|
254
254
|
end
|
|
255
255
|
end
|
|
256
256
|
end
|
|
@@ -396,7 +396,7 @@ module ActiveRecord
|
|
|
396
396
|
relation.select_values = columns
|
|
397
397
|
|
|
398
398
|
result = if relation.where_clause.contradiction?
|
|
399
|
-
ActiveRecord::Result.empty
|
|
399
|
+
ActiveRecord::Result.empty(async: @async)
|
|
400
400
|
else
|
|
401
401
|
skip_query_cache_if_necessary do
|
|
402
402
|
model.with_connection do |c|
|
|
@@ -493,7 +493,7 @@ module ActiveRecord
|
|
|
493
493
|
|
|
494
494
|
column = relation.aggregate_column(column_name)
|
|
495
495
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
496
|
-
select_value.distinct = true if operation == "sum"
|
|
496
|
+
select_value.distinct = true if distinct && (operation == "sum" || operation == "average")
|
|
497
497
|
|
|
498
498
|
relation.select_values = [select_value]
|
|
499
499
|
|
|
@@ -549,6 +549,7 @@ module ActiveRecord
|
|
|
549
549
|
column = relation.aggregate_column(column_name)
|
|
550
550
|
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
|
551
551
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
552
|
+
select_value.distinct = true if distinct && (operation == "sum" || operation == "average")
|
|
552
553
|
select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))
|
|
553
554
|
|
|
554
555
|
select_values = [select_value]
|
|
@@ -572,7 +573,11 @@ module ActiveRecord
|
|
|
572
573
|
|
|
573
574
|
result.then do |calculated_data|
|
|
574
575
|
if association
|
|
575
|
-
key_ids
|
|
576
|
+
key_ids = if association.klass.base_class.composite_primary_key?
|
|
577
|
+
calculated_data.collect { |row| group_aliases.map { |group_alias| row[group_alias] } }
|
|
578
|
+
else
|
|
579
|
+
calculated_data.collect { |row| row[group_aliases.first] }
|
|
580
|
+
end
|
|
576
581
|
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
|
577
582
|
key_records = key_records.index_by(&:id)
|
|
578
583
|
end
|
|
@@ -462,15 +462,15 @@ module ActiveRecord
|
|
|
462
462
|
relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
|
|
463
463
|
|
|
464
464
|
if eager_loading && has_limit_or_offset? && !(
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
)
|
|
465
|
+
using_limitable_reflections?(join_dependency.reflections) &&
|
|
466
|
+
using_limitable_reflections?(
|
|
467
|
+
construct_join_dependency(
|
|
468
|
+
select_association_list(joins_values).concat(
|
|
469
|
+
select_association_list(left_outer_joins_values)
|
|
470
|
+
), nil
|
|
471
|
+
).reflections
|
|
473
472
|
)
|
|
473
|
+
)
|
|
474
474
|
relation = skip_query_cache_if_necessary do
|
|
475
475
|
model.with_connection do |c|
|
|
476
476
|
c.distinct_relation_for_primary_key(relation)
|
|
@@ -492,15 +492,16 @@ module ActiveRecord
|
|
|
492
492
|
def find_with_ids(*ids)
|
|
493
493
|
raise UnknownPrimaryKey.new(model) if primary_key.nil?
|
|
494
494
|
|
|
495
|
+
first_item = ids.first
|
|
496
|
+
return [] if first_item.is_a?(Array) && first_item.empty?
|
|
497
|
+
|
|
495
498
|
expects_array = if model.composite_primary_key?
|
|
496
|
-
|
|
499
|
+
first_item.first.is_a?(Array)
|
|
497
500
|
else
|
|
498
|
-
|
|
501
|
+
first_item.is_a?(Array)
|
|
499
502
|
end
|
|
500
503
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
ids = ids.first if expects_array
|
|
504
|
+
ids = first_item if expects_array
|
|
504
505
|
|
|
505
506
|
ids = ids.compact.uniq
|
|
506
507
|
|
|
@@ -542,10 +543,6 @@ module ActiveRecord
|
|
|
542
543
|
def find_some(ids)
|
|
543
544
|
return find_some_ordered(ids) unless order_values.present?
|
|
544
545
|
|
|
545
|
-
relation = where(primary_key => ids)
|
|
546
|
-
relation = relation.select(table[primary_key]) unless select_values.empty?
|
|
547
|
-
result = relation.to_a
|
|
548
|
-
|
|
549
546
|
expected_size =
|
|
550
547
|
if limit_value && ids.size > limit_value
|
|
551
548
|
limit_value
|
|
@@ -554,10 +551,18 @@ module ActiveRecord
|
|
|
554
551
|
end
|
|
555
552
|
|
|
556
553
|
# 11 ids with limit 3, offset 9 should give 2 results.
|
|
557
|
-
if offset_value
|
|
558
|
-
|
|
554
|
+
if offset_value
|
|
555
|
+
if ids.size <= offset_value
|
|
556
|
+
return []
|
|
557
|
+
elsif ids.size - offset_value < expected_size
|
|
558
|
+
expected_size = ids.size - offset_value
|
|
559
|
+
end
|
|
559
560
|
end
|
|
560
561
|
|
|
562
|
+
relation = where(primary_key => ids)
|
|
563
|
+
relation = relation.select(table[primary_key]) unless select_values.empty?
|
|
564
|
+
result = relation.to_a
|
|
565
|
+
|
|
561
566
|
if result.size == expected_size
|
|
562
567
|
result
|
|
563
568
|
else
|
|
@@ -567,6 +572,7 @@ module ActiveRecord
|
|
|
567
572
|
|
|
568
573
|
def find_some_ordered(ids)
|
|
569
574
|
ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
|
|
575
|
+
return [] if ids.empty?
|
|
570
576
|
|
|
571
577
|
relation = except(:limit, :offset)
|
|
572
578
|
relation = relation.where(primary_key => ids)
|
|
@@ -574,12 +580,20 @@ module ActiveRecord
|
|
|
574
580
|
result = relation.records
|
|
575
581
|
|
|
576
582
|
if result.size == ids.size
|
|
577
|
-
result.in_order_of(:id, ids.map { |id|
|
|
583
|
+
result.in_order_of(:id, ids.map { |id| cast_primary_key(id) })
|
|
578
584
|
else
|
|
579
585
|
raise_record_not_found_exception!(ids, result.size, ids.size)
|
|
580
586
|
end
|
|
581
587
|
end
|
|
582
588
|
|
|
589
|
+
def cast_primary_key(id)
|
|
590
|
+
if model.composite_primary_key?
|
|
591
|
+
primary_key.zip(id).map! { |attr, value| model.type_for_attribute(attr).cast(value) }
|
|
592
|
+
else
|
|
593
|
+
model.type_for_attribute(primary_key).cast(id)
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
|
|
583
597
|
def find_take
|
|
584
598
|
if loaded?
|
|
585
599
|
records.first
|
|
@@ -161,8 +161,10 @@ module ActiveRecord
|
|
|
161
161
|
relation.order!(*other.order_values)
|
|
162
162
|
end
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
unless other.extensions.empty?
|
|
165
|
+
extensions = other.extensions - relation.extensions
|
|
166
|
+
relation.extending!(*extensions) if extensions.any?
|
|
167
|
+
end
|
|
166
168
|
end
|
|
167
169
|
|
|
168
170
|
def merge_single_values
|
|
@@ -93,7 +93,7 @@ module ActiveRecord
|
|
|
93
93
|
if key.is_a?(Array)
|
|
94
94
|
queries = Array(value).map do |ids_set|
|
|
95
95
|
raise ArgumentError, "Expected corresponding value for #{key} to be an Array" unless ids_set.is_a?(Array)
|
|
96
|
-
expand_from_hash(key.zip(ids_set).to_h)
|
|
96
|
+
expand_from_hash(convert_dot_notation_to_hash(key.zip(ids_set).to_h))
|
|
97
97
|
end
|
|
98
98
|
grouping_queries(queries)
|
|
99
99
|
elsif value.is_a?(Hash) && !table.has_column?(key)
|
|
@@ -721,8 +721,16 @@ module ActiveRecord
|
|
|
721
721
|
references = column_references([column])
|
|
722
722
|
self.references_values |= references unless references.empty?
|
|
723
723
|
|
|
724
|
-
|
|
725
|
-
|
|
724
|
+
if column.is_a?(Arel::Nodes::SqlLiteral)
|
|
725
|
+
arel_column = column
|
|
726
|
+
else
|
|
727
|
+
arel_column = order_column(column.to_s)
|
|
728
|
+
|
|
729
|
+
unless arel_column.is_a?(Arel::Nodes::SqlLiteral)
|
|
730
|
+
values = cast_values_for_in_order_of(values, arel_column.type_caster)
|
|
731
|
+
end
|
|
732
|
+
return spawn.none! if values.empty?
|
|
733
|
+
end
|
|
726
734
|
|
|
727
735
|
scope = spawn.order!(build_case_for_value_position(arel_column, values, filter: filter))
|
|
728
736
|
|
|
@@ -837,6 +845,26 @@ module ActiveRecord
|
|
|
837
845
|
self
|
|
838
846
|
end
|
|
839
847
|
|
|
848
|
+
def table_name_qualified_unscope_values
|
|
849
|
+
self.unscope_values.map do |scope|
|
|
850
|
+
case scope
|
|
851
|
+
when Hash
|
|
852
|
+
scope.transform_values do |target_value|
|
|
853
|
+
case target_value
|
|
854
|
+
when Array
|
|
855
|
+
target_value.map { |value| qualify_attribute_with_table_name(value) }
|
|
856
|
+
when Symbol, String
|
|
857
|
+
qualify_attribute_with_table_name(target_value)
|
|
858
|
+
else
|
|
859
|
+
target_value
|
|
860
|
+
end
|
|
861
|
+
end
|
|
862
|
+
else
|
|
863
|
+
scope
|
|
864
|
+
end
|
|
865
|
+
end
|
|
866
|
+
end
|
|
867
|
+
|
|
840
868
|
# Performs JOINs on +args+. The given symbol(s) should match the name of
|
|
841
869
|
# the association(s).
|
|
842
870
|
#
|
|
@@ -1048,13 +1076,13 @@ module ActiveRecord
|
|
|
1048
1076
|
# Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
|
|
1049
1077
|
#
|
|
1050
1078
|
# Post.where(trashed: true).where(trashed: false)
|
|
1051
|
-
# # WHERE `trashed` =
|
|
1079
|
+
# # WHERE `trashed` = true AND `trashed` = false
|
|
1052
1080
|
#
|
|
1053
1081
|
# Post.where(trashed: true).rewhere(trashed: false)
|
|
1054
|
-
# # WHERE `trashed` =
|
|
1082
|
+
# # WHERE `trashed` = false
|
|
1055
1083
|
#
|
|
1056
1084
|
# Post.where(active: true).where(trashed: true).rewhere(trashed: false)
|
|
1057
|
-
# # WHERE `active` =
|
|
1085
|
+
# # WHERE `active` = true AND `trashed` = false
|
|
1058
1086
|
#
|
|
1059
1087
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
|
1060
1088
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
|
@@ -1076,16 +1104,16 @@ module ActiveRecord
|
|
|
1076
1104
|
# end
|
|
1077
1105
|
#
|
|
1078
1106
|
# User.where(accepted: true)
|
|
1079
|
-
# # WHERE `accepted` =
|
|
1107
|
+
# # WHERE `accepted` = true
|
|
1080
1108
|
#
|
|
1081
1109
|
# User.where(accepted: true).invert_where
|
|
1082
|
-
# # WHERE `accepted` !=
|
|
1110
|
+
# # WHERE `accepted` != true
|
|
1083
1111
|
#
|
|
1084
1112
|
# User.active
|
|
1085
|
-
# # WHERE `accepted` =
|
|
1113
|
+
# # WHERE `accepted` = true AND `locked` = false
|
|
1086
1114
|
#
|
|
1087
1115
|
# User.active.invert_where
|
|
1088
|
-
# # WHERE NOT (`accepted` =
|
|
1116
|
+
# # WHERE NOT (`accepted` = true AND `locked` = false)
|
|
1089
1117
|
#
|
|
1090
1118
|
# Be careful because this inverts all conditions before +invert_where+ call.
|
|
1091
1119
|
#
|
|
@@ -1096,7 +1124,7 @@ module ActiveRecord
|
|
|
1096
1124
|
#
|
|
1097
1125
|
# # It also inverts `where(role: 'admin')` unexpectedly.
|
|
1098
1126
|
# User.where(role: 'admin').inactive
|
|
1099
|
-
# # WHERE NOT (`role` = 'admin' AND `accepted` =
|
|
1127
|
+
# # WHERE NOT (`role` = 'admin' AND `accepted` = true AND `locked` = false)
|
|
1100
1128
|
#
|
|
1101
1129
|
def invert_where
|
|
1102
1130
|
spawn.invert_where!
|
|
@@ -1586,8 +1614,8 @@ module ActiveRecord
|
|
|
1586
1614
|
alias :without :excluding
|
|
1587
1615
|
|
|
1588
1616
|
def excluding!(records) # :nodoc:
|
|
1589
|
-
|
|
1590
|
-
self.where_clause +=
|
|
1617
|
+
ids = records.map { |record| record.is_a?(model) ? record.id : record }
|
|
1618
|
+
self.where_clause += build_where_clause(primary_key => ids).invert
|
|
1591
1619
|
self
|
|
1592
1620
|
end
|
|
1593
1621
|
|
|
@@ -1898,7 +1926,7 @@ module ActiveRecord
|
|
|
1898
1926
|
def build_select(arel)
|
|
1899
1927
|
if select_values.any?
|
|
1900
1928
|
arel.project(*arel_columns(select_values))
|
|
1901
|
-
elsif model.ignored_columns.any? || model.enumerate_columns_in_select_statements
|
|
1929
|
+
elsif model.ignored_columns.any? || model.enumerate_columns_in_select_statements || model.only_columns.any?
|
|
1902
1930
|
arel.project(*model.column_names.map { |field| table[field] })
|
|
1903
1931
|
else
|
|
1904
1932
|
arel.project(table[Arel.star])
|
|
@@ -2205,6 +2233,10 @@ module ActiveRecord
|
|
|
2205
2233
|
end
|
|
2206
2234
|
end
|
|
2207
2235
|
|
|
2236
|
+
def qualify_attribute_with_table_name(attr)
|
|
2237
|
+
attr.to_s.include?(".") ? attr : predicate_builder.resolve_arel_attribute(table_name, attr)
|
|
2238
|
+
end
|
|
2239
|
+
|
|
2208
2240
|
# Checks to make sure that the arguments are not blank. Note that if some
|
|
2209
2241
|
# blank-like object were initially passed into the query method, then this
|
|
2210
2242
|
# method will not raise an error.
|
|
@@ -2242,6 +2274,24 @@ module ActiveRecord
|
|
|
2242
2274
|
end
|
|
2243
2275
|
end
|
|
2244
2276
|
|
|
2277
|
+
def cast_values_for_in_order_of(values, type_caster)
|
|
2278
|
+
bad_value = Symbol # use some object that can not be a valid value
|
|
2279
|
+
|
|
2280
|
+
values = values.map do |value|
|
|
2281
|
+
serialized = type_caster.serialize(value) if type_caster.serializable?(value)
|
|
2282
|
+
|
|
2283
|
+
if value.nil?
|
|
2284
|
+
nil
|
|
2285
|
+
elsif !serialized.nil?
|
|
2286
|
+
serialized
|
|
2287
|
+
else
|
|
2288
|
+
bad_value
|
|
2289
|
+
end
|
|
2290
|
+
end
|
|
2291
|
+
|
|
2292
|
+
values.without(bad_value)
|
|
2293
|
+
end
|
|
2294
|
+
|
|
2245
2295
|
def arel_column_aliases_from_hash(fields)
|
|
2246
2296
|
fields.flat_map do |key, columns_aliases|
|
|
2247
2297
|
table_name = key.is_a?(Symbol) ? key.name : key
|
|
@@ -280,7 +280,7 @@ module ActiveRecord
|
|
|
280
280
|
record
|
|
281
281
|
rescue ActiveRecord::RecordNotUnique
|
|
282
282
|
if connection.transaction_open?
|
|
283
|
-
|
|
283
|
+
rewhere(attributes).lock(connection.create_or_find_by_lock).take!
|
|
284
284
|
else
|
|
285
285
|
find_by!(attributes)
|
|
286
286
|
end
|
|
@@ -300,7 +300,7 @@ module ActiveRecord
|
|
|
300
300
|
record
|
|
301
301
|
rescue ActiveRecord::RecordNotUnique
|
|
302
302
|
if connection.transaction_open?
|
|
303
|
-
|
|
303
|
+
rewhere(attributes).lock(connection.create_or_find_by_lock).take!
|
|
304
304
|
else
|
|
305
305
|
find_by!(attributes)
|
|
306
306
|
end
|
|
@@ -381,10 +381,11 @@ module ActiveRecord
|
|
|
381
381
|
|
|
382
382
|
# Returns true if there are no records.
|
|
383
383
|
#
|
|
384
|
-
# When
|
|
385
|
-
#
|
|
384
|
+
# When an argument is given, returns true if no records match the argument
|
|
385
|
+
# via the case-equality operator (<tt>===</tt>).
|
|
386
386
|
#
|
|
387
|
-
# posts.none?(
|
|
387
|
+
# posts.none?(Post) # => true if posts is empty
|
|
388
|
+
# posts.none?(Comment) # => true
|
|
388
389
|
def none?(*args)
|
|
389
390
|
return true if @none
|
|
390
391
|
|
|
@@ -394,10 +395,12 @@ module ActiveRecord
|
|
|
394
395
|
|
|
395
396
|
# Returns true if there are any records.
|
|
396
397
|
#
|
|
397
|
-
# When a pattern argument is given, this method checks whether elements in
|
|
398
|
-
# the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
|
|
399
398
|
#
|
|
400
|
-
#
|
|
399
|
+
# When an argument is given, returns true if at least one records matches
|
|
400
|
+
# the argument via the case-equality operator (<tt>===</tt>).
|
|
401
|
+
#
|
|
402
|
+
# posts.any?(Post) # => true if at least one record
|
|
403
|
+
# posts.any?(Comment) # => false
|
|
401
404
|
def any?(*args)
|
|
402
405
|
return false if @none
|
|
403
406
|
|
|
@@ -407,10 +410,11 @@ module ActiveRecord
|
|
|
407
410
|
|
|
408
411
|
# Returns true if there is exactly one record.
|
|
409
412
|
#
|
|
410
|
-
# When
|
|
411
|
-
#
|
|
413
|
+
# When an argument is given, returns true if exactly one records matches the
|
|
414
|
+
# argument via the case-equality operator (<tt>===</tt>).
|
|
412
415
|
#
|
|
413
|
-
# posts.one?(Post) # => true
|
|
416
|
+
# posts.one?(Post) # => true if exactly one record
|
|
417
|
+
# posts.any?(Comment) # => false
|
|
414
418
|
def one?(*args)
|
|
415
419
|
return false if @none
|
|
416
420
|
|
|
@@ -485,7 +489,7 @@ module ActiveRecord
|
|
|
485
489
|
if loaded?
|
|
486
490
|
size = records.size
|
|
487
491
|
if size > 0
|
|
488
|
-
timestamp = records.
|
|
492
|
+
timestamp = records.filter_map { |record| record.read_attribute(timestamp_column) }.max
|
|
489
493
|
end
|
|
490
494
|
else
|
|
491
495
|
collection = eager_loading? ? apply_join_dependency : self
|
|
@@ -1077,6 +1081,10 @@ module ActiveRecord
|
|
|
1077
1081
|
def delete(id_or_array)
|
|
1078
1082
|
return 0 if id_or_array.nil? || (id_or_array.is_a?(Array) && id_or_array.empty?)
|
|
1079
1083
|
|
|
1084
|
+
if model.composite_primary_key? && !id_or_array.first.is_a?(Array)
|
|
1085
|
+
id_or_array = [id_or_array]
|
|
1086
|
+
end
|
|
1087
|
+
|
|
1080
1088
|
where(model.primary_key => id_or_array).delete_all
|
|
1081
1089
|
end
|
|
1082
1090
|
|
data/lib/active_record/schema.rb
CHANGED
|
@@ -192,7 +192,7 @@ module ActiveRecord
|
|
|
192
192
|
tbl.puts ", force: :cascade do |t|"
|
|
193
193
|
|
|
194
194
|
# then dump all non-primary key columns
|
|
195
|
-
columns.
|
|
195
|
+
columns.each do |column|
|
|
196
196
|
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
|
|
197
197
|
next if column.name == pk
|
|
198
198
|
|
|
@@ -156,7 +156,7 @@ module ActiveRecord
|
|
|
156
156
|
@model.find_by_sql(sql, bind_values, preparable: true, allow_retry: @query_builder.retryable, &block)
|
|
157
157
|
end
|
|
158
158
|
rescue ::RangeError
|
|
159
|
-
async ? Promise.
|
|
159
|
+
async ? Promise::Complete.new([]) : []
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
def self.unsupported_value?(value)
|
data/lib/active_record/store.rb
CHANGED
|
@@ -154,7 +154,8 @@ module ActiveRecord
|
|
|
154
154
|
return unless attribute_changed?(store_attribute)
|
|
155
155
|
prev_store, new_store = changes[store_attribute]
|
|
156
156
|
accessor = store_accessor_for(store_attribute)
|
|
157
|
-
|
|
157
|
+
prev_value, new_value = accessor.get(prev_store, key), accessor.get(new_store, key)
|
|
158
|
+
[prev_value, new_value] unless prev_value == new_value
|
|
158
159
|
end
|
|
159
160
|
|
|
160
161
|
define_method("#{accessor_key}_was") do
|
|
@@ -175,7 +176,8 @@ module ActiveRecord
|
|
|
175
176
|
return unless saved_change_to_attribute?(store_attribute)
|
|
176
177
|
prev_store, new_store = saved_changes[store_attribute]
|
|
177
178
|
accessor = store_accessor_for(store_attribute)
|
|
178
|
-
|
|
179
|
+
prev_value, new_value = accessor.get(prev_store, key), accessor.get(new_store, key)
|
|
180
|
+
[prev_value, new_value] unless prev_value == new_value
|
|
179
181
|
end
|
|
180
182
|
|
|
181
183
|
define_method("#{accessor_key}_before_last_save") do
|
|
@@ -58,11 +58,18 @@ module ActiveRecord
|
|
|
58
58
|
|
|
59
59
|
def run_cmd_error(cmd, args)
|
|
60
60
|
msg = +"failed to execute:\n"
|
|
61
|
-
msg << "#{cmd} #{args.join(' ')}\n\n"
|
|
61
|
+
msg << "#{cmd} #{filter_sensitive_args(args).join(' ')}\n\n"
|
|
62
62
|
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
|
|
63
63
|
msg
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
+
# The failed command is echoed back, so anything secret it was given has
|
|
67
|
+
# to be kept out of the message.
|
|
68
|
+
def filter_sensitive_args(args)
|
|
69
|
+
filtered = "--password=#{ActiveSupport::ParameterFilter::FILTERED}"
|
|
70
|
+
args.map { |arg| arg.to_s.sub(/\A--password=.+/m, filtered) }
|
|
71
|
+
end
|
|
72
|
+
|
|
66
73
|
def with_temporary_pool(db_config, migration_class, clobber: false)
|
|
67
74
|
original_db_config = migration_class.connection_db_config
|
|
68
75
|
pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
|
|
@@ -61,22 +61,7 @@ module ActiveRecord
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def prepare_command_options
|
|
64
|
-
|
|
65
|
-
host: "--host",
|
|
66
|
-
port: "--port",
|
|
67
|
-
socket: "--socket",
|
|
68
|
-
username: "--user",
|
|
69
|
-
password: "--password",
|
|
70
|
-
encoding: "--default-character-set",
|
|
71
|
-
sslca: "--ssl-ca",
|
|
72
|
-
sslcert: "--ssl-cert",
|
|
73
|
-
sslcapath: "--ssl-capath",
|
|
74
|
-
sslcipher: "--ssl-cipher",
|
|
75
|
-
sslkey: "--ssl-key",
|
|
76
|
-
ssl_mode: "--ssl-mode"
|
|
77
|
-
}.filter_map { |opt, arg| "#{arg}=#{configuration_hash[opt]}" if configuration_hash[opt] }
|
|
78
|
-
|
|
79
|
-
args
|
|
64
|
+
db_config.adapter_class.cli_args(configuration_hash)
|
|
80
65
|
end
|
|
81
66
|
end
|
|
82
67
|
end
|
|
@@ -16,6 +16,10 @@ module ActiveRecord
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
ActiveSupport::Testing::Parallelization.run_cleanup_hook do
|
|
20
|
+
ActiveRecord::Base.connection_handler.each_connection_pool.each(&:discard!)
|
|
21
|
+
end
|
|
22
|
+
|
|
19
23
|
def self.create_and_load_schema(i, env_name:)
|
|
20
24
|
old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
|
|
21
25
|
|
|
@@ -263,19 +263,28 @@ module ActiveRecord
|
|
|
263
263
|
|
|
264
264
|
def teardown_shared_connection_pool
|
|
265
265
|
handler = ActiveRecord::Base.connection_handler
|
|
266
|
+
removed_pool_configs = []
|
|
267
|
+
pool_managers = handler.send(:connection_name_to_pool_manager)
|
|
266
268
|
|
|
267
269
|
@saved_pool_configs.each_pair do |name, shards|
|
|
268
|
-
pool_manager =
|
|
270
|
+
pool_manager = pool_managers[name]
|
|
269
271
|
shards.each_pair do |shard_name, roles|
|
|
270
272
|
roles.each_pair do |role, pool_config|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
273
|
+
if pool_manager.get_pool_config(role, shard_name)
|
|
274
|
+
pool_manager.set_pool_config(role, shard_name, pool_config)
|
|
275
|
+
else
|
|
276
|
+
removed_pool_configs << pool_config
|
|
277
|
+
end
|
|
274
278
|
end
|
|
275
279
|
end
|
|
276
280
|
end
|
|
277
281
|
|
|
278
282
|
@saved_pool_configs.clear
|
|
283
|
+
|
|
284
|
+
remaining_pool_configs = pool_managers.values.flat_map(&:pool_configs)
|
|
285
|
+
removed_pool_configs.compact.uniq.each do |pool_config|
|
|
286
|
+
pool_config.disconnect! unless remaining_pool_configs.include?(pool_config)
|
|
287
|
+
end
|
|
279
288
|
end
|
|
280
289
|
|
|
281
290
|
def load_fixtures(config)
|