activerecord 6.1.6 → 7.0.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1314 -975
- data/README.rdoc +1 -1
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/association_relation.rb +0 -10
- data/lib/active_record/associations/association.rb +33 -17
- data/lib/active_record/associations/association_scope.rb +1 -3
- data/lib/active_record/associations/belongs_to_association.rb +15 -4
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
- data/lib/active_record/associations/builder/association.rb +8 -2
- data/lib/active_record/associations/builder/belongs_to.rb +19 -6
- data/lib/active_record/associations/builder/collection_association.rb +10 -3
- data/lib/active_record/associations/builder/has_many.rb +3 -2
- data/lib/active_record/associations/builder/has_one.rb +2 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -2
- data/lib/active_record/associations/collection_association.rb +19 -21
- data/lib/active_record/associations/collection_proxy.rb +10 -5
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/has_many_association.rb +8 -5
- data/lib/active_record/associations/has_many_through_association.rb +2 -1
- data/lib/active_record/associations/has_one_association.rb +10 -7
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +23 -15
- data/lib/active_record/associations/preloader/association.rb +186 -52
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +147 -0
- data/lib/active_record/associations/preloader/through_association.rb +49 -13
- data/lib/active_record/associations/preloader.rb +39 -113
- data/lib/active_record/associations/singular_association.rb +8 -2
- data/lib/active_record/associations/through_association.rb +3 -3
- data/lib/active_record/associations.rb +124 -95
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +1 -1
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
- data/lib/active_record/attribute_methods/dirty.rb +49 -16
- data/lib/active_record/attribute_methods/primary_key.rb +2 -2
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +7 -5
- data/lib/active_record/attribute_methods/serialization.rb +57 -19
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
- data/lib/active_record/attribute_methods/write.rb +7 -10
- data/lib/active_record/attribute_methods.rb +14 -15
- data/lib/active_record/attributes.rb +24 -35
- data/lib/active_record/autosave_association.rb +8 -23
- data/lib/active_record/base.rb +19 -1
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/coders/yaml_column.rb +10 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
- data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +38 -13
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -24
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
- data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +105 -81
- data/lib/active_record/connection_adapters/column.rb +4 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -24
- data/lib/active_record/connection_adapters/mysql/quoting.rb +37 -21
- 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/mysql2_adapter.rb +12 -6
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +51 -51
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +37 -19
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +208 -107
- data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +17 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +96 -32
- data/lib/active_record/connection_adapters.rb +6 -5
- data/lib/active_record/connection_handling.rb +49 -55
- data/lib/active_record/core.rb +124 -134
- data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
- data/lib/active_record/database_configurations/database_config.rb +12 -9
- data/lib/active_record/database_configurations/hash_config.rb +63 -5
- data/lib/active_record/database_configurations/url_config.rb +2 -2
- data/lib/active_record/database_configurations.rb +15 -32
- data/lib/active_record/delegated_type.rb +53 -12
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +44 -0
- data/lib/active_record/encryption/configurable.rb +67 -0
- data/lib/active_record/encryption/context.rb +35 -0
- data/lib/active_record/encryption/contexts.rb +72 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +206 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +155 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +42 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_serializer.rb +90 -0
- data/lib/active_record/encryption/null_encryptor.rb +21 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
- data/lib/active_record/encryption/scheme.rb +99 -0
- data/lib/active_record/encryption.rb +55 -0
- data/lib/active_record/enum.rb +50 -43
- data/lib/active_record/errors.rb +67 -4
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/fixture_set/file.rb +15 -1
- data/lib/active_record/fixture_set/table_row.rb +41 -6
- data/lib/active_record/fixture_set/table_rows.rb +4 -4
- data/lib/active_record/fixtures.rb +20 -23
- data/lib/active_record/future_result.rb +139 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +55 -17
- data/lib/active_record/insert_all.rb +80 -14
- data/lib/active_record/integration.rb +4 -3
- data/lib/active_record/internal_metadata.rb +1 -5
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/optimistic.rb +10 -9
- data/lib/active_record/locking/pessimistic.rb +10 -4
- data/lib/active_record/log_subscriber.rb +23 -7
- data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
- data/lib/active_record/middleware/database_selector.rb +18 -6
- data/lib/active_record/middleware/shard_selector.rb +60 -0
- data/lib/active_record/migration/command_recorder.rb +7 -7
- data/lib/active_record/migration/compatibility.rb +84 -2
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration.rb +114 -83
- data/lib/active_record/model_schema.rb +58 -59
- data/lib/active_record/nested_attributes.rb +13 -12
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/null_relation.rb +2 -6
- data/lib/active_record/persistence.rb +228 -60
- data/lib/active_record/query_cache.rb +2 -2
- data/lib/active_record/query_logs.rb +138 -0
- data/lib/active_record/querying.rb +16 -6
- data/lib/active_record/railtie.rb +136 -22
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +78 -136
- data/lib/active_record/readonly_attributes.rb +11 -0
- data/lib/active_record/reflection.rb +73 -50
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +6 -6
- data/lib/active_record/relation/calculations.rb +43 -38
- data/lib/active_record/relation/delegation.rb +7 -7
- data/lib/active_record/relation/finder_methods.rb +31 -35
- data/lib/active_record/relation/merger.rb +20 -13
- data/lib/active_record/relation/predicate_builder.rb +1 -6
- data/lib/active_record/relation/query_attribute.rb +5 -11
- data/lib/active_record/relation/query_methods.rb +276 -67
- data/lib/active_record/relation/record_fetch_warning.rb +7 -9
- data/lib/active_record/relation/spawn_methods.rb +2 -2
- data/lib/active_record/relation/where_clause.rb +10 -19
- data/lib/active_record/relation.rb +189 -88
- data/lib/active_record/result.rb +17 -7
- data/lib/active_record/runtime_registry.rb +9 -13
- data/lib/active_record/sanitization.rb +17 -12
- data/lib/active_record/schema.rb +38 -23
- data/lib/active_record/schema_dumper.rb +25 -19
- data/lib/active_record/schema_migration.rb +4 -4
- data/lib/active_record/scoping/default.rb +60 -13
- data/lib/active_record/scoping/named.rb +3 -11
- data/lib/active_record/scoping.rb +64 -34
- data/lib/active_record/serialization.rb +6 -1
- data/lib/active_record/signed_id.rb +3 -3
- data/lib/active_record/store.rb +7 -2
- data/lib/active_record/suppressor.rb +11 -15
- data/lib/active_record/tasks/database_tasks.rb +127 -60
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +16 -9
- data/lib/active_record/timestamp.rb +3 -4
- data/lib/active_record/transactions.rb +9 -14
- data/lib/active_record/translation.rb +3 -3
- data/lib/active_record/type/adapter_specific_registry.rb +32 -7
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
- data/lib/active_record/type/internal/timezone.rb +2 -2
- data/lib/active_record/type/serialized.rb +1 -1
- data/lib/active_record/type/type_map.rb +17 -20
- data/lib/active_record/type.rb +1 -2
- data/lib/active_record/validations/associated.rb +4 -4
- data/lib/active_record/validations/presence.rb +2 -2
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +217 -27
- data/lib/arel/attributes/attribute.rb +0 -8
- data/lib/arel/crud.rb +28 -22
- data/lib/arel/delete_manager.rb +18 -4
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/casted.rb +1 -1
- data/lib/arel/nodes/delete_statement.rb +12 -13
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/update_statement.rb +8 -3
- data/lib/arel/nodes.rb +1 -0
- data/lib/arel/predications.rb +11 -3
- data/lib/arel/select_manager.rb +10 -4
- data/lib/arel/table.rb +0 -1
- data/lib/arel/tree_manager.rb +0 -12
- data/lib/arel/update_manager.rb +18 -4
- data/lib/arel/visitors/dot.rb +80 -90
- data/lib/arel/visitors/mysql.rb +8 -2
- data/lib/arel/visitors/postgresql.rb +0 -10
- data/lib/arel/visitors/to_sql.rb +58 -2
- data/lib/arel.rb +2 -1
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +55 -11
@@ -15,15 +15,6 @@ module ActiveRecord
|
|
15
15
|
@name = name
|
16
16
|
end
|
17
17
|
|
18
|
-
def spec_name
|
19
|
-
@name
|
20
|
-
end
|
21
|
-
deprecate spec_name: "please use name instead"
|
22
|
-
|
23
|
-
def config
|
24
|
-
raise NotImplementedError
|
25
|
-
end
|
26
|
-
|
27
18
|
def adapter_method
|
28
19
|
"#{adapter}_connection"
|
29
20
|
end
|
@@ -48,6 +39,18 @@ module ActiveRecord
|
|
48
39
|
raise NotImplementedError
|
49
40
|
end
|
50
41
|
|
42
|
+
def min_threads
|
43
|
+
raise NotImplementedError
|
44
|
+
end
|
45
|
+
|
46
|
+
def max_threads
|
47
|
+
raise NotImplementedError
|
48
|
+
end
|
49
|
+
|
50
|
+
def max_queue
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
53
|
+
|
51
54
|
def checkout_timeout
|
52
55
|
raise NotImplementedError
|
53
56
|
end
|
@@ -26,16 +26,12 @@ module ActiveRecord
|
|
26
26
|
# connections.
|
27
27
|
class HashConfig < DatabaseConfig
|
28
28
|
attr_reader :configuration_hash
|
29
|
+
|
29
30
|
def initialize(env_name, name, configuration_hash)
|
30
31
|
super(env_name, name)
|
31
32
|
@configuration_hash = configuration_hash.symbolize_keys.freeze
|
32
33
|
end
|
33
34
|
|
34
|
-
def config
|
35
|
-
ActiveSupport::Deprecation.warn("DatabaseConfig#config will be removed in 7.0.0 in favor of DatabaseConfig#configuration_hash which returns a hash with symbol keys")
|
36
|
-
configuration_hash.stringify_keys
|
37
|
-
end
|
38
|
-
|
39
35
|
# Determines whether a database configuration is for a replica / readonly
|
40
36
|
# connection. If the +replica+ key is present in the config, +replica?+ will
|
41
37
|
# return +true+.
|
@@ -54,6 +50,10 @@ module ActiveRecord
|
|
54
50
|
configuration_hash[:host]
|
55
51
|
end
|
56
52
|
|
53
|
+
def socket # :nodoc:
|
54
|
+
configuration_hash[:socket]
|
55
|
+
end
|
56
|
+
|
57
57
|
def database
|
58
58
|
configuration_hash[:database]
|
59
59
|
end
|
@@ -66,6 +66,18 @@ module ActiveRecord
|
|
66
66
|
(configuration_hash[:pool] || 5).to_i
|
67
67
|
end
|
68
68
|
|
69
|
+
def min_threads
|
70
|
+
(configuration_hash[:min_threads] || 0).to_i
|
71
|
+
end
|
72
|
+
|
73
|
+
def max_threads
|
74
|
+
(configuration_hash[:max_threads] || pool).to_i
|
75
|
+
end
|
76
|
+
|
77
|
+
def max_queue
|
78
|
+
max_threads * 4
|
79
|
+
end
|
80
|
+
|
69
81
|
def checkout_timeout
|
70
82
|
(configuration_hash[:checkout_timeout] || 5).to_f
|
71
83
|
end
|
@@ -91,6 +103,52 @@ module ActiveRecord
|
|
91
103
|
def schema_cache_path
|
92
104
|
configuration_hash[:schema_cache_path]
|
93
105
|
end
|
106
|
+
|
107
|
+
def default_schema_cache_path
|
108
|
+
"db/schema_cache.yml"
|
109
|
+
end
|
110
|
+
|
111
|
+
def lazy_schema_cache_path
|
112
|
+
schema_cache_path || default_schema_cache_path
|
113
|
+
end
|
114
|
+
|
115
|
+
def primary? # :nodoc:
|
116
|
+
Base.configurations.primary?(name)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Determines whether to dump the schema/structure files and the
|
120
|
+
# filename that should be used.
|
121
|
+
#
|
122
|
+
# If +configuration_hash[:schema_dump]+ is set to +false+ or +nil+
|
123
|
+
# the schema will not be dumped.
|
124
|
+
#
|
125
|
+
# If the config option is set that will be used. Otherwise Rails
|
126
|
+
# will generate the filename from the database config name.
|
127
|
+
def schema_dump(format = ActiveRecord.schema_format)
|
128
|
+
if configuration_hash.key?(:schema_dump)
|
129
|
+
if config = configuration_hash[:schema_dump]
|
130
|
+
config
|
131
|
+
end
|
132
|
+
elsif primary?
|
133
|
+
schema_file_type(format)
|
134
|
+
else
|
135
|
+
"#{name}_#{schema_file_type(format)}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def database_tasks? # :nodoc:
|
140
|
+
!replica? && !!configuration_hash.fetch(:database_tasks, true)
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
def schema_file_type(format)
|
145
|
+
case format
|
146
|
+
when :ruby
|
147
|
+
"schema.rb"
|
148
|
+
when :sql
|
149
|
+
"structure.sql"
|
150
|
+
end
|
151
|
+
end
|
94
152
|
end
|
95
153
|
end
|
96
154
|
end
|
@@ -19,7 +19,7 @@ module ActiveRecord
|
|
19
19
|
#
|
20
20
|
# ==== Options
|
21
21
|
#
|
22
|
-
# * <tt>:env_name</tt> - The Rails environment,
|
22
|
+
# * <tt>:env_name</tt> - The Rails environment, i.e. "development".
|
23
23
|
# * <tt>:name</tt> - The db config name. In a standard two-tier
|
24
24
|
# database configuration this will default to "primary". In a multiple
|
25
25
|
# database three-tier database configuration this corresponds to the name
|
@@ -42,7 +42,7 @@ module ActiveRecord
|
|
42
42
|
# Return a Hash that can be merged into the main config that represents
|
43
43
|
# the passed in url
|
44
44
|
def build_url_hash
|
45
|
-
if url.nil? ||
|
45
|
+
if url.nil? || url.start_with?("jdbc:", "http:", "https:")
|
46
46
|
{ url: url }
|
47
47
|
else
|
48
48
|
ConnectionUrlResolver.new(url).to_hash
|
@@ -21,7 +21,7 @@ module ActiveRecord
|
|
21
21
|
end
|
22
22
|
|
23
23
|
# Collects the configs for the environment and optionally the specification
|
24
|
-
# name passed in. To include replica configurations pass <tt>
|
24
|
+
# name passed in. To include replica configurations pass <tt>include_hidden: true</tt>.
|
25
25
|
#
|
26
26
|
# If a name is provided a single DatabaseConfig object will be
|
27
27
|
# returned, otherwise an array of DatabaseConfig objects will be
|
@@ -34,22 +34,26 @@ module ActiveRecord
|
|
34
34
|
# * <tt>name:</tt> The db config name (i.e. primary, animals, etc.). Defaults
|
35
35
|
# to +nil+. If no +env_name+ is specified the config for the default env and the
|
36
36
|
# passed +name+ will be returned.
|
37
|
-
# * <tt>include_replicas:</tt> Determines whether to include replicas in
|
37
|
+
# * <tt>include_replicas:</tt> Deprecated. Determines whether to include replicas in
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
# * <tt>include_hidden:</tt> Determines whether to include replicas and configurations
|
42
|
+
# hidden by +database_tasks: false+ in the returned list. Most of the time we're only
|
43
|
+
# iterating over the primary connections (i.e. migrations don't need to run for the
|
44
|
+
# write and read connection). Defaults to +false+.
|
45
|
+
def configs_for(env_name: nil, name: nil, include_replicas: false, include_hidden: false)
|
46
|
+
if include_replicas
|
47
|
+
include_hidden = include_replicas
|
48
|
+
ActiveSupport::Deprecation.warn("The kwarg `include_replicas` is deprecated in favor of `include_hidden`. When `include_hidden` is passed, configurations with `replica: true` or `database_tasks: false` will be returned. `include_replicas` will be removed in Rails 7.1.")
|
45
49
|
end
|
46
50
|
|
47
51
|
env_name ||= default_env if name
|
48
52
|
configs = env_with_configs(env_name)
|
49
53
|
|
50
|
-
unless
|
54
|
+
unless include_hidden
|
51
55
|
configs = configs.select do |db_config|
|
52
|
-
|
56
|
+
db_config.database_tasks?
|
53
57
|
end
|
54
58
|
end
|
55
59
|
|
@@ -62,19 +66,6 @@ module ActiveRecord
|
|
62
66
|
end
|
63
67
|
end
|
64
68
|
|
65
|
-
# Returns the config hash that corresponds with the environment
|
66
|
-
#
|
67
|
-
# If the application has multiple databases +default_hash+ will
|
68
|
-
# return the first config hash for the environment.
|
69
|
-
#
|
70
|
-
# { database: "my_db", adapter: "mysql2" }
|
71
|
-
def default_hash(env = default_env)
|
72
|
-
default = find_db_config(env)
|
73
|
-
default.configuration_hash if default
|
74
|
-
end
|
75
|
-
alias :[] :default_hash
|
76
|
-
deprecate "[]": "Use configs_for", default_hash: "Use configs_for"
|
77
|
-
|
78
69
|
# Returns a single DatabaseConfig object based on the requested environment.
|
79
70
|
#
|
80
71
|
# If the application has multiple databases +find_db_config+ will return
|
@@ -101,14 +92,6 @@ module ActiveRecord
|
|
101
92
|
first_config && name == first_config.name
|
102
93
|
end
|
103
94
|
|
104
|
-
# Returns the DatabaseConfigurations object as a Hash.
|
105
|
-
def to_h
|
106
|
-
configurations.inject({}) do |memo, db_config|
|
107
|
-
memo.merge(db_config.env_name => db_config.configuration_hash.stringify_keys)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
deprecate to_h: "You can use `ActiveRecord::Base.configurations.configs_for(env_name: 'env', name: 'primary').configuration_hash` to get the configuration hashes."
|
111
|
-
|
112
95
|
# Checks if the application's configurations are empty.
|
113
96
|
#
|
114
97
|
# Aliased to blank?
|
@@ -167,7 +150,7 @@ module ActiveRecord
|
|
167
150
|
return configs if configs.is_a?(Array)
|
168
151
|
|
169
152
|
db_configs = configs.flat_map do |env_name, config|
|
170
|
-
if config.is_a?(Hash) && config.all?
|
153
|
+
if config.is_a?(Hash) && config.values.all?(Hash)
|
171
154
|
walk_configs(env_name.to_s, config)
|
172
155
|
else
|
173
156
|
build_db_config_from_raw_config(env_name.to_s, "primary", config)
|
@@ -194,7 +177,7 @@ module ActiveRecord
|
|
194
177
|
raise AdapterNotSpecified, <<~MSG
|
195
178
|
The `#{name}` database is not configured for the `#{default_env}` environment.
|
196
179
|
|
197
|
-
Available
|
180
|
+
Available database configurations are:
|
198
181
|
|
199
182
|
#{build_configuration_sentence}
|
200
183
|
MSG
|
@@ -202,7 +185,7 @@ module ActiveRecord
|
|
202
185
|
end
|
203
186
|
|
204
187
|
def build_configuration_sentence
|
205
|
-
configs = configs_for(
|
188
|
+
configs = configs_for(include_hidden: true)
|
206
189
|
|
207
190
|
configs.group_by(&:env_name).map do |env, config|
|
208
191
|
names = config.map(&:name)
|
@@ -51,10 +51,9 @@ module ActiveRecord
|
|
51
51
|
# end
|
52
52
|
# end
|
53
53
|
#
|
54
|
-
# # Schema: messages[ id, subject ]
|
54
|
+
# # Schema: messages[ id, subject, body ]
|
55
55
|
# class Message < ApplicationRecord
|
56
56
|
# include Entryable
|
57
|
-
# has_rich_text :content
|
58
57
|
# end
|
59
58
|
#
|
60
59
|
# # Schema: comments[ id, content ]
|
@@ -66,7 +65,7 @@ module ActiveRecord
|
|
66
65
|
# resides in the +Entry+ "superclass". But the +Entry+ absolutely can stand alone in terms of querying capacity
|
67
66
|
# in particular. You can now easily do things like:
|
68
67
|
#
|
69
|
-
# Account.entries.order(created_at: :desc).limit(50)
|
68
|
+
# Account.find(1).entries.order(created_at: :desc).limit(50)
|
70
69
|
#
|
71
70
|
# Which is exactly what you want when displaying both comments and messages together. The entry itself can
|
72
71
|
# be rendered as its delegated type easily, like so:
|
@@ -76,7 +75,9 @@ module ActiveRecord
|
|
76
75
|
#
|
77
76
|
# # entries/entryables/_message.html.erb
|
78
77
|
# <div class="message">
|
79
|
-
#
|
78
|
+
# <div class="subject"><%= entry.message.subject %></div>
|
79
|
+
# <p><%= entry.message.body %></p>
|
80
|
+
# <i>Posted on <%= entry.created_at %> by <%= entry.creator.name %></i>
|
80
81
|
# </div>
|
81
82
|
#
|
82
83
|
# # entries/entryables/_comment.html.erb
|
@@ -135,7 +136,22 @@ module ActiveRecord
|
|
135
136
|
# end
|
136
137
|
# end
|
137
138
|
#
|
138
|
-
# 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
|
+
#
|
141
|
+
# == Nested Attributes
|
142
|
+
#
|
143
|
+
# Enabling nested attributes on a delegated_type association allows you to
|
144
|
+
# create the entry and message in one go:
|
145
|
+
#
|
146
|
+
# class Entry < ApplicationRecord
|
147
|
+
# delegated_type :entryable, types: %w[ Message Comment ]
|
148
|
+
# accepts_nested_attributes_for :entryable
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# params = { entry: { entryable_type: 'Message', entryable_attributes: { subject: 'Smiling' } } }
|
152
|
+
# entry = Entry.create(params[:entry])
|
153
|
+
# entry.entryable.id # => 2
|
154
|
+
# entry.entryable.subject # => 'Smiling'
|
139
155
|
module DelegatedType
|
140
156
|
# Defines this as a class that'll delegate its type for the passed +role+ to the class references in +types+.
|
141
157
|
# That'll create a polymorphic +belongs_to+ relationship to that +role+, and it'll add all the delegated
|
@@ -156,8 +172,6 @@ module ActiveRecord
|
|
156
172
|
# Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
|
157
173
|
# Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
|
158
174
|
#
|
159
|
-
# The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
|
160
|
-
#
|
161
175
|
# You can also declare namespaced types:
|
162
176
|
#
|
163
177
|
# class Entry < ApplicationRecord
|
@@ -167,15 +181,38 @@ module ActiveRecord
|
|
167
181
|
# Entry.access_notice_messages
|
168
182
|
# entry.access_notice_message
|
169
183
|
# entry.access_notice_message?
|
184
|
+
#
|
185
|
+
# === Options
|
186
|
+
#
|
187
|
+
# The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
|
188
|
+
# The following options can be included to specialize the behavior of the delegated type convenience methods.
|
189
|
+
#
|
190
|
+
# [:foreign_key]
|
191
|
+
# Specify the foreign key used for the convenience methods. By default this is guessed to be the passed
|
192
|
+
# +role+ with an "_id" suffix. So a class that defines a
|
193
|
+
# <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_id" as
|
194
|
+
# the default <tt>:foreign_key</tt>.
|
195
|
+
# [:primary_key]
|
196
|
+
# Specify the method that returns the primary key of associated object used for the convenience methods.
|
197
|
+
# By default this is +id+.
|
198
|
+
#
|
199
|
+
# Option examples:
|
200
|
+
# class Entry < ApplicationRecord
|
201
|
+
# delegated_type :entryable, types: %w[ Message Comment ], primary_key: :uuid, foreign_key: :entryable_uuid
|
202
|
+
# end
|
203
|
+
#
|
204
|
+
# Entry#message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
|
205
|
+
# Entry#comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
|
170
206
|
def delegated_type(role, types:, **options)
|
171
207
|
belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
|
172
|
-
define_delegated_type_methods role, types: types
|
208
|
+
define_delegated_type_methods role, types: types, options: options
|
173
209
|
end
|
174
210
|
|
175
211
|
private
|
176
|
-
def define_delegated_type_methods(role, types:)
|
212
|
+
def define_delegated_type_methods(role, types:, options:)
|
213
|
+
primary_key = options[:primary_key] || "id"
|
177
214
|
role_type = "#{role}_type"
|
178
|
-
role_id = "#{role}_id"
|
215
|
+
role_id = options[:foreign_key] || "#{role}_id"
|
179
216
|
|
180
217
|
define_method "#{role}_class" do
|
181
218
|
public_send("#{role}_type").constantize
|
@@ -185,8 +222,12 @@ module ActiveRecord
|
|
185
222
|
public_send("#{role}_class").model_name.singular.inquiry
|
186
223
|
end
|
187
224
|
|
225
|
+
define_method "build_#{role}" do |*params|
|
226
|
+
public_send("#{role}=", public_send("#{role}_class").new(*params))
|
227
|
+
end
|
228
|
+
|
188
229
|
types.each do |type|
|
189
|
-
scope_name = type.tableize.
|
230
|
+
scope_name = type.tableize.tr("/", "_")
|
190
231
|
singular = scope_name.singularize
|
191
232
|
query = "#{singular}?"
|
192
233
|
|
@@ -200,7 +241,7 @@ module ActiveRecord
|
|
200
241
|
public_send(role) if public_send(query)
|
201
242
|
end
|
202
243
|
|
203
|
-
define_method "#{singular}
|
244
|
+
define_method "#{singular}_#{primary_key}" do
|
204
245
|
public_send(role_id) if public_send(query)
|
205
246
|
end
|
206
247
|
end
|
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
6
6
|
|
7
7
|
# Job to destroy the records associated with a destroyed record in background.
|
8
8
|
class DestroyAssociationAsyncJob < ActiveJob::Base
|
9
|
-
queue_as { ActiveRecord
|
9
|
+
queue_as { ActiveRecord.queues[:destroy] }
|
10
10
|
|
11
11
|
discard_on ActiveJob::DeserializationError
|
12
12
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class DisableJoinsAssociationRelation < Relation # :nodoc:
|
5
|
+
attr_reader :ids, :key
|
6
|
+
|
7
|
+
def initialize(klass, key, ids)
|
8
|
+
@ids = ids.uniq
|
9
|
+
@key = key
|
10
|
+
super(klass)
|
11
|
+
end
|
12
|
+
|
13
|
+
def limit(value)
|
14
|
+
records.take(value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def first(limit = nil)
|
18
|
+
if limit
|
19
|
+
records.limit(limit).first
|
20
|
+
else
|
21
|
+
records.first
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def load
|
26
|
+
super
|
27
|
+
records = @records
|
28
|
+
|
29
|
+
records_by_id = records.group_by do |record|
|
30
|
+
record[key]
|
31
|
+
end
|
32
|
+
|
33
|
+
records = ids.flat_map { |id| records_by_id[id.to_i] }
|
34
|
+
records.compact!
|
35
|
+
|
36
|
+
@records = records
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
require "base64"
|
5
|
+
|
6
|
+
module ActiveRecord
|
7
|
+
module Encryption
|
8
|
+
class Cipher
|
9
|
+
# A 256-GCM cipher.
|
10
|
+
#
|
11
|
+
# By default it will use random initialization vectors. For deterministic encryption, it will use a SHA-256 hash of
|
12
|
+
# the text to encrypt and the secret.
|
13
|
+
#
|
14
|
+
# See +Encryptor+
|
15
|
+
class Aes256Gcm
|
16
|
+
CIPHER_TYPE = "aes-256-gcm"
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def key_length
|
20
|
+
OpenSSL::Cipher.new(CIPHER_TYPE).key_len
|
21
|
+
end
|
22
|
+
|
23
|
+
def iv_length
|
24
|
+
OpenSSL::Cipher.new(CIPHER_TYPE).iv_len
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# When iv not provided, it will generate a random iv on each encryption operation (default and
|
29
|
+
# recommended operation)
|
30
|
+
def initialize(secret, deterministic: false)
|
31
|
+
@secret = secret
|
32
|
+
@deterministic = deterministic
|
33
|
+
end
|
34
|
+
|
35
|
+
def encrypt(clear_text)
|
36
|
+
# This code is extracted from +ActiveSupport::MessageEncryptor+. Not using it directly because we want to control
|
37
|
+
# the message format and only serialize things once at the +ActiveRecord::Encryption::Message+ level. Also, this
|
38
|
+
# cipher is prepared to deal with deterministic/non deterministic encryption modes.
|
39
|
+
|
40
|
+
cipher = OpenSSL::Cipher.new(CIPHER_TYPE)
|
41
|
+
cipher.encrypt
|
42
|
+
cipher.key = @secret
|
43
|
+
|
44
|
+
iv = generate_iv(cipher, clear_text)
|
45
|
+
cipher.iv = iv
|
46
|
+
|
47
|
+
encrypted_data = clear_text.empty? ? clear_text.dup : cipher.update(clear_text)
|
48
|
+
encrypted_data << cipher.final
|
49
|
+
|
50
|
+
ActiveRecord::Encryption::Message.new(payload: encrypted_data).tap do |message|
|
51
|
+
message.headers.iv = iv
|
52
|
+
message.headers.auth_tag = cipher.auth_tag
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def decrypt(encrypted_message)
|
57
|
+
encrypted_data = encrypted_message.payload
|
58
|
+
iv = encrypted_message.headers.iv
|
59
|
+
auth_tag = encrypted_message.headers.auth_tag
|
60
|
+
|
61
|
+
# Currently the OpenSSL bindings do not raise an error if auth_tag is
|
62
|
+
# truncated, which would allow an attacker to easily forge it. See
|
63
|
+
# https://github.com/ruby/openssl/issues/63
|
64
|
+
raise ActiveRecord::Encryption::Errors::EncryptedContentIntegrity if auth_tag.nil? || auth_tag.bytes.length != 16
|
65
|
+
|
66
|
+
cipher = OpenSSL::Cipher.new(CIPHER_TYPE)
|
67
|
+
|
68
|
+
cipher.decrypt
|
69
|
+
cipher.key = @secret
|
70
|
+
cipher.iv = iv
|
71
|
+
|
72
|
+
cipher.auth_tag = auth_tag
|
73
|
+
cipher.auth_data = ""
|
74
|
+
|
75
|
+
decrypted_data = encrypted_data.empty? ? encrypted_data : cipher.update(encrypted_data)
|
76
|
+
decrypted_data << cipher.final
|
77
|
+
|
78
|
+
decrypted_data
|
79
|
+
rescue OpenSSL::Cipher::CipherError, TypeError, ArgumentError
|
80
|
+
raise ActiveRecord::Encryption::Errors::Decryption
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def generate_iv(cipher, clear_text)
|
85
|
+
if @deterministic
|
86
|
+
generate_deterministic_iv(clear_text)
|
87
|
+
else
|
88
|
+
cipher.random_iv
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def generate_deterministic_iv(clear_text)
|
93
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, @secret, clear_text)[0, ActiveRecord::Encryption.cipher.iv_length]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
# The algorithm used for encrypting and decrypting +Message+ objects.
|
6
|
+
#
|
7
|
+
# It uses AES-256-GCM. It will generate a random IV for non deterministic encryption (default)
|
8
|
+
# or derive an initialization vector from the encrypted content for deterministic encryption.
|
9
|
+
#
|
10
|
+
# See +Cipher::Aes256Gcm+.
|
11
|
+
class Cipher
|
12
|
+
DEFAULT_ENCODING = Encoding::UTF_8
|
13
|
+
|
14
|
+
# Encrypts the provided text and return an encrypted +Message+.
|
15
|
+
def encrypt(clean_text, key:, deterministic: false)
|
16
|
+
cipher_for(key, deterministic: deterministic).encrypt(clean_text).tap do |message|
|
17
|
+
message.headers.encoding = clean_text.encoding.name unless clean_text.encoding == DEFAULT_ENCODING
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Decrypt the provided +Message+.
|
22
|
+
#
|
23
|
+
# When +key+ is an Array, it will try all the keys raising a
|
24
|
+
# +ActiveRecord::Encryption::Errors::Decryption+ if none works.
|
25
|
+
def decrypt(encrypted_message, key:)
|
26
|
+
try_to_decrypt_with_each(encrypted_message, keys: Array(key)).tap do |decrypted_text|
|
27
|
+
decrypted_text.force_encoding(encrypted_message.headers.encoding || DEFAULT_ENCODING)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def key_length
|
32
|
+
Aes256Gcm.key_length
|
33
|
+
end
|
34
|
+
|
35
|
+
def iv_length
|
36
|
+
Aes256Gcm.iv_length
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def try_to_decrypt_with_each(encrypted_text, keys:)
|
41
|
+
keys.each.with_index do |key, index|
|
42
|
+
return cipher_for(key).decrypt(encrypted_text)
|
43
|
+
rescue ActiveRecord::Encryption::Errors::Decryption
|
44
|
+
raise if index == keys.length - 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def cipher_for(secret, deterministic: false)
|
49
|
+
Aes256Gcm.new(secret, deterministic: deterministic)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
# Container of configuration options
|
6
|
+
class Config
|
7
|
+
attr_accessor :primary_key, :deterministic_key, :store_key_references, :key_derivation_salt,
|
8
|
+
:support_unencrypted_data, :encrypt_fixtures, :validate_column_size, :add_to_filter_parameters,
|
9
|
+
:excluded_from_filter_parameters, :extend_queries, :previous_schemes, :forced_encoding_for_deterministic_encryption
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
set_defaults
|
13
|
+
end
|
14
|
+
|
15
|
+
# Configure previous encryption schemes.
|
16
|
+
#
|
17
|
+
# config.active_record.encryption.previous = [ { key_provider: MyOldKeyProvider.new } ]
|
18
|
+
def previous=(previous_schemes_properties)
|
19
|
+
previous_schemes_properties.each do |properties|
|
20
|
+
add_previous_scheme(**properties)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def set_defaults
|
26
|
+
self.store_key_references = false
|
27
|
+
self.support_unencrypted_data = false
|
28
|
+
self.encrypt_fixtures = false
|
29
|
+
self.validate_column_size = true
|
30
|
+
self.add_to_filter_parameters = true
|
31
|
+
self.excluded_from_filter_parameters = []
|
32
|
+
self.previous_schemes = []
|
33
|
+
self.forced_encoding_for_deterministic_encryption = Encoding::UTF_8
|
34
|
+
|
35
|
+
# TODO: Setting to false for now as the implementation is a bit experimental
|
36
|
+
self.extend_queries = false
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_previous_scheme(**properties)
|
40
|
+
previous_schemes << ActiveRecord::Encryption::Scheme.new(**properties)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|