activerecord 8.0.0 → 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 +703 -248
- 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 +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- 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_association.rb +3 -3
- 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/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +4 -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.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 +17 -4
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attribute_methods.rb +23 -18
- 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/coders/json.rb +14 -5
- 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 +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -108
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
- 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 +89 -23
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -64
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +57 -20
- 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 -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 +33 -4
- 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 +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +26 -17
- 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 +22 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +67 -31
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +82 -49
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +38 -10
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -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 +14 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -12
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +65 -36
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -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 +34 -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 +59 -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/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
- 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 +73 -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 +3 -3
- data/lib/active_record/gem_version.rb +2 -2
- 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 +19 -3
- 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 +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 +7 -7
- data/lib/active_record/querying.rb +4 -4
- 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 +35 -0
- data/lib/active_record/relation/batches.rb +25 -11
- data/lib/active_record/relation/calculations.rb +54 -38
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +42 -25
- 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 +9 -7
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +43 -32
- 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 +43 -19
- 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_dumper.rb +42 -22
- 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 +15 -11
- 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 +44 -45
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- 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 +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 +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.rb +71 -6
- 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/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 +6 -22
- 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
|
-
|
|
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
|
|
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,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.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,76 @@
|
|
|
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} #{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
|
+
def with_temporary_pool(db_config, migration_class, clobber: false)
|
|
67
|
+
original_db_config = migration_class.connection_db_config
|
|
68
|
+
pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
|
|
69
|
+
|
|
70
|
+
yield pool
|
|
71
|
+
ensure
|
|
72
|
+
migration_class.connection_handler.establish_connection(original_db_config, clobber: clobber)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|