activerecord 7.1.4 → 7.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +515 -2387
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +14 -7
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +6 -4
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +1 -1
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +33 -16
- data/lib/active_record/attribute_assignment.rb +1 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +1 -1
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +4 -16
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +60 -71
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +13 -32
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +52 -36
- data/lib/active_record/counter_cache.rb +18 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
- data/lib/active_record/database_configurations/database_config.rb +15 -4
- data/lib/active_record/database_configurations/hash_config.rb +38 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +24 -0
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +2 -2
- data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
- data/lib/active_record/encryption/encryptor.rb +17 -2
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/enum.rb +11 -2
- data/lib/active_record/errors.rb +16 -11
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +8 -4
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +5 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +85 -76
- data/lib/active_record/model_schema.rb +28 -67
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +48 -57
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +41 -44
- data/lib/active_record/reflection.rb +90 -35
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +94 -61
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -57
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +496 -72
- data/lib/active_record/result.rb +31 -44
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/signed_id.rb +11 -1
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +76 -59
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +81 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +1 -1
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +68 -0
- data/lib/active_record/transactions.rb +43 -14
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +14 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +149 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- metadata +17 -12
@@ -1,53 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
class DatabaseConfigurations
|
5
|
-
#
|
7
|
+
# # Active Record Database Hash Config
|
6
8
|
#
|
7
|
-
# A
|
8
|
-
#
|
9
|
+
# A `HashConfig` object is created for each database configuration entry that is
|
10
|
+
# created from a hash.
|
9
11
|
#
|
10
12
|
# A hash config:
|
11
13
|
#
|
12
|
-
#
|
14
|
+
# { "development" => { "database" => "db_name" } }
|
13
15
|
#
|
14
16
|
# Becomes:
|
15
17
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
+
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
|
19
|
+
# @env_name="development", @name="primary", @config={database: "db_name"}>
|
18
20
|
#
|
19
21
|
# See ActiveRecord::DatabaseConfigurations for more info.
|
20
22
|
class HashConfig < DatabaseConfig
|
21
23
|
attr_reader :configuration_hash
|
22
24
|
|
23
|
-
|
24
|
-
#
|
25
|
+
# Initialize a new `HashConfig` object
|
26
|
+
#
|
27
|
+
# #### Parameters
|
25
28
|
#
|
26
|
-
#
|
29
|
+
# * `env_name` - The Rails environment, i.e. "development".
|
30
|
+
# * `name` - The db config name. In a standard two-tier database configuration
|
31
|
+
# this will default to "primary". In a multiple database three-tier database
|
32
|
+
# configuration this corresponds to the name used in the second tier, for
|
33
|
+
# example "primary_readonly".
|
34
|
+
# * `configuration_hash` - The config hash. This is the hash that contains the
|
35
|
+
# database adapter, name, and other important information for database
|
36
|
+
# connections.
|
27
37
|
#
|
28
|
-
# * <tt>:env_name</tt> - The \Rails environment, i.e. "development".
|
29
|
-
# * <tt>:name</tt> - The db config name. In a standard two-tier
|
30
|
-
# database configuration this will default to "primary". In a multiple
|
31
|
-
# database three-tier database configuration this corresponds to the name
|
32
|
-
# used in the second tier, for example "primary_readonly".
|
33
|
-
# * <tt>:config</tt> - The config hash. This is the hash that contains the
|
34
|
-
# database adapter, name, and other important information for database
|
35
|
-
# connections.
|
36
38
|
def initialize(env_name, name, configuration_hash)
|
37
39
|
super(env_name, name)
|
38
40
|
@configuration_hash = configuration_hash.symbolize_keys.freeze
|
39
41
|
end
|
40
42
|
|
41
43
|
# Determines whether a database configuration is for a replica / readonly
|
42
|
-
# connection. If the
|
43
|
-
# return
|
44
|
+
# connection. If the `replica` key is present in the config, `replica?` will
|
45
|
+
# return `true`.
|
44
46
|
def replica?
|
45
47
|
configuration_hash[:replica]
|
46
48
|
end
|
47
49
|
|
48
|
-
# The migrations paths for a database configuration. If the
|
49
|
-
#
|
50
|
-
# will return its value.
|
50
|
+
# The migrations paths for a database configuration. If the `migrations_paths`
|
51
|
+
# key is present in the config, `migrations_paths` will return its value.
|
51
52
|
def migrations_paths
|
52
53
|
configuration_hash[:migrations_paths]
|
53
54
|
end
|
@@ -92,8 +93,8 @@ module ActiveRecord
|
|
92
93
|
(configuration_hash[:checkout_timeout] || 5).to_f
|
93
94
|
end
|
94
95
|
|
95
|
-
#
|
96
|
-
# also be useful if someone wants a very low
|
96
|
+
# `reaping_frequency` is configurable mostly for historical reasons, but it
|
97
|
+
# could also be useful if someone wants a very low `idle_timeout`.
|
97
98
|
def reaping_frequency
|
98
99
|
configuration_hash.fetch(:reaping_frequency, 60)&.to_f
|
99
100
|
end
|
@@ -104,12 +105,11 @@ module ActiveRecord
|
|
104
105
|
end
|
105
106
|
|
106
107
|
def adapter
|
107
|
-
configuration_hash[:adapter]
|
108
|
+
configuration_hash[:adapter]&.to_s
|
108
109
|
end
|
109
110
|
|
110
|
-
# The path to the schema cache dump file for a database.
|
111
|
-
#
|
112
|
-
# default will be derived.
|
111
|
+
# The path to the schema cache dump file for a database. If omitted, the
|
112
|
+
# filename will be read from ENV or a default will be derived.
|
113
113
|
def schema_cache_path
|
114
114
|
configuration_hash[:schema_cache_path]
|
115
115
|
end
|
@@ -130,14 +130,14 @@ module ActiveRecord
|
|
130
130
|
Base.configurations.primary?(name)
|
131
131
|
end
|
132
132
|
|
133
|
-
# Determines whether to dump the schema/structure files and the
|
134
|
-
#
|
133
|
+
# Determines whether to dump the schema/structure files and the filename that
|
134
|
+
# should be used.
|
135
135
|
#
|
136
|
-
# If
|
137
|
-
#
|
136
|
+
# If `configuration_hash[:schema_dump]` is set to `false` or `nil` the schema
|
137
|
+
# will not be dumped.
|
138
138
|
#
|
139
|
-
# If the config option is set that will be used. Otherwise
|
140
|
-
#
|
139
|
+
# If the config option is set that will be used. Otherwise Rails will generate
|
140
|
+
# the filename from the database config name.
|
141
141
|
def schema_dump(format = ActiveRecord.schema_format)
|
142
142
|
if configuration_hash.key?(:schema_dump)
|
143
143
|
if config = configuration_hash[:schema_dump]
|
@@ -154,6 +154,10 @@ module ActiveRecord
|
|
154
154
|
!replica? && !!configuration_hash.fetch(:database_tasks, true)
|
155
155
|
end
|
156
156
|
|
157
|
+
def use_metadata_table? # :nodoc:
|
158
|
+
configuration_hash.fetch(:use_metadata_table, true)
|
159
|
+
end
|
160
|
+
|
157
161
|
private
|
158
162
|
def schema_file_type(format)
|
159
163
|
case format
|
@@ -41,10 +41,29 @@ module ActiveRecord
|
|
41
41
|
super(env_name, name, configuration_hash)
|
42
42
|
|
43
43
|
@url = url
|
44
|
-
@configuration_hash = @configuration_hash.merge(build_url_hash)
|
44
|
+
@configuration_hash = @configuration_hash.merge(build_url_hash)
|
45
|
+
|
46
|
+
if @configuration_hash[:schema_dump] == "false"
|
47
|
+
@configuration_hash[:schema_dump] = false
|
48
|
+
end
|
49
|
+
|
50
|
+
if @configuration_hash[:query_cache] == "false"
|
51
|
+
@configuration_hash[:query_cache] = false
|
52
|
+
end
|
53
|
+
|
54
|
+
to_boolean!(@configuration_hash, :replica)
|
55
|
+
to_boolean!(@configuration_hash, :database_tasks)
|
56
|
+
|
57
|
+
@configuration_hash.freeze
|
45
58
|
end
|
46
59
|
|
47
60
|
private
|
61
|
+
def to_boolean!(configuration_hash, key)
|
62
|
+
if configuration_hash[key].is_a?(String)
|
63
|
+
configuration_hash[key] = configuration_hash[key] != "false"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
48
67
|
# Return a Hash that can be merged into the main config that represents
|
49
68
|
# the passed in url
|
50
69
|
def build_url_hash
|
@@ -113,6 +113,26 @@ module ActiveRecord
|
|
113
113
|
# end
|
114
114
|
# end
|
115
115
|
#
|
116
|
+
# == Querying across records
|
117
|
+
#
|
118
|
+
# A consequence of delegated types is that querying attributes spread across multiple classes becomes slightly more
|
119
|
+
# tricky, but not impossible.
|
120
|
+
#
|
121
|
+
# The simplest method is to join the "superclass" to the "subclass" and apply the query parameters (i.e. <tt>#where</tt>)
|
122
|
+
# in appropriate places:
|
123
|
+
#
|
124
|
+
# Comment.joins(:entry).where(comments: { content: 'Hello!' }, entry: { creator: Current.user } )
|
125
|
+
#
|
126
|
+
# For convenience, add a scope on the concern. Now all classes that implement the concern will automatically include
|
127
|
+
# the method:
|
128
|
+
#
|
129
|
+
# # app/models/concerns/entryable.rb
|
130
|
+
# scope :with_entry, ->(attrs) { joins(:entry).where(entry: attrs) }
|
131
|
+
#
|
132
|
+
# Now the query can be shortened significantly:
|
133
|
+
#
|
134
|
+
# Comment.where(content: 'Hello!').with_entry(creator: Current.user)
|
135
|
+
#
|
116
136
|
# == Adding further delegation
|
117
137
|
#
|
118
138
|
# The delegated type shouldn't just answer the question of what the underlying class is called. In fact, that's
|
@@ -219,6 +239,10 @@ module ActiveRecord
|
|
219
239
|
role_type = options[:foreign_type] || "#{role}_type"
|
220
240
|
role_id = options[:foreign_key] || "#{role}_id"
|
221
241
|
|
242
|
+
define_singleton_method "#{role}_types" do
|
243
|
+
types.map(&:to_s)
|
244
|
+
end
|
245
|
+
|
222
246
|
define_method "#{role}_class" do
|
223
247
|
public_send(role_type).constantize
|
224
248
|
end
|
@@ -12,12 +12,12 @@ module ActiveRecord
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
def method_missing(name,
|
15
|
+
def method_missing(name, ...)
|
16
16
|
match = Method.match(self, name)
|
17
17
|
|
18
18
|
if match && match.valid?
|
19
19
|
match.define
|
20
|
-
send(name,
|
20
|
+
send(name, ...)
|
21
21
|
else
|
22
22
|
super
|
23
23
|
end
|
@@ -84,7 +84,7 @@ module ActiveRecord
|
|
84
84
|
def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
|
85
85
|
encrypted_attributes << name.to_sym
|
86
86
|
|
87
|
-
|
87
|
+
decorate_attributes([name]) do |name, cast_type|
|
88
88
|
scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, \
|
89
89
|
downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
|
90
90
|
|
@@ -221,7 +221,7 @@ module ActiveRecord
|
|
221
221
|
end
|
222
222
|
|
223
223
|
def cant_modify_encrypted_attributes_when_frozen
|
224
|
-
self.class
|
224
|
+
self.class.encrypted_attributes.each do |attribute|
|
225
225
|
errors.add(attribute.to_sym, "can't be modified because it is encrypted") if changed_attributes.include?(attribute)
|
226
226
|
end
|
227
227
|
end
|
@@ -81,7 +81,7 @@ module ActiveRecord
|
|
81
81
|
@previous_type
|
82
82
|
end
|
83
83
|
|
84
|
-
def
|
84
|
+
def decrypt_as_text(value)
|
85
85
|
with_context do
|
86
86
|
unless value.nil?
|
87
87
|
if @default && @default == value
|
@@ -99,6 +99,10 @@ module ActiveRecord
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
def decrypt(value)
|
103
|
+
text_to_database_type decrypt_as_text(value)
|
104
|
+
end
|
105
|
+
|
102
106
|
def try_to_deserialize_with_previous_encrypted_types(value)
|
103
107
|
previous_types.each.with_index do |type, index|
|
104
108
|
break type.deserialize(value)
|
@@ -129,12 +133,20 @@ module ActiveRecord
|
|
129
133
|
encrypt(casted_value.to_s) unless casted_value.nil?
|
130
134
|
end
|
131
135
|
|
132
|
-
def
|
136
|
+
def encrypt_as_text(value)
|
133
137
|
with_context do
|
138
|
+
if encryptor.binary? && !cast_type.binary?
|
139
|
+
raise Errors::Encoding, "Binary encoded data can only be stored in binary columns"
|
140
|
+
end
|
141
|
+
|
134
142
|
encryptor.encrypt(value, **encryption_options)
|
135
143
|
end
|
136
144
|
end
|
137
145
|
|
146
|
+
def encrypt(value)
|
147
|
+
text_to_database_type encrypt_as_text(value)
|
148
|
+
end
|
149
|
+
|
138
150
|
def encryptor
|
139
151
|
ActiveRecord::Encryption.encryptor
|
140
152
|
end
|
@@ -150,6 +162,14 @@ module ActiveRecord
|
|
150
162
|
def clean_text_scheme
|
151
163
|
@clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new)
|
152
164
|
end
|
165
|
+
|
166
|
+
def text_to_database_type(value)
|
167
|
+
if value && cast_type.binary?
|
168
|
+
ActiveModel::Type::Binary::Data.new(value)
|
169
|
+
else
|
170
|
+
value
|
171
|
+
end
|
172
|
+
end
|
153
173
|
end
|
154
174
|
end
|
155
175
|
end
|
@@ -12,6 +12,14 @@ module ActiveRecord
|
|
12
12
|
# It interacts with a KeyProvider for getting the keys, and delegate to
|
13
13
|
# ActiveRecord::Encryption::Cipher the actual encryption algorithm.
|
14
14
|
class Encryptor
|
15
|
+
# === Options
|
16
|
+
#
|
17
|
+
# * <tt>:compress</tt> - Boolean indicating whether records should be compressed before encryption.
|
18
|
+
# Defaults to +true+.
|
19
|
+
def initialize(compress: true)
|
20
|
+
@compress = compress
|
21
|
+
end
|
22
|
+
|
15
23
|
# Encrypts +clean_text+ and returns the encrypted result
|
16
24
|
#
|
17
25
|
# Internally, it will:
|
@@ -66,6 +74,10 @@ module ActiveRecord
|
|
66
74
|
false
|
67
75
|
end
|
68
76
|
|
77
|
+
def binary?
|
78
|
+
serializer.binary?
|
79
|
+
end
|
80
|
+
|
69
81
|
private
|
70
82
|
DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
|
71
83
|
ENCODING_ERRORS = [EncodingError, Errors::Encoding]
|
@@ -100,7 +112,6 @@ module ActiveRecord
|
|
100
112
|
end
|
101
113
|
|
102
114
|
def deserialize_message(message)
|
103
|
-
raise Errors::Encoding unless message.is_a?(String)
|
104
115
|
serializer.load message
|
105
116
|
rescue ArgumentError, TypeError, Errors::ForbiddenClass
|
106
117
|
raise Errors::Encoding
|
@@ -112,13 +123,17 @@ module ActiveRecord
|
|
112
123
|
|
113
124
|
# Under certain threshold, ZIP compression is actually worse that not compressing
|
114
125
|
def compress_if_worth_it(string)
|
115
|
-
if string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
|
126
|
+
if compress? && string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
|
116
127
|
[compress(string), true]
|
117
128
|
else
|
118
129
|
[string, false]
|
119
130
|
end
|
120
131
|
end
|
121
132
|
|
133
|
+
def compress?
|
134
|
+
@compress
|
135
|
+
end
|
136
|
+
|
122
137
|
def compress(data)
|
123
138
|
Zlib::Deflate.deflate(data).tap do |compressed_data|
|
124
139
|
compressed_data.force_encoding(data.encoding)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/message_pack"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Encryption
|
7
|
+
# A message serializer that serializes +Messages+ with MessagePack.
|
8
|
+
#
|
9
|
+
# The message is converted to a hash with this structure:
|
10
|
+
#
|
11
|
+
# {
|
12
|
+
# p: <payload>,
|
13
|
+
# h: {
|
14
|
+
# header1: value1,
|
15
|
+
# header2: value2,
|
16
|
+
# ...
|
17
|
+
# }
|
18
|
+
# }
|
19
|
+
#
|
20
|
+
# Then it is converted to the MessagePack format.
|
21
|
+
class MessagePackMessageSerializer
|
22
|
+
def dump(message)
|
23
|
+
raise Errors::ForbiddenClass unless message.is_a?(Message)
|
24
|
+
ActiveSupport::MessagePack.dump(message_to_hash(message))
|
25
|
+
end
|
26
|
+
|
27
|
+
def load(serialized_content)
|
28
|
+
data = ActiveSupport::MessagePack.load(serialized_content)
|
29
|
+
hash_to_message(data, 1)
|
30
|
+
rescue RuntimeError
|
31
|
+
raise Errors::Decryption
|
32
|
+
end
|
33
|
+
|
34
|
+
def binary?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def message_to_hash(message)
|
40
|
+
{
|
41
|
+
"p" => message.payload,
|
42
|
+
"h" => headers_to_hash(message.headers)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def headers_to_hash(headers)
|
47
|
+
headers.transform_values do |value|
|
48
|
+
value.is_a?(Message) ? message_to_hash(value) : value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def hash_to_message(data, level)
|
53
|
+
validate_message_data_format(data, level)
|
54
|
+
Message.new(payload: data["p"], headers: parse_properties(data["h"], level))
|
55
|
+
end
|
56
|
+
|
57
|
+
def validate_message_data_format(data, level)
|
58
|
+
if level > 2
|
59
|
+
raise Errors::Decryption, "More than one level of hash nesting in headers is not supported"
|
60
|
+
end
|
61
|
+
|
62
|
+
unless data.is_a?(Hash) && data.has_key?("p")
|
63
|
+
raise Errors::Decryption, "Invalid data format: hash without payload"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_properties(headers, level)
|
68
|
+
Properties.new.tap do |properties|
|
69
|
+
headers&.each do |key, value|
|
70
|
+
properties[key] = value.is_a?(Hash) ? hash_to_message(value, level + 1) : value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/active_record/enum.rb
CHANGED
@@ -223,6 +223,13 @@ module ActiveRecord
|
|
223
223
|
options.transform_keys! { |key| :"#{key[1..-1]}" }
|
224
224
|
|
225
225
|
definitions.each { |name, values| _enum(name, values, **options) }
|
226
|
+
|
227
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
228
|
+
Defining enums with keyword arguments is deprecated and will be removed
|
229
|
+
in Rails 7.3. Positional arguments should be used instead:
|
230
|
+
|
231
|
+
#{definitions.map { |name, values| "enum :#{name}, #{values}" }.join("\n")}
|
232
|
+
MSG
|
226
233
|
end
|
227
234
|
|
228
235
|
private
|
@@ -245,9 +252,11 @@ module ActiveRecord
|
|
245
252
|
detect_enum_conflict!(name, name)
|
246
253
|
detect_enum_conflict!(name, "#{name}=")
|
247
254
|
|
248
|
-
attribute(name, **options)
|
255
|
+
attribute(name, **options)
|
256
|
+
|
257
|
+
decorate_attributes([name]) do |_name, subtype|
|
249
258
|
if subtype == ActiveModel::Type.default_value
|
250
|
-
raise "Undeclared attribute type for enum '#{name}'. Enums must be" \
|
259
|
+
raise "Undeclared attribute type for enum '#{name}' in #{self.name}. Enums must be" \
|
251
260
|
" backed by a database column or declared with an explicit type" \
|
252
261
|
" via `attribute`."
|
253
262
|
end
|
data/lib/active_record/errors.rb
CHANGED
@@ -7,14 +7,6 @@ module ActiveRecord
|
|
7
7
|
class ActiveRecordError < StandardError
|
8
8
|
end
|
9
9
|
|
10
|
-
# DEPRECATED: Previously raised when trying to use a feature in Active Record which
|
11
|
-
# requires Active Job but the gem is not present. Now raises a NameError.
|
12
|
-
include ActiveSupport::Deprecation::DeprecatedConstantAccessor
|
13
|
-
DeprecatedActiveJobRequiredError = Class.new(ActiveRecordError) # :nodoc:
|
14
|
-
deprecate_constant "ActiveJobRequiredError", "ActiveRecord::DeprecatedActiveJobRequiredError",
|
15
|
-
message: "ActiveRecord::ActiveJobRequiredError has been deprecated. If Active Job is not present, a NameError will be raised instead.",
|
16
|
-
deprecator: ActiveRecord.deprecator
|
17
|
-
|
18
10
|
# Raised when the single-table inheritance mechanism fails to locate the subclass
|
19
11
|
# (for example due to improper usage of column that
|
20
12
|
# {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column]
|
@@ -66,7 +58,7 @@ module ActiveRecord
|
|
66
58
|
end
|
67
59
|
|
68
60
|
# Raised when connection to the database could not been established (for example when
|
69
|
-
# {ActiveRecord::Base.
|
61
|
+
# {ActiveRecord::Base.lease_connection=}[rdoc-ref:ConnectionHandling#lease_connection]
|
70
62
|
# is given a +nil+ object).
|
71
63
|
class ConnectionNotEstablished < AdapterError
|
72
64
|
def initialize(message = nil, connection_pool: nil)
|
@@ -137,8 +129,10 @@ module ActiveRecord
|
|
137
129
|
end
|
138
130
|
|
139
131
|
# Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
|
140
|
-
# {ActiveRecord::Base.
|
141
|
-
# methods when a record is
|
132
|
+
# {ActiveRecord::Base.update_attribute!}[rdoc-ref:Persistence#update_attribute!]
|
133
|
+
# methods when a record is failed to validate or cannot be saved due to any of the
|
134
|
+
# <tt>before_*</tt> callbacks throwing +:abort+. See
|
135
|
+
# ActiveRecord::Callbacks for further details
|
142
136
|
class RecordNotSaved < ActiveRecordError
|
143
137
|
attr_reader :record
|
144
138
|
|
@@ -374,6 +368,12 @@ module ActiveRecord
|
|
374
368
|
end
|
375
369
|
|
376
370
|
# Raised on attempt to lazily load records that are marked as strict loading.
|
371
|
+
#
|
372
|
+
# You can resolve this error by eager loading marked records before accessing
|
373
|
+
# them. The
|
374
|
+
# {Eager Loading Associations}[https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations]
|
375
|
+
# guide covers solutions, such as using
|
376
|
+
# {ActiveRecord::Base.includes}[rdoc-ref:QueryMethods#includes].
|
377
377
|
class StrictLoadingViolationError < ActiveRecordError
|
378
378
|
end
|
379
379
|
|
@@ -577,4 +577,9 @@ module ActiveRecord
|
|
577
577
|
# values, such as request parameters or model attributes to query methods.
|
578
578
|
class UnknownAttributeReference < ActiveRecordError
|
579
579
|
end
|
580
|
+
|
581
|
+
# DatabaseVersionError will be raised when the database version is not supported, or when
|
582
|
+
# the database version cannot be determined.
|
583
|
+
class DatabaseVersionError < ActiveRecordError
|
584
|
+
end
|
580
585
|
end
|
@@ -17,16 +17,17 @@ module ActiveRecord
|
|
17
17
|
# Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
|
18
18
|
# Returns a formatted string ready to be logged.
|
19
19
|
def exec_explain(queries, options = []) # :nodoc:
|
20
|
-
str =
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
20
|
+
str = with_connection do |c|
|
21
|
+
queries.map do |sql, binds|
|
22
|
+
msg = +"#{build_explain_clause(c, options)} #{sql}"
|
23
|
+
unless binds.empty?
|
24
|
+
msg << " "
|
25
|
+
msg << binds.map { |attr| render_bind(c, attr) }.inspect
|
26
|
+
end
|
27
|
+
msg << "\n"
|
28
|
+
msg << c.explain(sql, binds, options)
|
29
|
+
end.join("\n")
|
30
|
+
end
|
30
31
|
# Overriding inspect to be more human readable, especially in the console.
|
31
32
|
def str.inspect
|
32
33
|
self
|
@@ -36,7 +37,7 @@ module ActiveRecord
|
|
36
37
|
end
|
37
38
|
|
38
39
|
private
|
39
|
-
def render_bind(attr)
|
40
|
+
def render_bind(connection, attr)
|
40
41
|
if ActiveModel::Attribute === attr
|
41
42
|
value = if attr.type.binary? && attr.value
|
42
43
|
"<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
|
@@ -51,24 +52,12 @@ module ActiveRecord
|
|
51
52
|
[attr&.name, value]
|
52
53
|
end
|
53
54
|
|
54
|
-
def build_explain_clause(options = [])
|
55
|
+
def build_explain_clause(connection, options = [])
|
55
56
|
if connection.respond_to?(:build_explain_clause, true)
|
56
57
|
connection.build_explain_clause(options)
|
57
58
|
else
|
58
59
|
"EXPLAIN for:"
|
59
60
|
end
|
60
61
|
end
|
61
|
-
|
62
|
-
def connection_explain(sql, binds, options)
|
63
|
-
if connection.method(:explain).parameters.size == 2
|
64
|
-
ActiveRecord.deprecator.warn(<<~MSG.squish)
|
65
|
-
The current database adapter, #{connection.adapter_name}, does not support explain options.
|
66
|
-
To remove this warning, the adapter must implement `build_explain_clause(options = [])`.
|
67
|
-
MSG
|
68
|
-
connection.explain(sql, binds)
|
69
|
-
else
|
70
|
-
connection.explain(sql, binds, options)
|
71
|
-
end
|
72
|
-
end
|
73
62
|
end
|
74
63
|
end
|