activerecord 8.0.3 → 8.1.0.rc1
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 +520 -514
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +2 -0
- data/lib/active_record/associations/builder/association.rb +16 -5
- 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_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/join_dependency.rb +2 -0
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +0 -2
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +405 -72
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -3
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -22
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -20
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -13
- 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/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +17 -15
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- 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/schema_creation.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +66 -31
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +81 -48
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +23 -7
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -30
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +2 -1
- data/lib/active_record/core.rb +5 -4
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +53 -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/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/encryptor.rb +12 -0
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +20 -4
- 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 -2
- 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 +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +2 -6
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +14 -1
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +26 -16
- data/lib/active_record/model_schema.rb +36 -10
- data/lib/active_record/nested_attributes.rb +2 -0
- data/lib/active_record/persistence.rb +34 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +3 -7
- data/lib/active_record/railtie.rb +32 -3
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +15 -3
- 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 +42 -3
- data/lib/active_record/relation/batches.rb +25 -11
- data/lib/active_record/relation/calculations.rb +20 -9
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +27 -11
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +9 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +38 -28
- data/lib/active_record/relation/where_clause.rb +1 -8
- data/lib/active_record/relation.rb +24 -12
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +41 -58
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +12 -10
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +25 -34
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +27 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +32 -10
- 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.rb +65 -3
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +6 -11
- 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 +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +7 -2
- 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 +14 -10
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
|
@@ -3,80 +3,63 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
# This is a thread locals registry for Active Record. For example:
|
|
5
5
|
#
|
|
6
|
-
# ActiveRecord::RuntimeRegistry.sql_runtime
|
|
6
|
+
# ActiveRecord::RuntimeRegistry.stats.sql_runtime
|
|
7
7
|
#
|
|
8
8
|
# returns the connection handler local to the current unit of execution (either thread of fiber).
|
|
9
9
|
module RuntimeRegistry # :nodoc:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
class Stats
|
|
11
|
+
attr_accessor :sql_runtime, :async_sql_runtime, :queries_count, :cached_queries_count
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@sql_runtime = 0.0
|
|
15
|
+
@async_sql_runtime = 0.0
|
|
16
|
+
@queries_count = 0
|
|
17
|
+
@cached_queries_count = 0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reset_runtimes
|
|
21
|
+
sql_runtime_was = @sql_runtime
|
|
22
|
+
@sql_runtime = 0.0
|
|
23
|
+
@async_sql_runtime = 0.0
|
|
24
|
+
sql_runtime_was
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
public alias_method :reset, :initialize
|
|
14
28
|
end
|
|
15
29
|
|
|
16
|
-
|
|
17
|
-
ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def async_sql_runtime
|
|
21
|
-
ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
|
|
22
|
-
end
|
|
30
|
+
extend self
|
|
23
31
|
|
|
24
|
-
def
|
|
25
|
-
|
|
32
|
+
def call(name, start, finish, id, payload)
|
|
33
|
+
record(
|
|
34
|
+
payload[:name],
|
|
35
|
+
(finish - start) * 1_000.0,
|
|
36
|
+
async: payload[:async],
|
|
37
|
+
lock_wait: payload[:lock_wait],
|
|
38
|
+
)
|
|
26
39
|
end
|
|
27
40
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
end
|
|
41
|
+
def record(query_name, runtime, cached: false, async: false, lock_wait: nil)
|
|
42
|
+
stats = self.stats
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
unless query_name == "TRANSACTION" || query_name == "SCHEMA"
|
|
45
|
+
stats.queries_count += 1
|
|
46
|
+
stats.cached_queries_count += 1 if cached
|
|
47
|
+
end
|
|
35
48
|
|
|
36
|
-
|
|
37
|
-
|
|
49
|
+
if async
|
|
50
|
+
stats.async_sql_runtime += (runtime - lock_wait)
|
|
51
|
+
end
|
|
52
|
+
stats.sql_runtime += runtime
|
|
38
53
|
end
|
|
39
54
|
|
|
40
|
-
def
|
|
41
|
-
ActiveSupport::IsolatedExecutionState[:
|
|
55
|
+
def stats
|
|
56
|
+
ActiveSupport::IsolatedExecutionState[:active_record_runtime] ||= Stats.new
|
|
42
57
|
end
|
|
43
58
|
|
|
44
59
|
def reset
|
|
45
|
-
|
|
46
|
-
reset_queries_count
|
|
47
|
-
reset_cached_queries_count
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def reset_runtimes
|
|
51
|
-
rt, self.sql_runtime = sql_runtime, 0.0
|
|
52
|
-
self.async_sql_runtime = 0.0
|
|
53
|
-
rt
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def reset_queries_count
|
|
57
|
-
qc = queries_count
|
|
58
|
-
self.queries_count = 0
|
|
59
|
-
qc
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def reset_cached_queries_count
|
|
63
|
-
qc = cached_queries_count
|
|
64
|
-
self.cached_queries_count = 0
|
|
65
|
-
qc
|
|
60
|
+
stats.reset
|
|
66
61
|
end
|
|
67
62
|
end
|
|
68
63
|
end
|
|
69
64
|
|
|
70
|
-
ActiveSupport::Notifications.monotonic_subscribe("sql.active_record"
|
|
71
|
-
unless ["SCHEMA", "TRANSACTION"].include?(payload[:name])
|
|
72
|
-
ActiveRecord::RuntimeRegistry.queries_count += 1
|
|
73
|
-
ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
runtime = (finish - start) * 1_000.0
|
|
77
|
-
|
|
78
|
-
if payload[:async]
|
|
79
|
-
ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
|
|
80
|
-
end
|
|
81
|
-
ActiveRecord::RuntimeRegistry.sql_runtime += runtime
|
|
82
|
-
end
|
|
65
|
+
ActiveSupport::Notifications.monotonic_subscribe("sql.active_record", ActiveRecord::RuntimeRegistry)
|
|
@@ -161,6 +161,8 @@ module ActiveRecord
|
|
|
161
161
|
#
|
|
162
162
|
# sanitize_sql_array(["role = ?", 0])
|
|
163
163
|
# # => "role = '0'"
|
|
164
|
+
#
|
|
165
|
+
# Before using this method, please consider if Arel.sql would be better for your use-case
|
|
164
166
|
def sanitize_sql_array(ary)
|
|
165
167
|
statement, *values = ary
|
|
166
168
|
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
|
|
@@ -165,7 +165,7 @@ module ActiveRecord
|
|
|
165
165
|
# first dump primary key column
|
|
166
166
|
pk = @connection.primary_key(table)
|
|
167
167
|
|
|
168
|
-
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
|
|
168
|
+
tbl.print " create_table #{relation_name(remove_prefix_and_suffix(table)).inspect}"
|
|
169
169
|
|
|
170
170
|
case pk
|
|
171
171
|
when String
|
|
@@ -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.each do |column|
|
|
195
|
+
columns.sort_by(&:name).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
|
|
|
@@ -233,7 +233,7 @@ module ActiveRecord
|
|
|
233
233
|
if (indexes = @connection.indexes(table)).any?
|
|
234
234
|
add_index_statements = indexes.map do |index|
|
|
235
235
|
table_name = remove_prefix_and_suffix(index.table).inspect
|
|
236
|
-
" add_index #{([table_name] + index_parts(index)).join(', ')}"
|
|
236
|
+
" add_index #{([relation_name(table_name)] + index_parts(index)).join(', ')}"
|
|
237
237
|
end
|
|
238
238
|
|
|
239
239
|
stream.puts add_index_statements.sort.join("\n")
|
|
@@ -277,6 +277,7 @@ module ActiveRecord
|
|
|
277
277
|
index_parts << "nulls_not_distinct: #{index.nulls_not_distinct.inspect}" if index.nulls_not_distinct
|
|
278
278
|
index_parts << "type: #{index.type.inspect}" if index.type
|
|
279
279
|
index_parts << "comment: #{index.comment.inspect}" if index.comment
|
|
280
|
+
index_parts << "enabled: #{index.enabled.inspect}" if @connection.supports_disabling_indexes? && index.disabled?
|
|
280
281
|
index_parts
|
|
281
282
|
end
|
|
282
283
|
|
|
@@ -317,8 +318,8 @@ module ActiveRecord
|
|
|
317
318
|
if (foreign_keys = @connection.foreign_keys(table)).any?
|
|
318
319
|
add_foreign_key_statements = foreign_keys.map do |foreign_key|
|
|
319
320
|
parts = [
|
|
320
|
-
|
|
321
|
-
remove_prefix_and_suffix(foreign_key.to_table).inspect,
|
|
321
|
+
relation_name(remove_prefix_and_suffix(foreign_key.from_table)).inspect,
|
|
322
|
+
relation_name(remove_prefix_and_suffix(foreign_key.to_table)).inspect,
|
|
322
323
|
]
|
|
323
324
|
|
|
324
325
|
if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table, "id")
|
|
@@ -329,16 +330,13 @@ module ActiveRecord
|
|
|
329
330
|
parts << "primary_key: #{foreign_key.primary_key.inspect}"
|
|
330
331
|
end
|
|
331
332
|
|
|
332
|
-
if foreign_key.export_name_on_schema_dump?
|
|
333
|
-
parts << "name: #{foreign_key.name.inspect}"
|
|
334
|
-
end
|
|
335
|
-
|
|
333
|
+
parts << "name: #{foreign_key.name.inspect}" if foreign_key.export_name_on_schema_dump?
|
|
336
334
|
parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
|
|
337
335
|
parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
|
|
338
336
|
parts << "deferrable: #{foreign_key.deferrable.inspect}" if foreign_key.deferrable
|
|
339
337
|
parts << "validate: #{foreign_key.validate?.inspect}" unless foreign_key.validate?
|
|
340
338
|
|
|
341
|
-
" #{parts.join(', ')}"
|
|
339
|
+
" add_foreign_key #{parts.join(', ')}"
|
|
342
340
|
end
|
|
343
341
|
|
|
344
342
|
stream.puts add_foreign_key_statements.sort.join("\n")
|
|
@@ -363,6 +361,10 @@ module ActiveRecord
|
|
|
363
361
|
end
|
|
364
362
|
end
|
|
365
363
|
|
|
364
|
+
def relation_name(name)
|
|
365
|
+
name
|
|
366
|
+
end
|
|
367
|
+
|
|
366
368
|
def remove_prefix_and_suffix(table)
|
|
367
369
|
# This method appears at the top when profiling active_record test cases run.
|
|
368
370
|
# Avoid costly calculation when there are no prefix and suffix.
|
|
@@ -6,11 +6,27 @@ module ActiveRecord
|
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
8
|
included do
|
|
9
|
+
class_attribute :_signed_id_verifier, instance_accessor: false, instance_predicate: false
|
|
10
|
+
|
|
9
11
|
##
|
|
10
12
|
# :singleton-method:
|
|
11
13
|
# Set the secret used for the signed id verifier instance when using Active Record outside of \Rails.
|
|
12
14
|
# Within \Rails, this is automatically set using the \Rails application key generator.
|
|
13
15
|
class_attribute :signed_id_verifier_secret, instance_writer: false
|
|
16
|
+
module DeprecateSignedIdVerifierSecret
|
|
17
|
+
def signed_id_verifier_secret=(secret)
|
|
18
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
|
19
|
+
ActiveRecord::Base.signed_id_verifier_secret is deprecated and will be removed in Rails 8.2.
|
|
20
|
+
|
|
21
|
+
If the secret is model-specific, set Model.signed_id_verifier instead.
|
|
22
|
+
|
|
23
|
+
Otherwise, configure Rails.application.message_verifiers (or ActiveRecord.message_verifiers) with the secret.
|
|
24
|
+
MSG
|
|
25
|
+
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
singleton_class.prepend DeprecateSignedIdVerifierSecret
|
|
14
30
|
end
|
|
15
31
|
|
|
16
32
|
module RelationMethods # :nodoc:
|
|
@@ -49,10 +65,11 @@ module ActiveRecord
|
|
|
49
65
|
#
|
|
50
66
|
# travel_back
|
|
51
67
|
# User.find_signed signed_id, purpose: :password_reset # => User.first
|
|
52
|
-
def find_signed(signed_id, purpose: nil)
|
|
68
|
+
def find_signed(signed_id, purpose: nil, on_rotation: nil)
|
|
53
69
|
raise UnknownPrimaryKey.new(self) if primary_key.nil?
|
|
54
70
|
|
|
55
|
-
|
|
71
|
+
options = { on_rotation: on_rotation }.compact
|
|
72
|
+
if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose), **options)
|
|
56
73
|
find_by primary_key => id
|
|
57
74
|
end
|
|
58
75
|
end
|
|
@@ -69,26 +86,33 @@ module ActiveRecord
|
|
|
69
86
|
# signed_id = User.first.signed_id
|
|
70
87
|
# User.first.destroy
|
|
71
88
|
# User.find_signed! signed_id # => ActiveRecord::RecordNotFound
|
|
72
|
-
def find_signed!(signed_id, purpose: nil)
|
|
73
|
-
|
|
89
|
+
def find_signed!(signed_id, purpose: nil, on_rotation: nil)
|
|
90
|
+
options = { on_rotation: on_rotation }.compact
|
|
91
|
+
if id = signed_id_verifier.verify(signed_id, purpose: combine_signed_id_purposes(purpose), **options)
|
|
74
92
|
find(id)
|
|
75
93
|
end
|
|
76
94
|
end
|
|
77
95
|
|
|
78
|
-
# The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
|
|
79
|
-
# with the class-level +signed_id_verifier_secret+, which within Rails comes from
|
|
80
|
-
# {Rails.application.key_generator}[rdoc-ref:Rails::Application#key_generator].
|
|
81
|
-
# By default, it's SHA256 for the digest and JSON for the serialization.
|
|
82
96
|
def signed_id_verifier
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
97
|
+
if signed_id_verifier_secret
|
|
98
|
+
@signed_id_verifier ||= begin
|
|
99
|
+
secret = signed_id_verifier_secret
|
|
100
|
+
secret = secret.call if secret.respond_to?(:call)
|
|
101
|
+
|
|
102
|
+
if secret.nil?
|
|
103
|
+
raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed IDs"
|
|
104
|
+
end
|
|
86
105
|
|
|
87
|
-
if secret.nil?
|
|
88
|
-
raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
|
|
89
|
-
else
|
|
90
106
|
ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON, url_safe: true
|
|
91
107
|
end
|
|
108
|
+
else
|
|
109
|
+
return _signed_id_verifier if _signed_id_verifier
|
|
110
|
+
|
|
111
|
+
if ActiveRecord.message_verifiers.nil?
|
|
112
|
+
raise "You must set ActiveRecord.message_verifiers to use signed IDs"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
ActiveRecord.message_verifiers["active_record/signed_id"]
|
|
92
116
|
end
|
|
93
117
|
end
|
|
94
118
|
|
|
@@ -96,7 +120,11 @@ module ActiveRecord
|
|
|
96
120
|
# verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
|
|
97
121
|
# your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details.
|
|
98
122
|
def signed_id_verifier=(verifier)
|
|
99
|
-
|
|
123
|
+
if signed_id_verifier_secret
|
|
124
|
+
@signed_id_verifier = verifier
|
|
125
|
+
else
|
|
126
|
+
self._signed_id_verifier = verifier
|
|
127
|
+
end
|
|
100
128
|
end
|
|
101
129
|
|
|
102
130
|
# :nodoc:
|
|
@@ -31,8 +31,11 @@ module ActiveRecord
|
|
|
31
31
|
class Substitute; end # :nodoc:
|
|
32
32
|
|
|
33
33
|
class Query # :nodoc:
|
|
34
|
-
|
|
34
|
+
attr_reader :retryable
|
|
35
|
+
|
|
36
|
+
def initialize(sql, retryable:)
|
|
35
37
|
@sql = sql
|
|
38
|
+
@retryable = retryable
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
def sql_for(binds, connection)
|
|
@@ -41,11 +44,12 @@ module ActiveRecord
|
|
|
41
44
|
end
|
|
42
45
|
|
|
43
46
|
class PartialQuery < Query # :nodoc:
|
|
44
|
-
def initialize(values)
|
|
47
|
+
def initialize(values, retryable:)
|
|
45
48
|
@values = values
|
|
46
49
|
@indexes = values.each_with_index.find_all { |thing, i|
|
|
47
50
|
Substitute === thing
|
|
48
51
|
}.map(&:last)
|
|
52
|
+
@retryable = retryable
|
|
49
53
|
end
|
|
50
54
|
|
|
51
55
|
def sql_for(binds, connection)
|
|
@@ -94,12 +98,12 @@ module ActiveRecord
|
|
|
94
98
|
end
|
|
95
99
|
end
|
|
96
100
|
|
|
97
|
-
def self.query(
|
|
98
|
-
Query.new(
|
|
101
|
+
def self.query(...)
|
|
102
|
+
Query.new(...)
|
|
99
103
|
end
|
|
100
104
|
|
|
101
|
-
def self.partial_query(
|
|
102
|
-
PartialQuery.new(
|
|
105
|
+
def self.partial_query(...)
|
|
106
|
+
PartialQuery.new(...)
|
|
103
107
|
end
|
|
104
108
|
|
|
105
109
|
def self.partial_query_collector
|
|
@@ -142,14 +146,14 @@ module ActiveRecord
|
|
|
142
146
|
@model = model
|
|
143
147
|
end
|
|
144
148
|
|
|
145
|
-
def execute(params, connection,
|
|
149
|
+
def execute(params, connection, async: false, &block)
|
|
146
150
|
bind_values = @bind_map.bind params
|
|
147
151
|
sql = @query_builder.sql_for bind_values, connection
|
|
148
152
|
|
|
149
153
|
if async
|
|
150
|
-
@model.async_find_by_sql(sql, bind_values, preparable: true, allow_retry:
|
|
154
|
+
@model.async_find_by_sql(sql, bind_values, preparable: true, allow_retry: @query_builder.retryable, &block)
|
|
151
155
|
else
|
|
152
|
-
@model.find_by_sql(sql, bind_values, preparable: true, allow_retry:
|
|
156
|
+
@model.find_by_sql(sql, bind_values, preparable: true, allow_retry: @query_builder.retryable, &block)
|
|
153
157
|
end
|
|
154
158
|
rescue ::RangeError
|
|
155
159
|
async ? Promise.wrap([]) : []
|
data/lib/active_record/store.rb
CHANGED
|
@@ -146,37 +146,43 @@ module ActiveRecord
|
|
|
146
146
|
define_method("#{accessor_key}_changed?") do
|
|
147
147
|
return false unless attribute_changed?(store_attribute)
|
|
148
148
|
prev_store, new_store = changes[store_attribute]
|
|
149
|
-
|
|
149
|
+
accessor = store_accessor_for(store_attribute)
|
|
150
|
+
accessor.get(prev_store, key) != accessor.get(new_store, key)
|
|
150
151
|
end
|
|
151
152
|
|
|
152
153
|
define_method("#{accessor_key}_change") do
|
|
153
154
|
return unless attribute_changed?(store_attribute)
|
|
154
155
|
prev_store, new_store = changes[store_attribute]
|
|
155
|
-
|
|
156
|
+
accessor = store_accessor_for(store_attribute)
|
|
157
|
+
[accessor.get(prev_store, key), accessor.get(new_store, key)]
|
|
156
158
|
end
|
|
157
159
|
|
|
158
160
|
define_method("#{accessor_key}_was") do
|
|
159
161
|
return unless attribute_changed?(store_attribute)
|
|
160
162
|
prev_store, _new_store = changes[store_attribute]
|
|
161
|
-
|
|
163
|
+
accessor = store_accessor_for(store_attribute)
|
|
164
|
+
accessor.get(prev_store, key)
|
|
162
165
|
end
|
|
163
166
|
|
|
164
167
|
define_method("saved_change_to_#{accessor_key}?") do
|
|
165
168
|
return false unless saved_change_to_attribute?(store_attribute)
|
|
166
169
|
prev_store, new_store = saved_changes[store_attribute]
|
|
167
|
-
|
|
170
|
+
accessor = store_accessor_for(store_attribute)
|
|
171
|
+
accessor.get(prev_store, key) != accessor.get(new_store, key)
|
|
168
172
|
end
|
|
169
173
|
|
|
170
174
|
define_method("saved_change_to_#{accessor_key}") do
|
|
171
175
|
return unless saved_change_to_attribute?(store_attribute)
|
|
172
176
|
prev_store, new_store = saved_changes[store_attribute]
|
|
173
|
-
|
|
177
|
+
accessor = store_accessor_for(store_attribute)
|
|
178
|
+
[accessor.get(prev_store, key), accessor.get(new_store, key)]
|
|
174
179
|
end
|
|
175
180
|
|
|
176
181
|
define_method("#{accessor_key}_before_last_save") do
|
|
177
182
|
return unless saved_change_to_attribute?(store_attribute)
|
|
178
183
|
prev_store, _new_store = saved_changes[store_attribute]
|
|
179
|
-
|
|
184
|
+
accessor = store_accessor_for(store_attribute)
|
|
185
|
+
accessor.get(prev_store, key)
|
|
180
186
|
end
|
|
181
187
|
end
|
|
182
188
|
end
|
|
@@ -225,39 +231,58 @@ module ActiveRecord
|
|
|
225
231
|
end
|
|
226
232
|
|
|
227
233
|
class HashAccessor # :nodoc:
|
|
234
|
+
def self.get(store_object, key)
|
|
235
|
+
if store_object
|
|
236
|
+
store_object[key]
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
228
240
|
def self.read(object, attribute, key)
|
|
229
|
-
prepare(object, attribute)
|
|
230
|
-
|
|
241
|
+
store_object = prepare(object, attribute)
|
|
242
|
+
store_object[key]
|
|
231
243
|
end
|
|
232
244
|
|
|
233
245
|
def self.write(object, attribute, key, value)
|
|
234
|
-
prepare(object, attribute)
|
|
235
|
-
|
|
246
|
+
store_object = prepare(object, attribute)
|
|
247
|
+
store_object[key] = value if value != store_object[key]
|
|
236
248
|
end
|
|
237
249
|
|
|
238
250
|
def self.prepare(object, attribute)
|
|
239
|
-
|
|
251
|
+
store_object = object.public_send(attribute)
|
|
252
|
+
|
|
253
|
+
if store_object.nil?
|
|
254
|
+
store_object = {}
|
|
255
|
+
object.public_send(:"#{attribute}=", store_object)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
store_object
|
|
240
259
|
end
|
|
241
260
|
end
|
|
242
261
|
|
|
243
262
|
class StringKeyedHashAccessor < HashAccessor # :nodoc:
|
|
263
|
+
def self.get(store_object, key)
|
|
264
|
+
super store_object, Symbol === key ? key.name : key.to_s
|
|
265
|
+
end
|
|
266
|
+
|
|
244
267
|
def self.read(object, attribute, key)
|
|
245
|
-
super object, attribute, key.to_s
|
|
268
|
+
super object, attribute, Symbol === key ? key.name : key.to_s
|
|
246
269
|
end
|
|
247
270
|
|
|
248
271
|
def self.write(object, attribute, key, value)
|
|
249
|
-
super object, attribute, key.to_s, value
|
|
272
|
+
super object, attribute, Symbol === key ? key.name : key.to_s, value
|
|
250
273
|
end
|
|
251
274
|
end
|
|
252
275
|
|
|
253
276
|
class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
|
|
254
|
-
def self.prepare(object,
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
277
|
+
def self.prepare(object, attribute)
|
|
278
|
+
store_object = object.public_send(attribute)
|
|
279
|
+
|
|
280
|
+
unless store_object.is_a?(ActiveSupport::HashWithIndifferentAccess)
|
|
281
|
+
store_object = IndifferentCoder.as_indifferent_hash(store_object)
|
|
282
|
+
object.public_send :"#{attribute}=", store_object
|
|
259
283
|
end
|
|
260
|
-
|
|
284
|
+
|
|
285
|
+
store_object
|
|
261
286
|
end
|
|
262
287
|
end
|
|
263
288
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/structured_event_subscriber"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
class StructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber # :nodoc:
|
|
7
|
+
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
|
|
8
|
+
|
|
9
|
+
def strict_loading_violation(event)
|
|
10
|
+
owner = event.payload[:owner]
|
|
11
|
+
reflection = event.payload[:reflection]
|
|
12
|
+
|
|
13
|
+
emit_debug_event("active_record.strict_loading_violation",
|
|
14
|
+
owner: owner.name,
|
|
15
|
+
class: reflection.klass.name,
|
|
16
|
+
name: reflection.name,
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
debug_only :strict_loading_violation
|
|
20
|
+
|
|
21
|
+
def sql(event)
|
|
22
|
+
payload = event.payload
|
|
23
|
+
|
|
24
|
+
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
|
25
|
+
|
|
26
|
+
binds = nil
|
|
27
|
+
|
|
28
|
+
if payload[:binds]&.any?
|
|
29
|
+
casted_params = type_casted_binds(payload[:type_casted_binds])
|
|
30
|
+
|
|
31
|
+
binds = []
|
|
32
|
+
payload[:binds].each_with_index do |attr, i|
|
|
33
|
+
attribute_name = if attr.respond_to?(:name)
|
|
34
|
+
attr.name
|
|
35
|
+
elsif attr.respond_to?(:[]) && attr[i].respond_to?(:name)
|
|
36
|
+
attr[i].name
|
|
37
|
+
else
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
filtered_params = filter(attribute_name, casted_params[i])
|
|
42
|
+
|
|
43
|
+
binds << render_bind(attr, filtered_params)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
emit_debug_event("active_record.sql",
|
|
48
|
+
async: payload[:async],
|
|
49
|
+
name: payload[:name],
|
|
50
|
+
sql: payload[:sql],
|
|
51
|
+
cached: payload[:cached],
|
|
52
|
+
lock_wait: payload[:lock_wait],
|
|
53
|
+
binds: binds,
|
|
54
|
+
duration_ms: event.duration.round(2),
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
debug_only :sql
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
def type_casted_binds(casted_binds)
|
|
61
|
+
casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def render_bind(attr, value)
|
|
65
|
+
case attr
|
|
66
|
+
when ActiveModel::Attribute
|
|
67
|
+
if attr.type.binary? && attr.value
|
|
68
|
+
value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
|
|
69
|
+
end
|
|
70
|
+
when Array
|
|
71
|
+
attr = attr.first
|
|
72
|
+
else
|
|
73
|
+
attr = nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
[attr&.name, value]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def filter(name, value)
|
|
80
|
+
ActiveRecord::Base.inspection_filter.filter_param(name, value)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
ActiveRecord::StructuredEventSubscriber.attach_to :active_record
|
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
class TableMetadata # :nodoc:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def initialize(klass, arel_table, reflection = nil)
|
|
5
|
+
def initialize(klass, arel_table)
|
|
8
6
|
@klass = klass
|
|
9
7
|
@arel_table = arel_table
|
|
10
|
-
@reflection = reflection
|
|
11
8
|
end
|
|
12
9
|
|
|
13
10
|
def primary_key
|
|
@@ -22,7 +19,7 @@ module ActiveRecord
|
|
|
22
19
|
klass&.columns_hash&.key?(column_name)
|
|
23
20
|
end
|
|
24
21
|
|
|
25
|
-
def associated_with
|
|
22
|
+
def associated_with(table_name)
|
|
26
23
|
klass&._reflect_on_association(table_name)
|
|
27
24
|
end
|
|
28
25
|
|
|
@@ -42,26 +39,14 @@ module ActiveRecord
|
|
|
42
39
|
if association_klass
|
|
43
40
|
arel_table = association_klass.arel_table
|
|
44
41
|
arel_table = arel_table.alias(table_name) if arel_table.name != table_name
|
|
45
|
-
TableMetadata.new(association_klass, arel_table
|
|
42
|
+
TableMetadata.new(association_klass, arel_table)
|
|
46
43
|
else
|
|
47
44
|
type_caster = TypeCaster::Connection.new(klass, table_name)
|
|
48
45
|
arel_table = Arel::Table.new(table_name, type_caster: type_caster)
|
|
49
|
-
TableMetadata.new(nil, arel_table
|
|
46
|
+
TableMetadata.new(nil, arel_table)
|
|
50
47
|
end
|
|
51
48
|
end
|
|
52
49
|
|
|
53
|
-
def polymorphic_association?
|
|
54
|
-
reflection&.polymorphic?
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def polymorphic_name_association
|
|
58
|
-
reflection&.polymorphic_name
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def through_association?
|
|
62
|
-
reflection&.through_reflection?
|
|
63
|
-
end
|
|
64
|
-
|
|
65
50
|
def reflect_on_aggregation(aggregation_name)
|
|
66
51
|
klass&.reflect_on_aggregation(aggregation_name)
|
|
67
52
|
end
|
|
@@ -78,6 +63,6 @@ module ActiveRecord
|
|
|
78
63
|
attr_reader :arel_table
|
|
79
64
|
|
|
80
65
|
private
|
|
81
|
-
attr_reader :klass
|
|
66
|
+
attr_reader :klass
|
|
82
67
|
end
|
|
83
68
|
end
|