activerecord 7.2.3 → 8.1.3
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 +612 -1055
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/association.rb +35 -11
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +1 -1
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/base.rb +1 -2
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +412 -88
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +137 -75
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -32
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +150 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +63 -52
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +139 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -14
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +102 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +25 -2
- data/lib/active_record/core.rb +33 -17
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +67 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +8 -8
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encryptor.rb +28 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +33 -30
- data/lib/active_record/errors.rb +33 -9
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -13
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +45 -12
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +48 -42
- data/lib/active_record/model_schema.rb +38 -13
- data/lib/active_record/nested_attributes.rb +6 -6
- data/lib/active_record/persistence.rb +162 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +100 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +8 -8
- data/lib/active_record/railtie.rb +35 -30
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -38
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +53 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +52 -40
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +40 -24
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +22 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +140 -86
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +2 -9
- data/lib/active_record/relation.rb +107 -75
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema_dumper.rb +18 -11
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +39 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +37 -16
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +13 -2
- data/lib/active_record/type/serialized.rb +16 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +84 -49
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +3 -21
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +16 -13
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -87,6 +87,8 @@ module ActiveRecord
|
|
|
87
87
|
|
|
88
88
|
connections = []
|
|
89
89
|
|
|
90
|
+
@shard_keys = shards.keys
|
|
91
|
+
|
|
90
92
|
if shards.empty?
|
|
91
93
|
shards[:default] = database
|
|
92
94
|
end
|
|
@@ -98,7 +100,8 @@ module ActiveRecord
|
|
|
98
100
|
db_config = resolve_config_for_connection(database_key)
|
|
99
101
|
|
|
100
102
|
self.connection_class = true
|
|
101
|
-
|
|
103
|
+
shard = shard.to_sym unless shard.is_a? Integer
|
|
104
|
+
connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard)
|
|
102
105
|
end
|
|
103
106
|
end
|
|
104
107
|
|
|
@@ -177,6 +180,18 @@ module ActiveRecord
|
|
|
177
180
|
end
|
|
178
181
|
end
|
|
179
182
|
|
|
183
|
+
# Passes the block to +connected_to+ for every +shard+ the
|
|
184
|
+
# model is configured to connect to (if any), and returns the
|
|
185
|
+
# results in an array.
|
|
186
|
+
#
|
|
187
|
+
# Optionally, +role+ and/or +prevent_writes+ can be passed which
|
|
188
|
+
# will be forwarded to each +connected_to+ call.
|
|
189
|
+
def connected_to_all_shards(role: nil, prevent_writes: false, &blk)
|
|
190
|
+
shard_keys.map do |shard|
|
|
191
|
+
connected_to(shard: shard, role: role, prevent_writes: prevent_writes, &blk)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
180
195
|
# Use a specified connection.
|
|
181
196
|
#
|
|
182
197
|
# This method is useful for ensuring that a specific connection is
|
|
@@ -289,7 +304,7 @@ module ActiveRecord
|
|
|
289
304
|
|
|
290
305
|
# Checkouts a connection from the pool, yield it and then check it back in.
|
|
291
306
|
# If a connection was already leased via #lease_connection or a parent call to
|
|
292
|
-
# #with_connection, that same connection is
|
|
307
|
+
# #with_connection, that same connection is yielded.
|
|
293
308
|
# If #lease_connection is called inside the block, the connection won't be checked
|
|
294
309
|
# back in.
|
|
295
310
|
# If #connection is called inside the block, the connection won't be checked back in
|
|
@@ -361,6 +376,14 @@ module ActiveRecord
|
|
|
361
376
|
connection_pool.schema_cache.clear!
|
|
362
377
|
end
|
|
363
378
|
|
|
379
|
+
def shard_keys
|
|
380
|
+
connection_class_for_self.instance_variable_get(:@shard_keys) || []
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def sharded?
|
|
384
|
+
shard_keys.any?
|
|
385
|
+
end
|
|
386
|
+
|
|
364
387
|
private
|
|
365
388
|
def resolve_config_for_connection(config_or_env)
|
|
366
389
|
raise "Anonymous class is not allowed." unless name
|
data/lib/active_record/core.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_support/core_ext/enumerable"
|
|
4
|
-
require "active_support/core_ext/module/delegation"
|
|
5
4
|
require "active_support/parameter_filter"
|
|
6
5
|
require "concurrent/map"
|
|
7
6
|
|
|
@@ -89,6 +88,7 @@ module ActiveRecord
|
|
|
89
88
|
class_attribute :belongs_to_required_by_default, instance_accessor: false
|
|
90
89
|
|
|
91
90
|
class_attribute :strict_loading_by_default, instance_accessor: false, default: false
|
|
91
|
+
class_attribute :strict_loading_mode, instance_accessor: false, default: :all
|
|
92
92
|
|
|
93
93
|
class_attribute :has_many_inversing, instance_accessor: false, default: false
|
|
94
94
|
|
|
@@ -111,7 +111,7 @@ module ActiveRecord
|
|
|
111
111
|
# Post.attributes_for_inspect = [:id, :title]
|
|
112
112
|
# Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">"
|
|
113
113
|
#
|
|
114
|
-
# When set to
|
|
114
|
+
# When set to +:all+ inspect will list all the record's attributes:
|
|
115
115
|
#
|
|
116
116
|
# Post.attributes_for_inspect = :all
|
|
117
117
|
# Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
|
|
@@ -201,6 +201,17 @@ module ActiveRecord
|
|
|
201
201
|
false
|
|
202
202
|
end
|
|
203
203
|
|
|
204
|
+
# Intended to behave like `.current_preventing_writes` given the class name as input.
|
|
205
|
+
# See PoolConfig and ConnectionHandler::ConnectionDescriptor.
|
|
206
|
+
def self.preventing_writes?(class_name) # :nodoc:
|
|
207
|
+
connected_to_stack.reverse_each do |hash|
|
|
208
|
+
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
|
|
209
|
+
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].any? { |klass| klass.name == class_name }
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
false
|
|
213
|
+
end
|
|
214
|
+
|
|
204
215
|
def self.connected_to_stack # :nodoc:
|
|
205
216
|
if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack]
|
|
206
217
|
connected_to_stack
|
|
@@ -346,6 +357,8 @@ module ActiveRecord
|
|
|
346
357
|
def filter_attributes=(filter_attributes)
|
|
347
358
|
@inspection_filter = nil
|
|
348
359
|
@filter_attributes = filter_attributes
|
|
360
|
+
|
|
361
|
+
FilterAttributeHandler.sensitive_attribute_was_declared(self, filter_attributes)
|
|
349
362
|
end
|
|
350
363
|
|
|
351
364
|
def inspection_filter # :nodoc:
|
|
@@ -381,7 +394,7 @@ module ActiveRecord
|
|
|
381
394
|
end
|
|
382
395
|
|
|
383
396
|
def predicate_builder # :nodoc:
|
|
384
|
-
@predicate_builder ||= PredicateBuilder.new(
|
|
397
|
+
@predicate_builder ||= PredicateBuilder.new(TableMetadata.new(self, arel_table))
|
|
385
398
|
end
|
|
386
399
|
|
|
387
400
|
def type_caster # :nodoc:
|
|
@@ -426,10 +439,6 @@ module ActiveRecord
|
|
|
426
439
|
end
|
|
427
440
|
end
|
|
428
441
|
|
|
429
|
-
def table_metadata
|
|
430
|
-
TableMetadata.new(self, arel_table)
|
|
431
|
-
end
|
|
432
|
-
|
|
433
442
|
def cached_find_by(keys, values)
|
|
434
443
|
with_connection do |connection|
|
|
435
444
|
statement = cached_find_by_statement(connection, keys) { |params|
|
|
@@ -443,8 +452,8 @@ module ActiveRecord
|
|
|
443
452
|
where(wheres).limit(1)
|
|
444
453
|
}
|
|
445
454
|
|
|
446
|
-
|
|
447
|
-
|
|
455
|
+
statement.execute(values.flatten, connection).then do |r|
|
|
456
|
+
r.first
|
|
448
457
|
rescue TypeError
|
|
449
458
|
raise ActiveRecord::StatementInvalid
|
|
450
459
|
end
|
|
@@ -540,12 +549,7 @@ module ActiveRecord
|
|
|
540
549
|
|
|
541
550
|
##
|
|
542
551
|
def initialize_dup(other) # :nodoc:
|
|
543
|
-
@attributes =
|
|
544
|
-
if self.class.composite_primary_key?
|
|
545
|
-
@primary_key.each { |key| @attributes.reset(key) }
|
|
546
|
-
else
|
|
547
|
-
@attributes.reset(@primary_key)
|
|
548
|
-
end
|
|
552
|
+
@attributes = init_attributes(other)
|
|
549
553
|
|
|
550
554
|
_run_initialize_callbacks
|
|
551
555
|
|
|
@@ -557,6 +561,18 @@ module ActiveRecord
|
|
|
557
561
|
super
|
|
558
562
|
end
|
|
559
563
|
|
|
564
|
+
def init_attributes(_) # :nodoc:
|
|
565
|
+
attrs = @attributes.deep_dup
|
|
566
|
+
|
|
567
|
+
if self.class.composite_primary_key?
|
|
568
|
+
@primary_key.each { |key| attrs.reset(key) }
|
|
569
|
+
else
|
|
570
|
+
attrs.reset(@primary_key)
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
attrs
|
|
574
|
+
end
|
|
575
|
+
|
|
560
576
|
# Populate +coder+ with attributes about this record that should be
|
|
561
577
|
# serialized. The structure of +coder+ defined in this method is
|
|
562
578
|
# guaranteed to match the structure of +coder+ passed to the #init_with
|
|
@@ -626,7 +642,7 @@ module ActiveRecord
|
|
|
626
642
|
def hash
|
|
627
643
|
id = self.id
|
|
628
644
|
|
|
629
|
-
if primary_key_values_present?
|
|
645
|
+
if self.class.composite_primary_key? ? primary_key_values_present? : id
|
|
630
646
|
self.class.hash ^ id.hash
|
|
631
647
|
else
|
|
632
648
|
super
|
|
@@ -830,7 +846,7 @@ module ActiveRecord
|
|
|
830
846
|
|
|
831
847
|
@primary_key = klass.primary_key
|
|
832
848
|
@strict_loading = klass.strict_loading_by_default
|
|
833
|
-
@strict_loading_mode =
|
|
849
|
+
@strict_loading_mode = klass.strict_loading_mode
|
|
834
850
|
|
|
835
851
|
klass.define_attribute_methods
|
|
836
852
|
end
|
|
@@ -17,7 +17,7 @@ module ActiveRecord
|
|
|
17
17
|
#
|
|
18
18
|
# ==== Parameters
|
|
19
19
|
#
|
|
20
|
-
# * +id+ - The id of the object you wish to reset a counter on.
|
|
20
|
+
# * +id+ - The id of the object you wish to reset a counter on or an array of ids.
|
|
21
21
|
# * +counters+ - One or more association counters to reset. Association name or counter name can be given.
|
|
22
22
|
# * <tt>:touch</tt> - Touch timestamp columns when updating.
|
|
23
23
|
# Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
|
|
@@ -28,13 +28,25 @@ module ActiveRecord
|
|
|
28
28
|
# # For the Post with id #1, reset the comments_count
|
|
29
29
|
# Post.reset_counters(1, :comments)
|
|
30
30
|
#
|
|
31
|
+
# # For posts with ids #1 and #2, reset the comments_count
|
|
32
|
+
# Post.reset_counters([1, 2], :comments)
|
|
33
|
+
#
|
|
31
34
|
# # Like above, but also touch the updated_at and/or updated_on
|
|
32
35
|
# # attributes.
|
|
33
36
|
# Post.reset_counters(1, :comments, touch: true)
|
|
34
37
|
def reset_counters(id, *counters, touch: nil)
|
|
35
|
-
|
|
38
|
+
ids = if composite_primary_key?
|
|
39
|
+
if id.first.is_a?(Array)
|
|
40
|
+
id
|
|
41
|
+
else
|
|
42
|
+
[id]
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
Array(id)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
updates = Hash.new { |h, k| h[k] = {} }
|
|
36
49
|
|
|
37
|
-
updates = {}
|
|
38
50
|
counters.each do |counter_association|
|
|
39
51
|
has_many_association = _reflect_on_association(counter_association)
|
|
40
52
|
unless has_many_association
|
|
@@ -48,14 +60,22 @@ module ActiveRecord
|
|
|
48
60
|
has_many_association = has_many_association.through_reflection
|
|
49
61
|
end
|
|
50
62
|
|
|
63
|
+
counter_association = counter_association.to_sym
|
|
51
64
|
foreign_key = has_many_association.foreign_key.to_s
|
|
52
65
|
child_class = has_many_association.klass
|
|
53
66
|
reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
|
|
54
67
|
counter_name = reflection.counter_cache_column
|
|
55
68
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
counts =
|
|
70
|
+
unscoped
|
|
71
|
+
.joins(counter_association)
|
|
72
|
+
.where(primary_key => ids)
|
|
73
|
+
.group(primary_key)
|
|
74
|
+
.count(:all)
|
|
75
|
+
|
|
76
|
+
ids.each do |id|
|
|
77
|
+
updates[id].merge!(counter_name => counts[id] || 0)
|
|
78
|
+
end
|
|
59
79
|
end
|
|
60
80
|
|
|
61
81
|
if touch
|
|
@@ -63,10 +83,15 @@ module ActiveRecord
|
|
|
63
83
|
names = Array.wrap(names)
|
|
64
84
|
options = names.extract_options!
|
|
65
85
|
touch_updates = touch_attributes_with_time(*names, **options)
|
|
66
|
-
|
|
86
|
+
|
|
87
|
+
updates.each_value do |record_updates|
|
|
88
|
+
record_updates.merge!(touch_updates)
|
|
89
|
+
end
|
|
67
90
|
end
|
|
68
91
|
|
|
69
|
-
|
|
92
|
+
updates.each do |id, record_updates|
|
|
93
|
+
unscoped.where(primary_key => [id]).update_all(record_updates)
|
|
94
|
+
end
|
|
70
95
|
|
|
71
96
|
true
|
|
72
97
|
end
|
|
@@ -48,7 +48,11 @@ module ActiveRecord
|
|
|
48
48
|
raise NotImplementedError
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
def
|
|
51
|
+
def min_connections
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def max_connections
|
|
52
56
|
raise NotImplementedError
|
|
53
57
|
end
|
|
54
58
|
|
|
@@ -99,6 +103,10 @@ module ActiveRecord
|
|
|
99
103
|
def use_metadata_table?
|
|
100
104
|
raise NotImplementedError
|
|
101
105
|
end
|
|
106
|
+
|
|
107
|
+
def seeds?
|
|
108
|
+
raise NotImplementedError
|
|
109
|
+
end
|
|
102
110
|
end
|
|
103
111
|
end
|
|
104
112
|
end
|
|
@@ -38,6 +38,7 @@ module ActiveRecord
|
|
|
38
38
|
def initialize(env_name, name, configuration_hash)
|
|
39
39
|
super(env_name, name)
|
|
40
40
|
@configuration_hash = configuration_hash.symbolize_keys.freeze
|
|
41
|
+
validate_configuration!
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
# Determines whether a database configuration is for a replica / readonly
|
|
@@ -69,16 +70,35 @@ module ActiveRecord
|
|
|
69
70
|
@configuration_hash = configuration_hash.merge(database: database).freeze
|
|
70
71
|
end
|
|
71
72
|
|
|
72
|
-
def
|
|
73
|
-
(
|
|
73
|
+
def max_connections
|
|
74
|
+
max_connections = configuration_hash.fetch(:max_connections) {
|
|
75
|
+
configuration_hash.fetch(:pool, 5)
|
|
76
|
+
}&.to_i
|
|
77
|
+
max_connections if max_connections && max_connections >= 0
|
|
74
78
|
end
|
|
75
79
|
|
|
80
|
+
def min_connections
|
|
81
|
+
(configuration_hash[:min_connections] || 0).to_i
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
alias :pool :max_connections
|
|
85
|
+
deprecate pool: :max_connections, deprecator: ActiveRecord.deprecator
|
|
86
|
+
|
|
76
87
|
def min_threads
|
|
77
88
|
(configuration_hash[:min_threads] || 0).to_i
|
|
78
89
|
end
|
|
79
90
|
|
|
80
91
|
def max_threads
|
|
81
|
-
(configuration_hash[:max_threads] ||
|
|
92
|
+
(configuration_hash[:max_threads] || (max_connections || 5).clamp(0, 5)).to_i
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def max_age
|
|
96
|
+
v = configuration_hash[:max_age]&.to_i
|
|
97
|
+
if v && v > 0
|
|
98
|
+
v
|
|
99
|
+
else
|
|
100
|
+
Float::INFINITY
|
|
101
|
+
end
|
|
82
102
|
end
|
|
83
103
|
|
|
84
104
|
def query_cache
|
|
@@ -93,10 +113,8 @@ module ActiveRecord
|
|
|
93
113
|
(configuration_hash[:checkout_timeout] || 5).to_f
|
|
94
114
|
end
|
|
95
115
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def reaping_frequency
|
|
99
|
-
configuration_hash.fetch(:reaping_frequency, 60)&.to_f
|
|
116
|
+
def reaping_frequency # :nodoc:
|
|
117
|
+
configuration_hash.fetch(:reaping_frequency, default_reaping_frequency)&.to_f
|
|
100
118
|
end
|
|
101
119
|
|
|
102
120
|
def idle_timeout
|
|
@@ -104,6 +122,11 @@ module ActiveRecord
|
|
|
104
122
|
timeout if timeout > 0
|
|
105
123
|
end
|
|
106
124
|
|
|
125
|
+
def keepalive
|
|
126
|
+
keepalive = (configuration_hash[:keepalive] || 600).to_f
|
|
127
|
+
keepalive if keepalive > 0
|
|
128
|
+
end
|
|
129
|
+
|
|
107
130
|
def adapter
|
|
108
131
|
configuration_hash[:adapter]&.to_s
|
|
109
132
|
end
|
|
@@ -130,6 +153,14 @@ module ActiveRecord
|
|
|
130
153
|
Base.configurations.primary?(name)
|
|
131
154
|
end
|
|
132
155
|
|
|
156
|
+
# Determines whether the db:prepare task should seed the database from db/seeds.rb.
|
|
157
|
+
#
|
|
158
|
+
# If the `seeds` key is present in the config, `seeds?` will return its value. Otherwise, it
|
|
159
|
+
# will return `true` for the primary database and `false` for all other configs.
|
|
160
|
+
def seeds?
|
|
161
|
+
configuration_hash.fetch(:seeds, primary?)
|
|
162
|
+
end
|
|
163
|
+
|
|
133
164
|
# Determines whether to dump the schema/structure files and the filename that
|
|
134
165
|
# should be used.
|
|
135
166
|
#
|
|
@@ -138,7 +169,7 @@ module ActiveRecord
|
|
|
138
169
|
#
|
|
139
170
|
# If the config option is set that will be used. Otherwise Rails will generate
|
|
140
171
|
# the filename from the database config name.
|
|
141
|
-
def schema_dump(format =
|
|
172
|
+
def schema_dump(format = schema_format)
|
|
142
173
|
if configuration_hash.key?(:schema_dump)
|
|
143
174
|
if config = configuration_hash[:schema_dump]
|
|
144
175
|
config
|
|
@@ -150,6 +181,12 @@ module ActiveRecord
|
|
|
150
181
|
end
|
|
151
182
|
end
|
|
152
183
|
|
|
184
|
+
def schema_format # :nodoc:
|
|
185
|
+
format = configuration_hash.fetch(:schema_format, ActiveRecord.schema_format).to_sym
|
|
186
|
+
raise "Invalid schema format" unless [:ruby, :sql].include?(format)
|
|
187
|
+
format
|
|
188
|
+
end
|
|
189
|
+
|
|
153
190
|
def database_tasks? # :nodoc:
|
|
154
191
|
!replica? && !!configuration_hash.fetch(:database_tasks, true)
|
|
155
192
|
end
|
|
@@ -160,13 +197,34 @@ module ActiveRecord
|
|
|
160
197
|
|
|
161
198
|
private
|
|
162
199
|
def schema_file_type(format)
|
|
163
|
-
case format
|
|
200
|
+
case format.to_sym
|
|
164
201
|
when :ruby
|
|
165
202
|
"schema.rb"
|
|
166
203
|
when :sql
|
|
167
204
|
"structure.sql"
|
|
168
205
|
end
|
|
169
206
|
end
|
|
207
|
+
|
|
208
|
+
def default_reaping_frequency
|
|
209
|
+
# Reap every 20 seconds by default, but run more often as necessary to
|
|
210
|
+
# meet other configured timeouts.
|
|
211
|
+
[20, idle_timeout, max_age, keepalive].compact.min
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def validate_configuration!
|
|
215
|
+
if configuration_hash[:pool] && configuration_hash[:max_connections]
|
|
216
|
+
pool_val = configuration_hash[:pool].to_i
|
|
217
|
+
max_conn_val = configuration_hash[:max_connections].to_i
|
|
218
|
+
|
|
219
|
+
if pool_val != max_conn_val
|
|
220
|
+
raise "Ambiguous configuration: 'pool' (#{pool_val}) and 'max_connections' (#{max_conn_val}) are set to different values. Prefer just 'max_connections'."
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
if configuration_hash[:pool] && configuration_hash[:min_connections]
|
|
225
|
+
raise "Ambiguous configuration: when setting 'min_connections', use 'max_connections' instead of 'pool'."
|
|
226
|
+
end
|
|
227
|
+
end
|
|
170
228
|
end
|
|
171
229
|
end
|
|
172
230
|
end
|
|
@@ -47,9 +47,8 @@ module ActiveRecord
|
|
|
47
47
|
@configuration_hash[:schema_dump] = false
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
end
|
|
50
|
+
query_cache = parse_query_cache
|
|
51
|
+
@configuration_hash[:query_cache] = query_cache unless query_cache.nil?
|
|
53
52
|
|
|
54
53
|
to_boolean!(@configuration_hash, :replica)
|
|
55
54
|
to_boolean!(@configuration_hash, :database_tasks)
|
|
@@ -58,6 +57,17 @@ module ActiveRecord
|
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
private
|
|
60
|
+
def parse_query_cache
|
|
61
|
+
case value = @configuration_hash[:query_cache]
|
|
62
|
+
when /\A\d+\z/
|
|
63
|
+
value.to_i
|
|
64
|
+
when "false"
|
|
65
|
+
false
|
|
66
|
+
else
|
|
67
|
+
value
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
61
71
|
def to_boolean!(configuration_hash, key)
|
|
62
72
|
if configuration_hash[key].is_a?(String)
|
|
63
73
|
configuration_hash[key] = configuration_hash[key] != "false"
|
|
@@ -36,9 +36,11 @@ module ActiveRecord
|
|
|
36
36
|
# to respond to `sharded?`. To implement this define the following in an
|
|
37
37
|
# initializer:
|
|
38
38
|
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
39
|
+
# ActiveSupport.on_load(:active_record_database_configurations) do
|
|
40
|
+
# ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
|
|
41
|
+
# next unless config.key?(:vitess)
|
|
42
|
+
# VitessConfig.new(env_name, name, config)
|
|
43
|
+
# end
|
|
42
44
|
# end
|
|
43
45
|
#
|
|
44
46
|
# Note: applications must handle the condition in which custom config should be
|
|
@@ -306,4 +308,6 @@ module ActiveRecord
|
|
|
306
308
|
url
|
|
307
309
|
end
|
|
308
310
|
end
|
|
311
|
+
|
|
312
|
+
ActiveSupport.run_load_hooks(:active_record_database_configurations, DatabaseConfigurations)
|
|
309
313
|
end
|
|
@@ -229,7 +229,7 @@ module ActiveRecord
|
|
|
229
229
|
# @entry.message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
|
|
230
230
|
# @entry.comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
|
|
231
231
|
def delegated_type(role, types:, **options)
|
|
232
|
-
belongs_to role, options.delete(:scope), **options
|
|
232
|
+
belongs_to role, options.delete(:scope), **options, polymorphic: true
|
|
233
233
|
define_delegated_type_methods role, types: types, options: options
|
|
234
234
|
end
|
|
235
235
|
|
|
@@ -7,16 +7,18 @@ module ActiveRecord
|
|
|
7
7
|
if self == Base
|
|
8
8
|
super
|
|
9
9
|
else
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
super || begin
|
|
11
|
+
match = Method.match(name)
|
|
12
|
+
match && match.valid?(self, name)
|
|
13
|
+
end
|
|
12
14
|
end
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def method_missing(name, ...)
|
|
16
|
-
match = Method.match(
|
|
18
|
+
match = Method.match(name)
|
|
17
19
|
|
|
18
|
-
if match && match.valid?
|
|
19
|
-
match.define
|
|
20
|
+
if match && match.valid?(self, name)
|
|
21
|
+
match.define(self, name)
|
|
20
22
|
send(name, ...)
|
|
21
23
|
else
|
|
22
24
|
super
|
|
@@ -24,97 +26,80 @@ module ActiveRecord
|
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
class Method
|
|
27
|
-
@matchers = []
|
|
28
|
-
|
|
29
29
|
class << self
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def match(model, name)
|
|
33
|
-
klass = matchers.find { |k| k.pattern.match?(name) }
|
|
34
|
-
klass.new(model, name) if klass
|
|
30
|
+
def match(name)
|
|
31
|
+
FindBy.match?(name) || FindByBang.match?(name)
|
|
35
32
|
end
|
|
36
33
|
|
|
37
|
-
def
|
|
38
|
-
|
|
34
|
+
def valid?(model, name)
|
|
35
|
+
attribute_names(model, name.to_s).all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
|
|
39
36
|
end
|
|
40
37
|
|
|
41
|
-
def
|
|
42
|
-
|
|
38
|
+
def define(model, name)
|
|
39
|
+
model.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
40
|
+
def self.#{name}(#{signature(model, name)})
|
|
41
|
+
#{body(model, name)}
|
|
42
|
+
end
|
|
43
|
+
CODE
|
|
43
44
|
end
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
private
|
|
47
|
+
def make_pattern(prefix, suffix)
|
|
48
|
+
/\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/
|
|
49
|
+
end
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
def attribute_names(model, name)
|
|
52
|
+
attribute_names = name.match(pattern)[1].split("_and_")
|
|
53
|
+
attribute_names.map! { |name| model.attribute_aliases[name] || name }
|
|
54
|
+
end
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@attribute_names = @name.match(self.class.pattern)[1].split("_and_")
|
|
56
|
-
@attribute_names.map! { |name| @model.attribute_aliases[name] || name }
|
|
57
|
-
end
|
|
56
|
+
def body(model, method_name)
|
|
57
|
+
"#{finder}(#{attributes_hash(model, method_name)})"
|
|
58
|
+
end
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
# The parameters in the signature may have reserved Ruby words, in order
|
|
61
|
+
# to prevent errors, we start each param name with `_`.
|
|
62
|
+
def signature(model, method_name)
|
|
63
|
+
attribute_names(model, method_name.to_s).map { |name| "_#{name}" }.join(", ")
|
|
64
|
+
end
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def
|
|
66
|
-
#{
|
|
66
|
+
# Given that the parameters starts with `_`, the finder needs to use the
|
|
67
|
+
# same parameter name.
|
|
68
|
+
def attributes_hash(model, method_name)
|
|
69
|
+
"{" + attribute_names(model, method_name).map { |name| ":#{name} => _#{name}" }.join(",") + "}"
|
|
67
70
|
end
|
|
68
|
-
CODE
|
|
69
71
|
end
|
|
72
|
+
end
|
|
70
73
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"#{finder}(#{attributes_hash})"
|
|
74
|
-
end
|
|
74
|
+
class FindBy < Method
|
|
75
|
+
@pattern = make_pattern("find_by", "")
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def signature
|
|
79
|
-
attribute_names.map { |name| "_#{name}" }.join(", ")
|
|
80
|
-
end
|
|
77
|
+
class << self
|
|
78
|
+
attr_reader :pattern
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def attributes_hash
|
|
85
|
-
"{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(",") + "}"
|
|
80
|
+
def match?(name)
|
|
81
|
+
pattern.match?(name) && self
|
|
86
82
|
end
|
|
87
83
|
|
|
88
84
|
def finder
|
|
89
|
-
|
|
85
|
+
"find_by"
|
|
90
86
|
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
class FindBy < Method
|
|
94
|
-
Method.matchers << self
|
|
95
|
-
|
|
96
|
-
def self.prefix
|
|
97
|
-
"find_by"
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def finder
|
|
101
|
-
"find_by"
|
|
102
87
|
end
|
|
103
88
|
end
|
|
104
89
|
|
|
105
90
|
class FindByBang < Method
|
|
106
|
-
|
|
91
|
+
@pattern = make_pattern("find_by", "!")
|
|
107
92
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
end
|
|
93
|
+
class << self
|
|
94
|
+
attr_reader :pattern
|
|
111
95
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
96
|
+
def match?(name)
|
|
97
|
+
pattern.match?(name) && self
|
|
98
|
+
end
|
|
115
99
|
|
|
116
|
-
|
|
117
|
-
|
|
100
|
+
def finder
|
|
101
|
+
"find_by!"
|
|
102
|
+
end
|
|
118
103
|
end
|
|
119
104
|
end
|
|
120
105
|
end
|