activerecord 7.0.0 → 7.0.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 +357 -0
- data/MIT-LICENSE +1 -1
- data/lib/active_record/associations/collection_association.rb +1 -2
- data/lib/active_record/associations/collection_proxy.rb +2 -2
- data/lib/active_record/associations/has_many_association.rb +7 -4
- data/lib/active_record/associations/join_dependency.rb +17 -13
- data/lib/active_record/associations.rb +38 -17
- data/lib/active_record/attribute_methods/serialization.rb +34 -50
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
- data/lib/active_record/attribute_methods.rb +2 -2
- data/lib/active_record/autosave_association.rb +2 -2
- data/lib/active_record/base.rb +3 -3
- data/lib/active_record/coders/yaml_column.rb +10 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +12 -7
- data/lib/active_record/connection_adapters/abstract_adapter.rb +5 -5
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +10 -2
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +3 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +10 -3
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +6 -5
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +14 -14
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +35 -2
- data/lib/active_record/connection_handling.rb +2 -2
- data/lib/active_record/core.rb +3 -3
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/encryption/configurable.rb +9 -3
- data/lib/active_record/encryption/contexts.rb +3 -3
- data/lib/active_record/encryption/derived_secret_key_provider.rb +1 -1
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +2 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
- data/lib/active_record/encryption/encryptor.rb +7 -7
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +28 -28
- data/lib/active_record/encryption/message.rb +1 -1
- data/lib/active_record/encryption/properties.rb +1 -1
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +1 -1
- data/lib/active_record/fixtures.rb +5 -5
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/integration.rb +2 -2
- data/lib/active_record/locking/pessimistic.rb +3 -3
- data/lib/active_record/log_subscriber.rb +10 -5
- data/lib/active_record/middleware/database_selector.rb +13 -6
- data/lib/active_record/middleware/shard_selector.rb +4 -4
- data/lib/active_record/migration/command_recorder.rb +3 -3
- data/lib/active_record/migration/compatibility.rb +10 -7
- data/lib/active_record/migration.rb +6 -5
- data/lib/active_record/model_schema.rb +22 -10
- data/lib/active_record/persistence.rb +9 -8
- data/lib/active_record/querying.rb +1 -1
- data/lib/active_record/railtie.rb +22 -18
- data/lib/active_record/railties/databases.rake +16 -11
- data/lib/active_record/reflection.rb +7 -1
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +3 -2
- data/lib/active_record/relation/delegation.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +46 -11
- data/lib/active_record/relation.rb +22 -6
- data/lib/active_record/sanitization.rb +6 -5
- data/lib/active_record/schema.rb +38 -23
- data/lib/active_record/schema_dumper.rb +15 -16
- data/lib/active_record/scoping/default.rb +5 -7
- data/lib/active_record/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +2 -2
- data/lib/active_record/store.rb +7 -2
- data/lib/active_record/tasks/database_tasks.rb +32 -23
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -2
- data/lib/active_record/test_fixtures.rb +12 -5
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/validations/associated.rb +3 -3
- data/lib/active_record/validations/presence.rb +2 -2
- data/lib/active_record/validations/uniqueness.rb +3 -3
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +15 -1
- metadata +13 -13
|
@@ -75,7 +75,7 @@ module ActiveRecord
|
|
|
75
75
|
|
|
76
76
|
class << self
|
|
77
77
|
def new_client(conn_params)
|
|
78
|
-
PG.connect(conn_params)
|
|
78
|
+
PG.connect(**conn_params)
|
|
79
79
|
rescue ::PG::Error => error
|
|
80
80
|
if conn_params && conn_params[:dbname] && error.message.include?(conn_params[:dbname])
|
|
81
81
|
raise ActiveRecord::NoDatabaseError.db_error(conn_params[:dbname])
|
|
@@ -104,7 +104,7 @@ module ActiveRecord
|
|
|
104
104
|
|
|
105
105
|
##
|
|
106
106
|
# :singleton-method:
|
|
107
|
-
# PostgreSQL supports multiple types for DateTimes. By default if you use
|
|
107
|
+
# PostgreSQL supports multiple types for DateTimes. By default, if you use +datetime+
|
|
108
108
|
# in migrations, Rails will translate this to a PostgreSQL "timestamp without time zone".
|
|
109
109
|
# Change this in an initializer to use another NATIVE_DATABASE_TYPES. For example, to
|
|
110
110
|
# store DateTimes as "timestamp with time zone":
|
|
@@ -116,8 +116,8 @@ module ActiveRecord
|
|
|
116
116
|
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:my_custom_type] = { name: "my_custom_type_name" }
|
|
117
117
|
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :my_custom_type
|
|
118
118
|
#
|
|
119
|
-
# If you're using
|
|
120
|
-
# setting, you should immediately run bin/rails db:migrate to update the types in your schema.rb.
|
|
119
|
+
# If you're using +:ruby+ as your +config.active_record.schema_format+ and you change this
|
|
120
|
+
# setting, you should immediately run <tt>bin/rails db:migrate</tt> to update the types in your schema.rb.
|
|
121
121
|
class_attribute :datetime_type, default: :timestamp
|
|
122
122
|
|
|
123
123
|
NATIVE_DATABASE_TYPES = {
|
|
@@ -281,7 +281,7 @@ module ActiveRecord
|
|
|
281
281
|
def initialize(connection, logger, connection_parameters, config)
|
|
282
282
|
super(connection, logger, config)
|
|
283
283
|
|
|
284
|
-
@connection_parameters = connection_parameters
|
|
284
|
+
@connection_parameters = connection_parameters || {}
|
|
285
285
|
|
|
286
286
|
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
|
|
287
287
|
@local_tz = nil
|
|
@@ -1063,5 +1063,6 @@ module ActiveRecord
|
|
|
1063
1063
|
ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
|
|
1064
1064
|
ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
|
|
1065
1065
|
end
|
|
1066
|
+
ActiveSupport.run_load_hooks(:active_record_postgresqladapter, PostgreSQLAdapter)
|
|
1066
1067
|
end
|
|
1067
1068
|
end
|
|
@@ -45,6 +45,19 @@ module ActiveRecord
|
|
|
45
45
|
0
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
def quote_default_expression(value, column) # :nodoc:
|
|
49
|
+
if value.is_a?(Proc)
|
|
50
|
+
value = value.call
|
|
51
|
+
if value.match?(/\A\w+\(.*\)\z/)
|
|
52
|
+
"(#{value})"
|
|
53
|
+
else
|
|
54
|
+
value
|
|
55
|
+
end
|
|
56
|
+
else
|
|
57
|
+
super
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
48
61
|
def type_cast(value) # :nodoc:
|
|
49
62
|
case value
|
|
50
63
|
when BigDecimal
|
|
@@ -21,7 +21,7 @@ module ActiveRecord
|
|
|
21
21
|
WHERE name = #{quote(row['name'])} AND type = 'index'
|
|
22
22
|
SQL
|
|
23
23
|
|
|
24
|
-
/\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?\z/i =~ index_sql
|
|
24
|
+
/\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?(?:\s*\/\*.*\*\/)?\z/i =~ index_sql
|
|
25
25
|
|
|
26
26
|
columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
|
|
27
27
|
col["name"]
|
|
@@ -60,7 +60,7 @@ module ActiveRecord
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def remove_foreign_key(from_table, to_table = nil, **options)
|
|
63
|
-
return if options
|
|
63
|
+
return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
|
|
64
64
|
|
|
65
65
|
to_table ||= options[:to_table]
|
|
66
66
|
options = options.except(:name, :to_table, :validate)
|
|
@@ -127,20 +127,20 @@ module ActiveRecord
|
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
def new_column_from_field(table_name, field)
|
|
130
|
-
default =
|
|
131
|
-
case field["dflt_value"]
|
|
132
|
-
when /^null$/i
|
|
133
|
-
nil
|
|
134
|
-
when /^'(.*)'$/m
|
|
135
|
-
$1.gsub("''", "'")
|
|
136
|
-
when /^"(.*)"$/m
|
|
137
|
-
$1.gsub('""', '"')
|
|
138
|
-
else
|
|
139
|
-
field["dflt_value"]
|
|
140
|
-
end
|
|
130
|
+
default = field["dflt_value"]
|
|
141
131
|
|
|
142
132
|
type_metadata = fetch_type_metadata(field["type"])
|
|
143
|
-
|
|
133
|
+
default_value = extract_value_from_default(default)
|
|
134
|
+
default_function = extract_default_function(default_value, default)
|
|
135
|
+
|
|
136
|
+
Column.new(
|
|
137
|
+
field["name"],
|
|
138
|
+
default_value,
|
|
139
|
+
type_metadata,
|
|
140
|
+
field["notnull"].to_i == 0,
|
|
141
|
+
default_function,
|
|
142
|
+
collation: field["collation"]
|
|
143
|
+
)
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
def data_source_sql(name = nil, type: nil)
|
|
@@ -341,7 +341,7 @@ module ActiveRecord
|
|
|
341
341
|
end
|
|
342
342
|
|
|
343
343
|
def get_database_version # :nodoc:
|
|
344
|
-
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
|
|
344
|
+
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
|
|
345
345
|
end
|
|
346
346
|
|
|
347
347
|
def check_version # :nodoc:
|
|
@@ -389,6 +389,34 @@ module ActiveRecord
|
|
|
389
389
|
end
|
|
390
390
|
alias column_definitions table_structure
|
|
391
391
|
|
|
392
|
+
def extract_value_from_default(default)
|
|
393
|
+
case default
|
|
394
|
+
when /^null$/i
|
|
395
|
+
nil
|
|
396
|
+
# Quoted types
|
|
397
|
+
when /^'(.*)'$/m
|
|
398
|
+
$1.gsub("''", "'")
|
|
399
|
+
# Quoted types
|
|
400
|
+
when /^"(.*)"$/m
|
|
401
|
+
$1.gsub('""', '"')
|
|
402
|
+
# Numeric types
|
|
403
|
+
when /\A-?\d+(\.\d*)?\z/
|
|
404
|
+
$&
|
|
405
|
+
else
|
|
406
|
+
# Anything else is blank or some function
|
|
407
|
+
# and we can't know the value of that, so return nil.
|
|
408
|
+
nil
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def extract_default_function(default_value, default)
|
|
413
|
+
default if has_default_function?(default_value, default)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def has_default_function?(default_value, default)
|
|
417
|
+
!default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
|
|
418
|
+
end
|
|
419
|
+
|
|
392
420
|
# See: https://www.sqlite.org/lang_altertable.html
|
|
393
421
|
# SQLite has an additional restriction on the ALTER TABLE statement
|
|
394
422
|
def invalid_alter_table_type?(type, options)
|
|
@@ -449,8 +477,13 @@ module ActiveRecord
|
|
|
449
477
|
options[:rename][column.name.to_sym] ||
|
|
450
478
|
column.name) : column.name
|
|
451
479
|
|
|
480
|
+
if column.has_default?
|
|
481
|
+
type = lookup_cast_type_from_column(column)
|
|
482
|
+
default = type.deserialize(column.default)
|
|
483
|
+
end
|
|
484
|
+
|
|
452
485
|
@definition.column(column_name, column.type,
|
|
453
|
-
limit: column.limit, default:
|
|
486
|
+
limit: column.limit, default: default,
|
|
454
487
|
precision: column.precision, scale: column.scale,
|
|
455
488
|
null: column.null, collation: column.collation,
|
|
456
489
|
primary_key: column_name == from_primary_key
|
|
@@ -44,7 +44,7 @@ module ActiveRecord
|
|
|
44
44
|
#
|
|
45
45
|
# ActiveRecord::Base.establish_connection(:production)
|
|
46
46
|
#
|
|
47
|
-
# The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
|
|
47
|
+
# The exceptions AdapterNotSpecified, AdapterNotFound, and +ArgumentError+
|
|
48
48
|
# may be returned on an error.
|
|
49
49
|
def establish_connection(config_or_env = nil)
|
|
50
50
|
config_or_env ||= DEFAULT_ENV.call.to_sym
|
|
@@ -108,7 +108,7 @@ module ActiveRecord
|
|
|
108
108
|
connections
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
-
# Connects to a role (
|
|
111
|
+
# Connects to a role (e.g. writing, reading, or a custom role) and/or
|
|
112
112
|
# shard for the duration of the block. At the end of the block the
|
|
113
113
|
# connection will be returned to the original role / shard.
|
|
114
114
|
#
|
data/lib/active_record/core.rb
CHANGED
|
@@ -60,7 +60,7 @@ module ActiveRecord
|
|
|
60
60
|
##
|
|
61
61
|
# :singleton-method:
|
|
62
62
|
# Force enumeration of all columns in SELECT statements.
|
|
63
|
-
# e.g.
|
|
63
|
+
# e.g. <tt>SELECT first_name, last_name FROM ...</tt> instead of <tt>SELECT * FROM ...</tt>
|
|
64
64
|
# This avoids +PreparedStatementCacheExpired+ errors when a column is added
|
|
65
65
|
# to the database while the app is running.
|
|
66
66
|
class_attribute :enumerate_columns_in_select_statements, instance_accessor: false, default: false
|
|
@@ -238,7 +238,7 @@ module ActiveRecord
|
|
|
238
238
|
def self.strict_loading_violation!(owner:, reflection:) # :nodoc:
|
|
239
239
|
case ActiveRecord.action_on_strict_loading_violation
|
|
240
240
|
when :raise
|
|
241
|
-
message =
|
|
241
|
+
message = reflection.strict_loading_violation_message(owner)
|
|
242
242
|
raise ActiveRecord::StrictLoadingViolationError.new(message)
|
|
243
243
|
when :log
|
|
244
244
|
name = "strict_loading_violation.active_record"
|
|
@@ -411,7 +411,7 @@ module ActiveRecord
|
|
|
411
411
|
end
|
|
412
412
|
end
|
|
413
413
|
|
|
414
|
-
#
|
|
414
|
+
# Override the default class equality method to provide support for decorated models.
|
|
415
415
|
def ===(object) # :nodoc:
|
|
416
416
|
object.is_a?(self)
|
|
417
417
|
end
|
|
@@ -38,7 +38,7 @@ module ActiveRecord
|
|
|
38
38
|
# the returned list. Most of the time we're only iterating over the write
|
|
39
39
|
# connection (i.e. migrations don't need to run for the write and read connection).
|
|
40
40
|
# Defaults to +false+.
|
|
41
|
-
# * <tt>include_hidden:</
|
|
41
|
+
# * <tt>include_hidden:</tt> Determines whether to include replicas and configurations
|
|
42
42
|
# hidden by +database_tasks: false+ in the returned list. Most of the time we're only
|
|
43
43
|
# iterating over the primary connections (i.e. migrations don't need to run for the
|
|
44
44
|
# write and read connection). Defaults to +false+.
|
|
@@ -136,7 +136,7 @@ module ActiveRecord
|
|
|
136
136
|
# end
|
|
137
137
|
# end
|
|
138
138
|
#
|
|
139
|
-
# Now you can list a bunch of entries, call
|
|
139
|
+
# Now you can list a bunch of entries, call <tt>Entry#title</tt>, and polymorphism will provide you with the answer.
|
|
140
140
|
#
|
|
141
141
|
# == Nested Attributes
|
|
142
142
|
#
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module Encryption
|
|
5
|
-
# Configuration API for
|
|
5
|
+
# Configuration API for ActiveRecord::Encryption
|
|
6
6
|
module Configurable
|
|
7
7
|
extend ActiveSupport::Concern
|
|
8
8
|
|
|
@@ -50,11 +50,17 @@ module ActiveRecord
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
def
|
|
53
|
+
def install_auto_filtered_parameters_hook(application) # :nodoc:
|
|
54
54
|
ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, encrypted_attribute_name|
|
|
55
|
-
|
|
55
|
+
filter_parameter = [("#{klass.model_name.element}" if klass.name), encrypted_attribute_name.to_s].compact.join(".")
|
|
56
|
+
application.config.filter_parameters << filter_parameter unless excluded_from_filter_parameters?(filter_parameter)
|
|
56
57
|
end
|
|
57
58
|
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
def excluded_from_filter_parameters?(filter_parameter)
|
|
62
|
+
ActiveRecord::Encryption.config.excluded_from_filter_parameters.find { |excluded_filter| excluded_filter.to_s == filter_parameter }
|
|
63
|
+
end
|
|
58
64
|
end
|
|
59
65
|
end
|
|
60
66
|
end
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module Encryption
|
|
5
|
-
#
|
|
5
|
+
# ActiveRecord::Encryption uses encryption contexts to configure the different entities used to
|
|
6
6
|
# encrypt/decrypt at a given moment in time.
|
|
7
7
|
#
|
|
8
|
-
# By default, the library uses a default encryption context. This is the
|
|
8
|
+
# By default, the library uses a default encryption context. This is the Context that gets configured
|
|
9
9
|
# initially via +config.active_record.encryption+ options. Library users can define nested encryption contexts
|
|
10
10
|
# when running blocks of code.
|
|
11
11
|
#
|
|
12
|
-
# See
|
|
12
|
+
# See Context.
|
|
13
13
|
module Contexts
|
|
14
14
|
extend ActiveSupport::Concern
|
|
15
15
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module Encryption
|
|
5
|
-
# A
|
|
5
|
+
# A KeyProvider that derives keys from passwords.
|
|
6
6
|
class DerivedSecretKeyProvider < KeyProvider
|
|
7
7
|
def initialize(passwords)
|
|
8
8
|
super(Array(passwords).collect { |password| Key.derive_from(password) })
|
|
@@ -18,12 +18,10 @@ module ActiveRecord
|
|
|
18
18
|
#
|
|
19
19
|
# === Options
|
|
20
20
|
#
|
|
21
|
-
# * <tt>:key_provider</tt> -
|
|
22
|
-
#
|
|
21
|
+
# * <tt>:key_provider</tt> - A key provider to provide encryption and decryption keys. Defaults to
|
|
22
|
+
# +ActiveRecord::Encryption.key_provider+.
|
|
23
23
|
# * <tt>:key</tt> - A password to derive the key from. It's a shorthand for a +:key_provider+ that
|
|
24
24
|
# serves derivated keys. Both options can't be used at the same time.
|
|
25
|
-
# * <tt>:key_provider</tt> - Set a +:key_provider+ to provide encryption and decryption keys. If not
|
|
26
|
-
# provided, it will default to the key provider set with `config.key_provider`.
|
|
27
25
|
# * <tt>:deterministic</tt> - By default, encryption is not deterministic. It will use a random
|
|
28
26
|
# initialization vector for each encryption operation. This means that encrypting the same content
|
|
29
27
|
# with the same key twice will generate different ciphertexts. When set to +true+, it will generate the
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module Encryption
|
|
5
|
-
# An
|
|
5
|
+
# An ActiveModel::Type::Value that encrypts/decrypts strings of text.
|
|
6
6
|
#
|
|
7
7
|
# This is the central piece that connects the encryption system with +encrypts+ declarations in the
|
|
8
8
|
# model classes. Whenever you declare an attribute as encrypted, it configures an +EncryptedAttributeType+
|
|
@@ -19,7 +19,7 @@ module ActiveRecord
|
|
|
19
19
|
#
|
|
20
20
|
# * <tt>:scheme</tt> - A +Scheme+ with the encryption properties for this attribute.
|
|
21
21
|
# * <tt>:cast_type</tt> - A type that will be used to serialize (before encrypting) and deserialize
|
|
22
|
-
# (after decrypting).
|
|
22
|
+
# (after decrypting). ActiveModel::Type::String by default.
|
|
23
23
|
def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false)
|
|
24
24
|
super()
|
|
25
25
|
@scheme = scheme
|
|
@@ -6,17 +6,17 @@ require "active_support/core_ext/numeric"
|
|
|
6
6
|
|
|
7
7
|
module ActiveRecord
|
|
8
8
|
module Encryption
|
|
9
|
-
# An encryptor exposes the encryption API that
|
|
9
|
+
# An encryptor exposes the encryption API that ActiveRecord::Encryption::EncryptedAttributeType
|
|
10
10
|
# uses for encrypting and decrypting attribute values.
|
|
11
11
|
#
|
|
12
|
-
# It interacts with a
|
|
13
|
-
#
|
|
12
|
+
# It interacts with a KeyProvider for getting the keys, and delegate to
|
|
13
|
+
# ActiveRecord::Encryption::Cipher the actual encryption algorithm.
|
|
14
14
|
class Encryptor
|
|
15
15
|
# Encrypts +clean_text+ and returns the encrypted result
|
|
16
16
|
#
|
|
17
17
|
# Internally, it will:
|
|
18
18
|
#
|
|
19
|
-
# 1. Create a new
|
|
19
|
+
# 1. Create a new ActiveRecord::Encryption::Message
|
|
20
20
|
# 2. Compress and encrypt +clean_text+ as the message payload
|
|
21
21
|
# 3. Serialize it with +ActiveRecord::Encryption.message_serializer+ (+ActiveRecord::Encryption::SafeMarshal+
|
|
22
22
|
# by default)
|
|
@@ -26,10 +26,10 @@ module ActiveRecord
|
|
|
26
26
|
#
|
|
27
27
|
# [:key_provider]
|
|
28
28
|
# Key provider to use for the encryption operation. It will default to
|
|
29
|
-
# +ActiveRecord::Encryption.key_provider+ when not provided
|
|
29
|
+
# +ActiveRecord::Encryption.key_provider+ when not provided.
|
|
30
30
|
#
|
|
31
31
|
# [:cipher_options]
|
|
32
|
-
#
|
|
32
|
+
# Cipher-specific options that will be passed to the Cipher configured in
|
|
33
33
|
# +ActiveRecord::Encryption.cipher+
|
|
34
34
|
def encrypt(clear_text, key_provider: default_key_provider, cipher_options: {})
|
|
35
35
|
clear_text = force_encoding_if_needed(clear_text) if cipher_options[:deterministic]
|
|
@@ -47,7 +47,7 @@ module ActiveRecord
|
|
|
47
47
|
# +ActiveRecord::Encryption.key_provider+ when not provided
|
|
48
48
|
#
|
|
49
49
|
# [:cipher_options]
|
|
50
|
-
#
|
|
50
|
+
# Cipher-specific options that will be passed to the Cipher configured in
|
|
51
51
|
# +ActiveRecord::Encryption.cipher+
|
|
52
52
|
def decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {})
|
|
53
53
|
message = deserialize_message(encrypted_text)
|
|
@@ -4,13 +4,13 @@ module ActiveRecord
|
|
|
4
4
|
module Encryption
|
|
5
5
|
# Implements a simple envelope encryption approach where:
|
|
6
6
|
#
|
|
7
|
-
# * It generates a random data-encryption key for each encryption operation
|
|
7
|
+
# * It generates a random data-encryption key for each encryption operation.
|
|
8
8
|
# * It stores the generated key along with the encrypted payload. It encrypts this key
|
|
9
|
-
# with the master key provided in the
|
|
9
|
+
# with the master key provided in the +active_record_encryption.primary_key+ credential.
|
|
10
10
|
#
|
|
11
11
|
# This provider can work with multiple master keys. It will use the last one for encrypting.
|
|
12
12
|
#
|
|
13
|
-
# When
|
|
13
|
+
# When +config.active_record.encryption.store_key_references+ is true, it will also store a reference to
|
|
14
14
|
# the specific master key that was used to encrypt the data-encryption key. When not set,
|
|
15
15
|
# it will try all the configured master keys looking for the right one, in order to
|
|
16
16
|
# return the right decryption key.
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Automatically expand encrypted arguments to support querying both encrypted and unencrypted data
|
|
4
|
-
#
|
|
5
|
-
# Active Record Encryption supports querying the db using deterministic attributes. For example:
|
|
6
|
-
#
|
|
7
|
-
# Contact.find_by(email_address: "jorge@hey.com")
|
|
8
|
-
#
|
|
9
|
-
# The value "jorge@hey.com" will get encrypted automatically to perform the query. But there is
|
|
10
|
-
# a problem while the data is being encrypted. This won't work. During that time, you need these
|
|
11
|
-
# queries to be:
|
|
12
|
-
#
|
|
13
|
-
# Contact.find_by(email_address: [ "jorge@hey.com", "<encrypted jorge@hey.com>" ])
|
|
14
|
-
#
|
|
15
|
-
# This patches ActiveRecord to support this automatically. It addresses both:
|
|
16
|
-
#
|
|
17
|
-
# * ActiveRecord::Base: Used in +Contact.find_by_email_address(...)+
|
|
18
|
-
# * ActiveRecord::Relation: Used in +Contact.internal.find_by_email_address(...)+
|
|
19
|
-
#
|
|
20
|
-
# +ActiveRecord::Base+ relies on +ActiveRecord::Relation+ (+ActiveRecord::QueryMethods+) but it does
|
|
21
|
-
# some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
|
|
22
|
-
# as it's invoked (so that the proper prepared statement is cached).
|
|
23
|
-
#
|
|
24
|
-
# When modifying this file run performance tests in +test/performance/extended_deterministic_queries_performance_test.rb+ to
|
|
25
|
-
# make sure performance overhead is acceptable.
|
|
26
|
-
#
|
|
27
|
-
# We will extend this to support previous "encryption context" versions in future iterations
|
|
28
|
-
#
|
|
29
|
-
# @TODO Experimental. Support for every kind of query is pending
|
|
30
|
-
# @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
|
|
31
3
|
module ActiveRecord
|
|
32
4
|
module Encryption
|
|
5
|
+
# Automatically expand encrypted arguments to support querying both encrypted and unencrypted data
|
|
6
|
+
#
|
|
7
|
+
# Active Record \Encryption supports querying the db using deterministic attributes. For example:
|
|
8
|
+
#
|
|
9
|
+
# Contact.find_by(email_address: "jorge@hey.com")
|
|
10
|
+
#
|
|
11
|
+
# The value "jorge@hey.com" will get encrypted automatically to perform the query. But there is
|
|
12
|
+
# a problem while the data is being encrypted. This won't work. During that time, you need these
|
|
13
|
+
# queries to be:
|
|
14
|
+
#
|
|
15
|
+
# Contact.find_by(email_address: [ "jorge@hey.com", "<encrypted jorge@hey.com>" ])
|
|
16
|
+
#
|
|
17
|
+
# This patches ActiveRecord to support this automatically. It addresses both:
|
|
18
|
+
#
|
|
19
|
+
# * ActiveRecord::Base - Used in <tt>Contact.find_by_email_address(...)</tt>
|
|
20
|
+
# * ActiveRecord::Relation - Used in <tt>Contact.internal.find_by_email_address(...)</tt>
|
|
21
|
+
#
|
|
22
|
+
# ActiveRecord::Base relies on ActiveRecord::Relation (ActiveRecord::QueryMethods) but it does
|
|
23
|
+
# some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
|
|
24
|
+
# as it's invoked (so that the proper prepared statement is cached).
|
|
25
|
+
#
|
|
26
|
+
# When modifying this file run performance tests in +test/performance/extended_deterministic_queries_performance_test.rb+ to
|
|
27
|
+
# make sure performance overhead is acceptable.
|
|
28
|
+
#
|
|
29
|
+
# We will extend this to support previous "encryption context" versions in future iterations
|
|
30
|
+
#
|
|
31
|
+
# @TODO Experimental. Support for every kind of query is pending
|
|
32
|
+
# @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
|
|
33
33
|
module ExtendedDeterministicQueries
|
|
34
34
|
def self.install_support
|
|
35
35
|
ActiveRecord::Relation.prepend(RelationQueries)
|
|
@@ -12,7 +12,7 @@ module ActiveRecord
|
|
|
12
12
|
#
|
|
13
13
|
# message.headers.encrypted_data_key # instead of message.headers[:k]
|
|
14
14
|
#
|
|
15
|
-
# See +Properties
|
|
15
|
+
# See +Properties::DEFAULT_PROPERTIES+, Key, Message
|
|
16
16
|
class Properties
|
|
17
17
|
ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, TrueClass, FalseClass, Symbol, NilClass]
|
|
18
18
|
|
data/lib/active_record/enum.rb
CHANGED
|
@@ -83,7 +83,7 @@ module ActiveRecord
|
|
|
83
83
|
#
|
|
84
84
|
# In rare circumstances you might need to access the mapping directly.
|
|
85
85
|
# The mappings are exposed through a class method with the pluralized attribute
|
|
86
|
-
# name, which return the mapping in a
|
|
86
|
+
# name, which return the mapping in a ActiveSupport::HashWithIndifferentAccess :
|
|
87
87
|
#
|
|
88
88
|
# Conversation.statuses[:active] # => 0
|
|
89
89
|
# Conversation.statuses["archived"] # => 1
|
|
@@ -241,13 +241,13 @@ module ActiveRecord
|
|
|
241
241
|
# The generated ID for a given label is constant, so we can discover
|
|
242
242
|
# any fixture's ID without loading anything, as long as we know the label.
|
|
243
243
|
#
|
|
244
|
-
# == Label references for associations (belongs_to
|
|
244
|
+
# == Label references for associations (+belongs_to+, +has_one+, +has_many+)
|
|
245
245
|
#
|
|
246
246
|
# Specifying foreign keys in fixtures can be very fragile, not to
|
|
247
247
|
# mention difficult to read. Since Active Record can figure out the ID of
|
|
248
248
|
# any fixture from its label, you can specify FK's by label instead of ID.
|
|
249
249
|
#
|
|
250
|
-
# === belongs_to
|
|
250
|
+
# === +belongs_to+
|
|
251
251
|
#
|
|
252
252
|
# Let's break out some more monkeys and pirates.
|
|
253
253
|
#
|
|
@@ -286,7 +286,7 @@ module ActiveRecord
|
|
|
286
286
|
# a target *label* for the *association* (monkey: george) rather than
|
|
287
287
|
# a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
|
|
288
288
|
#
|
|
289
|
-
# ==== Polymorphic belongs_to
|
|
289
|
+
# ==== Polymorphic +belongs_to+
|
|
290
290
|
#
|
|
291
291
|
# Supporting polymorphic relationships is a little bit more complicated, since
|
|
292
292
|
# Active Record needs to know what type your association is pointing at. Something
|
|
@@ -311,7 +311,7 @@ module ActiveRecord
|
|
|
311
311
|
#
|
|
312
312
|
# Just provide the polymorphic target type and Active Record will take care of the rest.
|
|
313
313
|
#
|
|
314
|
-
# === has_and_belongs_to_many or has_many :through
|
|
314
|
+
# === +has_and_belongs_to_many+ or <tt>has_many :through</tt>
|
|
315
315
|
#
|
|
316
316
|
# Time to give our monkey some fruit.
|
|
317
317
|
#
|
|
@@ -407,7 +407,7 @@ module ActiveRecord
|
|
|
407
407
|
# defaults:
|
|
408
408
|
#
|
|
409
409
|
# DEFAULTS: &DEFAULTS
|
|
410
|
-
# created_on: <%= 3.weeks.ago.
|
|
410
|
+
# created_on: <%= 3.weeks.ago.to_fs(:db) %>
|
|
411
411
|
#
|
|
412
412
|
# first:
|
|
413
413
|
# name: Smurf
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
|
-
# Returns the version of
|
|
4
|
+
# Returns the currently loaded version of Active Record as a <tt>Gem::Version</tt>.
|
|
5
5
|
def self.gem_version
|
|
6
6
|
Gem::Version.new VERSION::STRING
|
|
7
7
|
end
|
|
@@ -9,7 +9,7 @@ module ActiveRecord
|
|
|
9
9
|
module VERSION
|
|
10
10
|
MAJOR = 7
|
|
11
11
|
MINOR = 0
|
|
12
|
-
TINY =
|
|
12
|
+
TINY = 4
|
|
13
13
|
PRE = nil
|
|
14
14
|
|
|
15
15
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
|
@@ -79,7 +79,7 @@ module ActiveRecord
|
|
|
79
79
|
timestamp = max_updated_column_timestamp
|
|
80
80
|
|
|
81
81
|
if timestamp
|
|
82
|
-
timestamp = timestamp.utc.
|
|
82
|
+
timestamp = timestamp.utc.to_fs(cache_timestamp_format)
|
|
83
83
|
"#{model_name.cache_key}/#{id}-#{timestamp}"
|
|
84
84
|
else
|
|
85
85
|
"#{model_name.cache_key}/#{id}"
|
|
@@ -103,7 +103,7 @@ module ActiveRecord
|
|
|
103
103
|
raw_timestamp_to_cache_version(timestamp)
|
|
104
104
|
|
|
105
105
|
elsif timestamp = updated_at
|
|
106
|
-
timestamp.utc.
|
|
106
|
+
timestamp.utc.to_fs(cache_timestamp_format)
|
|
107
107
|
end
|
|
108
108
|
elsif self.class.has_attribute?("updated_at")
|
|
109
109
|
raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
|
|
@@ -5,7 +5,7 @@ module ActiveRecord
|
|
|
5
5
|
# Locking::Pessimistic provides support for row-level locking using
|
|
6
6
|
# SELECT ... FOR UPDATE and other lock types.
|
|
7
7
|
#
|
|
8
|
-
# Chain <tt>ActiveRecord::Base#find</tt> to
|
|
8
|
+
# Chain <tt>ActiveRecord::Base#find</tt> to ActiveRecord::QueryMethods#lock to obtain an exclusive
|
|
9
9
|
# lock on the selected rows:
|
|
10
10
|
# # select * from accounts where id=1 for update
|
|
11
11
|
# Account.lock.find(1)
|
|
@@ -81,11 +81,11 @@ module ActiveRecord
|
|
|
81
81
|
|
|
82
82
|
# Wraps the passed block in a transaction, locking the object
|
|
83
83
|
# before yielding. You can pass the SQL locking clause
|
|
84
|
-
# as an optional argument (see
|
|
84
|
+
# as an optional argument (see #lock!).
|
|
85
85
|
#
|
|
86
86
|
# You can also pass options like <tt>requires_new:</tt>, <tt>isolation:</tt>,
|
|
87
87
|
# and <tt>joinable:</tt> to the wrapping transaction (see
|
|
88
|
-
#
|
|
88
|
+
# ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction).
|
|
89
89
|
def with_lock(*args)
|
|
90
90
|
transaction_opts = args.extract_options!
|
|
91
91
|
lock = args.present? ? args.first : true
|