activerecord 8.0.0 → 8.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1381 -241
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +35 -17
- data/lib/active_record/associations/association_scope.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +20 -4
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +36 -8
- 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 +9 -5
- data/lib/active_record/associations/collection_proxy.rb +26 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/foreign_association.rb +3 -1
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +5 -4
- data/lib/active_record/associations/preloader/association.rb +1 -1
- 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.rb +159 -21
- data/lib/active_record/attribute_methods/primary_key.rb +2 -1
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +19 -6
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attribute_methods.rb +24 -17
- data/lib/active_record/attributes.rb +40 -26
- data/lib/active_record/autosave_association.rb +22 -12
- data/lib/active_record/base.rb +3 -4
- data/lib/active_record/callbacks.rb +1 -2
- data/lib/active_record/coders/column_serializer.rb +1 -1
- data/lib/active_record/coders/json.rb +16 -6
- data/lib/active_record/coders/yaml_column.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +19 -18
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +474 -112
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +68 -41
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -9
- 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 +31 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +94 -28
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -64
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +133 -40
- data/lib/active_record/connection_adapters/column.rb +14 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
- 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 +40 -25
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +66 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
- 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 +29 -33
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
- 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 +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +70 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +99 -54
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +40 -12
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -23
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +15 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +7 -14
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +68 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +8 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +16 -2
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +15 -10
- data/lib/active_record/core.rb +44 -12
- data/lib/active_record/counter_cache.rb +36 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +66 -9
- data/lib/active_record/database_configurations/url_config.rb +15 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +19 -19
- data/lib/active_record/disable_joins_association_relation.rb +5 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
- data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
- data/lib/active_record/encryption/encryptor.rb +39 -25
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +37 -20
- data/lib/active_record/errors.rb +23 -7
- 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 +76 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/future_result.rb +5 -3
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +9 -4
- data/lib/active_record/insert_all.rb +14 -9
- 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 +30 -22
- data/lib/active_record/message_pack.rb +13 -0
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +32 -6
- 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 +31 -21
- data/lib/active_record/model_schema.rb +38 -12
- data/lib/active_record/nested_attributes.rb +10 -6
- data/lib/active_record/persistence.rb +35 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +7 -7
- data/lib/active_record/querying.rb +17 -5
- data/lib/active_record/railtie.rb +35 -6
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +24 -20
- 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 +36 -1
- data/lib/active_record/relation/batches.rb +25 -11
- data/lib/active_record/relation/calculations.rb +62 -41
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +76 -45
- data/lib/active_record/relation/merger.rb +4 -2
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +10 -8
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +106 -45
- data/lib/active_record/relation/spawn_methods.rb +6 -6
- data/lib/active_record/relation/where_clause.rb +10 -11
- data/lib/active_record/relation.rb +63 -31
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +41 -21
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +48 -19
- data/lib/active_record/statement_cache.rb +16 -12
- data/lib/active_record/store.rb +46 -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 +83 -0
- data/lib/active_record/tasks/database_tasks.rb +44 -45
- data/lib/active_record/tasks/mysql_database_tasks.rb +4 -56
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +18 -4
- data/lib/active_record/test_fixtures.rb +40 -6
- 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 +58 -32
- 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.rb +72 -7
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +1 -1
- 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/predications.rb +1 -3
- data/lib/arel/select_manager.rb +13 -4
- 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 +7 -23
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +16 -15
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
|
@@ -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,53 +65,66 @@ 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
|
-
|
|
56
|
-
|
|
71
|
+
options = { on_rotation: on_rotation }.compact
|
|
72
|
+
if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose), **options)
|
|
73
|
+
find_by(primary_key => [id])
|
|
57
74
|
end
|
|
58
75
|
end
|
|
59
76
|
|
|
60
|
-
# Works like find_signed, but will raise an
|
|
77
|
+
# Works like find_signed, but will raise an ActiveSupport::MessageVerifier::InvalidSignature
|
|
61
78
|
# exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
|
|
62
|
-
# or has been tampered with. It will also raise an
|
|
79
|
+
# or has been tampered with. It will also raise an ActiveRecord::RecordNotFound exception if
|
|
63
80
|
# the valid signed id can't find a record.
|
|
64
81
|
#
|
|
65
|
-
#
|
|
82
|
+
# ==== Examples
|
|
66
83
|
#
|
|
67
84
|
# User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature
|
|
68
85
|
#
|
|
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 the
|
|
80
|
-
# Rails.application.key_generator. By default, it's SHA256 for the digest and JSON for the serialization.
|
|
81
96
|
def signed_id_verifier
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
85
105
|
|
|
86
|
-
if secret.nil?
|
|
87
|
-
raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
|
|
88
|
-
else
|
|
89
106
|
ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON, url_safe: true
|
|
90
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"]
|
|
91
116
|
end
|
|
92
117
|
end
|
|
93
118
|
|
|
94
119
|
# Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different
|
|
95
120
|
# verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
|
|
96
|
-
# your custom verifier for that in advance. See
|
|
121
|
+
# your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details.
|
|
97
122
|
def signed_id_verifier=(verifier)
|
|
98
|
-
|
|
123
|
+
if signed_id_verifier_secret
|
|
124
|
+
@signed_id_verifier = verifier
|
|
125
|
+
else
|
|
126
|
+
self._signed_id_verifier = verifier
|
|
127
|
+
end
|
|
99
128
|
end
|
|
100
129
|
|
|
101
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)
|
|
@@ -74,13 +78,13 @@ module ActiveRecord
|
|
|
74
78
|
self
|
|
75
79
|
end
|
|
76
80
|
|
|
77
|
-
def add_bind(obj)
|
|
81
|
+
def add_bind(obj, &)
|
|
78
82
|
@binds << obj
|
|
79
83
|
@parts << Substitute.new
|
|
80
84
|
self
|
|
81
85
|
end
|
|
82
86
|
|
|
83
|
-
def add_binds(binds, proc_for_binds = nil)
|
|
87
|
+
def add_binds(binds, proc_for_binds = nil, &)
|
|
84
88
|
@binds.concat proc_for_binds ? binds.map(&proc_for_binds) : binds
|
|
85
89
|
binds.size.times do |i|
|
|
86
90
|
@parts << ", " unless i == 0
|
|
@@ -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,17 +146,17 @@ 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
|
-
async ? Promise.
|
|
159
|
+
async ? Promise::Complete.new([]) : []
|
|
156
160
|
end
|
|
157
161
|
|
|
158
162
|
def self.unsupported_value?(value)
|
data/lib/active_record/store.rb
CHANGED
|
@@ -146,37 +146,45 @@ 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
|
+
prev_value, new_value = accessor.get(prev_store, key), accessor.get(new_store, key)
|
|
158
|
+
[prev_value, new_value] unless prev_value == new_value
|
|
156
159
|
end
|
|
157
160
|
|
|
158
161
|
define_method("#{accessor_key}_was") do
|
|
159
162
|
return unless attribute_changed?(store_attribute)
|
|
160
163
|
prev_store, _new_store = changes[store_attribute]
|
|
161
|
-
|
|
164
|
+
accessor = store_accessor_for(store_attribute)
|
|
165
|
+
accessor.get(prev_store, key)
|
|
162
166
|
end
|
|
163
167
|
|
|
164
168
|
define_method("saved_change_to_#{accessor_key}?") do
|
|
165
169
|
return false unless saved_change_to_attribute?(store_attribute)
|
|
166
170
|
prev_store, new_store = saved_changes[store_attribute]
|
|
167
|
-
|
|
171
|
+
accessor = store_accessor_for(store_attribute)
|
|
172
|
+
accessor.get(prev_store, key) != accessor.get(new_store, key)
|
|
168
173
|
end
|
|
169
174
|
|
|
170
175
|
define_method("saved_change_to_#{accessor_key}") do
|
|
171
176
|
return unless saved_change_to_attribute?(store_attribute)
|
|
172
177
|
prev_store, new_store = saved_changes[store_attribute]
|
|
173
|
-
|
|
178
|
+
accessor = store_accessor_for(store_attribute)
|
|
179
|
+
prev_value, new_value = accessor.get(prev_store, key), accessor.get(new_store, key)
|
|
180
|
+
[prev_value, new_value] unless prev_value == new_value
|
|
174
181
|
end
|
|
175
182
|
|
|
176
183
|
define_method("#{accessor_key}_before_last_save") do
|
|
177
184
|
return unless saved_change_to_attribute?(store_attribute)
|
|
178
185
|
prev_store, _new_store = saved_changes[store_attribute]
|
|
179
|
-
|
|
186
|
+
accessor = store_accessor_for(store_attribute)
|
|
187
|
+
accessor.get(prev_store, key)
|
|
180
188
|
end
|
|
181
189
|
end
|
|
182
190
|
end
|
|
@@ -225,39 +233,58 @@ module ActiveRecord
|
|
|
225
233
|
end
|
|
226
234
|
|
|
227
235
|
class HashAccessor # :nodoc:
|
|
236
|
+
def self.get(store_object, key)
|
|
237
|
+
if store_object
|
|
238
|
+
store_object[key]
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
228
242
|
def self.read(object, attribute, key)
|
|
229
|
-
prepare(object, attribute)
|
|
230
|
-
|
|
243
|
+
store_object = prepare(object, attribute)
|
|
244
|
+
store_object[key]
|
|
231
245
|
end
|
|
232
246
|
|
|
233
247
|
def self.write(object, attribute, key, value)
|
|
234
|
-
prepare(object, attribute)
|
|
235
|
-
|
|
248
|
+
store_object = prepare(object, attribute)
|
|
249
|
+
store_object[key] = value if value != store_object[key]
|
|
236
250
|
end
|
|
237
251
|
|
|
238
252
|
def self.prepare(object, attribute)
|
|
239
|
-
|
|
253
|
+
store_object = object.public_send(attribute)
|
|
254
|
+
|
|
255
|
+
if store_object.nil?
|
|
256
|
+
store_object = {}
|
|
257
|
+
object.public_send(:"#{attribute}=", store_object)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
store_object
|
|
240
261
|
end
|
|
241
262
|
end
|
|
242
263
|
|
|
243
264
|
class StringKeyedHashAccessor < HashAccessor # :nodoc:
|
|
265
|
+
def self.get(store_object, key)
|
|
266
|
+
super store_object, Symbol === key ? key.name : key.to_s
|
|
267
|
+
end
|
|
268
|
+
|
|
244
269
|
def self.read(object, attribute, key)
|
|
245
|
-
super object, attribute, key.to_s
|
|
270
|
+
super object, attribute, Symbol === key ? key.name : key.to_s
|
|
246
271
|
end
|
|
247
272
|
|
|
248
273
|
def self.write(object, attribute, key, value)
|
|
249
|
-
super object, attribute, key.to_s, value
|
|
274
|
+
super object, attribute, Symbol === key ? key.name : key.to_s, value
|
|
250
275
|
end
|
|
251
276
|
end
|
|
252
277
|
|
|
253
278
|
class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
|
|
254
|
-
def self.prepare(object,
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
279
|
+
def self.prepare(object, attribute)
|
|
280
|
+
store_object = object.public_send(attribute)
|
|
281
|
+
|
|
282
|
+
unless store_object.is_a?(ActiveSupport::HashWithIndifferentAccess)
|
|
283
|
+
store_object = IndifferentCoder.as_indifferent_hash(store_object)
|
|
284
|
+
object.public_send :"#{attribute}=", store_object
|
|
259
285
|
end
|
|
260
|
-
|
|
286
|
+
|
|
287
|
+
store_object
|
|
261
288
|
end
|
|
262
289
|
end
|
|
263
290
|
|
|
@@ -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.polymorphic? ? nil : 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
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Tasks # :nodoc:
|
|
5
|
+
class AbstractTasks # :nodoc:
|
|
6
|
+
def self.using_database_configurations?
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(db_config)
|
|
11
|
+
@db_config = db_config
|
|
12
|
+
@configuration_hash = db_config.configuration_hash
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def charset
|
|
16
|
+
connection.encoding
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def collation
|
|
20
|
+
connection.collation
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def check_current_protected_environment!(db_config, migration_class)
|
|
24
|
+
with_temporary_pool(db_config, migration_class) do |pool|
|
|
25
|
+
migration_context = pool.migration_context
|
|
26
|
+
current = migration_context.current_environment
|
|
27
|
+
stored = migration_context.last_stored_environment
|
|
28
|
+
|
|
29
|
+
if migration_context.protected_environment?
|
|
30
|
+
raise ActiveRecord::ProtectedEnvironmentError.new(stored)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if stored && stored != current
|
|
34
|
+
raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
|
|
35
|
+
end
|
|
36
|
+
rescue ActiveRecord::NoDatabaseError
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
attr_reader :db_config, :configuration_hash
|
|
42
|
+
|
|
43
|
+
def connection
|
|
44
|
+
ActiveRecord::Base.lease_connection
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def establish_connection(config = db_config)
|
|
48
|
+
ActiveRecord::Base.establish_connection(config)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def configuration_hash_without_database
|
|
52
|
+
configuration_hash.merge(database: nil)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def run_cmd(cmd, *args, **opts)
|
|
56
|
+
fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, opts)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def run_cmd_error(cmd, args)
|
|
60
|
+
msg = +"failed to execute:\n"
|
|
61
|
+
msg << "#{cmd} #{filter_sensitive_args(args).join(' ')}\n\n"
|
|
62
|
+
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
|
|
63
|
+
msg
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The failed command is echoed back, so anything secret it was given has
|
|
67
|
+
# to be kept out of the message.
|
|
68
|
+
def filter_sensitive_args(args)
|
|
69
|
+
filtered = "--password=#{ActiveSupport::ParameterFilter::FILTERED}"
|
|
70
|
+
args.map { |arg| arg.to_s.sub(/\A--password=.+/m, filtered) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def with_temporary_pool(db_config, migration_class, clobber: false)
|
|
74
|
+
original_db_config = migration_class.connection_db_config
|
|
75
|
+
pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
|
|
76
|
+
|
|
77
|
+
yield pool
|
|
78
|
+
ensure
|
|
79
|
+
migration_class.connection_handler.establish_connection(original_db_config, clobber: clobber)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|