activerecord 7.2.2.1 → 8.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +564 -753
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +35 -11
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- 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 +10 -8
- 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/join_association.rb +25 -27
- 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 +17 -4
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +40 -26
- data/lib/active_record/autosave_association.rb +91 -39
- data/lib/active_record/base.rb +3 -4
- 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 +458 -117
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
- 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 +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
- 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 +7 -9
- 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 +10 -11
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- 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 +28 -45
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -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 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
- 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 +2 -19
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +37 -10
- data/lib/active_record/core.rb +61 -25
- data/lib/active_record/counter_cache.rb +34 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- 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 +19 -19
- 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 +9 -9
- data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
- data/lib/active_record/encryption/encryptor.rb +49 -28
- 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 +46 -42
- data/lib/active_record/errors.rb +36 -12
- 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/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +13 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- 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 +44 -11
- 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 +50 -43
- 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 +104 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +12 -12
- data/lib/active_record/railtie.rb +37 -32
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -37
- 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 +80 -63
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +54 -37
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -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 +4 -2
- data/lib/active_record/relation/query_methods.rb +156 -95
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +10 -11
- data/lib/active_record/relation.rb +122 -80
- 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 +47 -22
- 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/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +47 -18
- 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 +14 -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 +39 -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 +15 -2
- data/lib/active_record/type/serialized.rb +11 -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 +85 -50
- 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 +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -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/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +13 -4
- data/lib/arel/table.rb +3 -7
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +6 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +17 -17
- 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
|
|
|
@@ -170,9 +173,23 @@ module ActiveRecord
|
|
|
170
173
|
prevent_writes = true if role == ActiveRecord.reading_role
|
|
171
174
|
|
|
172
175
|
append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
begin
|
|
177
|
+
yield
|
|
178
|
+
ensure
|
|
179
|
+
connected_to_stack.pop
|
|
180
|
+
end
|
|
181
|
+
end
|
|
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
|
|
176
193
|
end
|
|
177
194
|
|
|
178
195
|
# Use a specified connection.
|
|
@@ -287,7 +304,7 @@ module ActiveRecord
|
|
|
287
304
|
|
|
288
305
|
# Checkouts a connection from the pool, yield it and then check it back in.
|
|
289
306
|
# If a connection was already leased via #lease_connection or a parent call to
|
|
290
|
-
# #with_connection, that same connection is
|
|
307
|
+
# #with_connection, that same connection is yielded.
|
|
291
308
|
# If #lease_connection is called inside the block, the connection won't be checked
|
|
292
309
|
# back in.
|
|
293
310
|
# If #connection is called inside the block, the connection won't be checked back in
|
|
@@ -359,6 +376,14 @@ module ActiveRecord
|
|
|
359
376
|
connection_pool.schema_cache.clear!
|
|
360
377
|
end
|
|
361
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
|
+
|
|
362
387
|
private
|
|
363
388
|
def resolve_config_for_connection(config_or_env)
|
|
364
389
|
raise "Anonymous class is not allowed." unless name
|
|
@@ -373,11 +398,13 @@ module ActiveRecord
|
|
|
373
398
|
prevent_writes = true if role == ActiveRecord.reading_role
|
|
374
399
|
|
|
375
400
|
append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self])
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
401
|
+
begin
|
|
402
|
+
return_value = yield
|
|
403
|
+
return_value.load if return_value.is_a? ActiveRecord::Relation
|
|
404
|
+
return_value
|
|
405
|
+
ensure
|
|
406
|
+
self.connected_to_stack.pop
|
|
407
|
+
end
|
|
381
408
|
end
|
|
382
409
|
|
|
383
410
|
def append_to_connected_to_stack(entry)
|
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
|
|
@@ -265,7 +276,7 @@ module ActiveRecord
|
|
|
265
276
|
return super if StatementCache.unsupported_value?(id)
|
|
266
277
|
|
|
267
278
|
cached_find_by([primary_key], [id]) ||
|
|
268
|
-
raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", name, primary_key, id))
|
|
279
|
+
raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id.inspect}", name, primary_key, id))
|
|
269
280
|
end
|
|
270
281
|
|
|
271
282
|
def find_by(*args) # :nodoc:
|
|
@@ -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
|
|
@@ -585,7 +601,7 @@ module ActiveRecord
|
|
|
585
601
|
#
|
|
586
602
|
# topic = Topic.new(title: "Budget", author_name: "Jason")
|
|
587
603
|
# topic.slice(:title, :author_name)
|
|
588
|
-
# => { "title" => "Budget", "author_name" => "Jason" }
|
|
604
|
+
# # => { "title" => "Budget", "author_name" => "Jason" }
|
|
589
605
|
#
|
|
590
606
|
#--
|
|
591
607
|
# Implemented by ActiveModel::Access#slice.
|
|
@@ -599,7 +615,7 @@ module ActiveRecord
|
|
|
599
615
|
#
|
|
600
616
|
# topic = Topic.new(title: "Budget", author_name: "Jason")
|
|
601
617
|
# topic.values_at(:title, :author_name)
|
|
602
|
-
# => ["Budget", "Jason"]
|
|
618
|
+
# # => ["Budget", "Jason"]
|
|
603
619
|
#
|
|
604
620
|
#--
|
|
605
621
|
# Implemented by ActiveModel::Access#values_at.
|
|
@@ -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
|
|
@@ -676,12 +692,14 @@ module ActiveRecord
|
|
|
676
692
|
# Sets the record to strict_loading mode. This will raise an error
|
|
677
693
|
# if the record tries to lazily load an association.
|
|
678
694
|
#
|
|
695
|
+
# NOTE: Strict loading is disabled during validation in order to let the record validate its association.
|
|
696
|
+
#
|
|
679
697
|
# user = User.first
|
|
680
698
|
# user.strict_loading! # => true
|
|
681
699
|
# user.address.city
|
|
682
|
-
# => ActiveRecord::StrictLoadingViolationError
|
|
700
|
+
# # => ActiveRecord::StrictLoadingViolationError
|
|
683
701
|
# user.comments.to_a
|
|
684
|
-
# => ActiveRecord::StrictLoadingViolationError
|
|
702
|
+
# # => ActiveRecord::StrictLoadingViolationError
|
|
685
703
|
#
|
|
686
704
|
# ==== Parameters
|
|
687
705
|
#
|
|
@@ -701,7 +719,7 @@ module ActiveRecord
|
|
|
701
719
|
# user.address.city # => "Tatooine"
|
|
702
720
|
# user.comments.to_a # => [#<Comment:0x00...]
|
|
703
721
|
# user.comments.first.ratings.to_a
|
|
704
|
-
# => ActiveRecord::StrictLoadingViolationError
|
|
722
|
+
# # => ActiveRecord::StrictLoadingViolationError
|
|
705
723
|
def strict_loading!(value = true, mode: :all)
|
|
706
724
|
unless [:all, :n_plus_one_only].include?(mode)
|
|
707
725
|
raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided."
|
|
@@ -723,11 +741,29 @@ module ActiveRecord
|
|
|
723
741
|
@strict_loading_mode == :all
|
|
724
742
|
end
|
|
725
743
|
|
|
726
|
-
#
|
|
744
|
+
# Prevents records from being written to the database:
|
|
745
|
+
#
|
|
746
|
+
# customer = Customer.new
|
|
747
|
+
# customer.readonly!
|
|
748
|
+
# customer.save # raises ActiveRecord::ReadOnlyRecord
|
|
727
749
|
#
|
|
728
750
|
# customer = Customer.first
|
|
729
751
|
# customer.readonly!
|
|
730
|
-
# customer.
|
|
752
|
+
# customer.update(name: 'New Name') # raises ActiveRecord::ReadOnlyRecord
|
|
753
|
+
#
|
|
754
|
+
# Read-only records cannot be deleted from the database either:
|
|
755
|
+
#
|
|
756
|
+
# customer = Customer.first
|
|
757
|
+
# customer.readonly!
|
|
758
|
+
# customer.destroy # raises ActiveRecord::ReadOnlyRecord
|
|
759
|
+
#
|
|
760
|
+
# Please, note that the objects themselves are still mutable in memory:
|
|
761
|
+
#
|
|
762
|
+
# customer = Customer.new
|
|
763
|
+
# customer.readonly!
|
|
764
|
+
# customer.name = 'New Name' # OK
|
|
765
|
+
#
|
|
766
|
+
# but you won't be able to persist the changes.
|
|
731
767
|
def readonly!
|
|
732
768
|
@readonly = true
|
|
733
769
|
end
|
|
@@ -810,7 +846,7 @@ module ActiveRecord
|
|
|
810
846
|
|
|
811
847
|
@primary_key = klass.primary_key
|
|
812
848
|
@strict_loading = klass.strict_loading_by_default
|
|
813
|
-
@strict_loading_mode =
|
|
849
|
+
@strict_loading_mode = klass.strict_loading_mode
|
|
814
850
|
|
|
815
851
|
klass.define_attribute_methods
|
|
816
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
|
-
# #
|
|
31
|
+
# # For posts with ids #1 and #2, reset the comments_count
|
|
32
|
+
# Post.reset_counters([1, 2], :comments)
|
|
33
|
+
#
|
|
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
|
|
@@ -81,7 +81,9 @@ module ActiveRecord
|
|
|
81
81
|
|
|
82
82
|
def resolved_adapter
|
|
83
83
|
adapter = uri.scheme && @uri.scheme.tr("-", "_")
|
|
84
|
-
adapter
|
|
84
|
+
if adapter && ActiveRecord.protocol_adapters[adapter]
|
|
85
|
+
adapter = ActiveRecord.protocol_adapters[adapter]
|
|
86
|
+
end
|
|
85
87
|
adapter
|
|
86
88
|
end
|
|
87
89
|
|
|
@@ -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
|