activerecord 6.1.3.2 → 7.0.0.alpha2
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 +734 -1058
- data/MIT-LICENSE +1 -1
- 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 +35 -7
- data/lib/active_record/associations/association_scope.rb +1 -3
- data/lib/active_record/associations/belongs_to_association.rb +16 -6
- 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 +1 -1
- 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 +24 -25
- data/lib/active_record/associations/collection_proxy.rb +8 -3
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- 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/preloader/association.rb +161 -49
- data/lib/active_record/associations/preloader/batch.rb +51 -0
- data/lib/active_record/associations/preloader/branch.rb +147 -0
- data/lib/active_record/associations/preloader/through_association.rb +37 -11
- data/lib/active_record/associations/preloader.rb +46 -110
- data/lib/active_record/associations/singular_association.rb +8 -2
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +76 -81
- data/lib/active_record/asynchronous_queries_tracker.rb +57 -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 +41 -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 +66 -12
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
- data/lib/active_record/attribute_methods/write.rb +7 -10
- data/lib/active_record/attribute_methods.rb +6 -9
- data/lib/active_record/attributes.rb +24 -35
- data/lib/active_record/autosave_association.rb +3 -18
- data/lib/active_record/base.rb +19 -1
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/coders/yaml_column.rb +11 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +312 -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 +31 -558
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +45 -21
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
- data/lib/active_record/connection_adapters/abstract/quoting.rb +14 -7
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -18
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -9
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +60 -16
- data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
- data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -69
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +96 -81
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +6 -2
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +33 -21
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +3 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
- data/lib/active_record/connection_adapters/pool_config.rb +1 -3
- data/lib/active_record/connection_adapters/pool_manager.rb +5 -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 +28 -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 +6 -6
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +157 -100
- data/lib/active_record/connection_adapters/schema_cache.rb +35 -4
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +0 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +23 -17
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -30
- data/lib/active_record/connection_adapters.rb +8 -5
- data/lib/active_record/connection_handling.rb +20 -38
- data/lib/active_record/core.rb +129 -117
- data/lib/active_record/database_configurations/database_config.rb +12 -0
- data/lib/active_record/database_configurations/hash_config.rb +27 -1
- data/lib/active_record/database_configurations/url_config.rb +2 -2
- data/lib/active_record/database_configurations.rb +18 -9
- data/lib/active_record/delegated_type.rb +33 -11
- 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 +61 -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 +208 -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 +29 -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 +80 -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 +44 -46
- data/lib/active_record/errors.rb +66 -3
- data/lib/active_record/fixture_set/file.rb +15 -1
- data/lib/active_record/fixture_set/table_row.rb +40 -5
- data/lib/active_record/fixture_set/table_rows.rb +4 -4
- data/lib/active_record/fixtures.rb +16 -11
- 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 +39 -6
- data/lib/active_record/integration.rb +1 -1
- data/lib/active_record/internal_metadata.rb +3 -5
- data/lib/active_record/legacy_yaml_adapter.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +10 -9
- data/lib/active_record/log_subscriber.rb +6 -2
- data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
- data/lib/active_record/middleware/database_selector.rb +8 -3
- data/lib/active_record/migration/command_recorder.rb +4 -4
- data/lib/active_record/migration/compatibility.rb +83 -1
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration.rb +109 -79
- data/lib/active_record/model_schema.rb +46 -32
- data/lib/active_record/nested_attributes.rb +3 -3
- data/lib/active_record/no_touching.rb +2 -2
- data/lib/active_record/null_relation.rb +2 -6
- data/lib/active_record/persistence.rb +134 -45
- data/lib/active_record/query_cache.rb +2 -2
- data/lib/active_record/query_logs.rb +203 -0
- data/lib/active_record/querying.rb +15 -5
- data/lib/active_record/railtie.rb +117 -17
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +83 -58
- data/lib/active_record/readonly_attributes.rb +11 -0
- data/lib/active_record/reflection.rb +45 -44
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +42 -25
- data/lib/active_record/relation/delegation.rb +6 -6
- data/lib/active_record/relation/finder_methods.rb +32 -23
- 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 +233 -50
- data/lib/active_record/relation/record_fetch_warning.rb +2 -2
- data/lib/active_record/relation/spawn_methods.rb +2 -2
- data/lib/active_record/relation/where_clause.rb +22 -15
- data/lib/active_record/relation.rb +170 -87
- data/lib/active_record/result.rb +17 -2
- data/lib/active_record/runtime_registry.rb +2 -4
- data/lib/active_record/sanitization.rb +11 -7
- data/lib/active_record/schema_dumper.rb +3 -3
- data/lib/active_record/schema_migration.rb +0 -4
- data/lib/active_record/scoping/default.rb +62 -15
- data/lib/active_record/scoping/named.rb +3 -11
- data/lib/active_record/scoping.rb +40 -22
- data/lib/active_record/serialization.rb +1 -1
- data/lib/active_record/signed_id.rb +1 -1
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/tasks/database_tasks.rb +107 -23
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -11
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +45 -4
- data/lib/active_record/timestamp.rb +3 -4
- data/lib/active_record/transactions.rb +9 -14
- data/lib/active_record/translation.rb +2 -2
- 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 +1 -1
- data/lib/active_record/validations/numericality.rb +1 -1
- data/lib/active_record.rb +170 -2
- data/lib/arel/attributes/attribute.rb +0 -8
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/composite.rb +3 -3
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/crud.rb +18 -22
- data/lib/arel/delete_manager.rb +2 -4
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/casted.rb +1 -1
- data/lib/arel/nodes/delete_statement.rb +8 -13
- data/lib/arel/nodes/homogeneous_in.rb +4 -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 +3 -2
- data/lib/arel/predications.rb +3 -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 +2 -4
- data/lib/arel/visitors/dot.rb +80 -90
- data/lib/arel/visitors/mysql.rb +6 -1
- data/lib/arel/visitors/postgresql.rb +0 -10
- data/lib/arel/visitors/to_sql.rb +44 -3
- data/lib/arel.rb +1 -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
- metadata +55 -16
@@ -48,6 +48,18 @@ module ActiveRecord
|
|
48
48
|
raise NotImplementedError
|
49
49
|
end
|
50
50
|
|
51
|
+
def min_threads
|
52
|
+
raise NotImplementedError
|
53
|
+
end
|
54
|
+
|
55
|
+
def max_threads
|
56
|
+
raise NotImplementedError
|
57
|
+
end
|
58
|
+
|
59
|
+
def max_queue
|
60
|
+
raise NotImplementedError
|
61
|
+
end
|
62
|
+
|
51
63
|
def checkout_timeout
|
52
64
|
raise NotImplementedError
|
53
65
|
end
|
@@ -26,13 +26,14 @@ 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
35
|
def config
|
35
|
-
ActiveSupport::Deprecation.warn("DatabaseConfig#config will be removed in
|
36
|
+
ActiveSupport::Deprecation.warn("DatabaseConfig#config will be removed in 7.0.0 in favor of DatabaseConfigurations#configuration_hash which returns a hash with symbol keys")
|
36
37
|
configuration_hash.stringify_keys
|
37
38
|
end
|
38
39
|
|
@@ -54,6 +55,10 @@ module ActiveRecord
|
|
54
55
|
configuration_hash[:host]
|
55
56
|
end
|
56
57
|
|
58
|
+
def socket # :nodoc:
|
59
|
+
configuration_hash[:socket]
|
60
|
+
end
|
61
|
+
|
57
62
|
def database
|
58
63
|
configuration_hash[:database]
|
59
64
|
end
|
@@ -66,6 +71,18 @@ module ActiveRecord
|
|
66
71
|
(configuration_hash[:pool] || 5).to_i
|
67
72
|
end
|
68
73
|
|
74
|
+
def min_threads
|
75
|
+
(configuration_hash[:min_threads] || 0).to_i
|
76
|
+
end
|
77
|
+
|
78
|
+
def max_threads
|
79
|
+
(configuration_hash[:max_threads] || pool).to_i
|
80
|
+
end
|
81
|
+
|
82
|
+
def max_queue
|
83
|
+
max_threads * 4
|
84
|
+
end
|
85
|
+
|
69
86
|
def checkout_timeout
|
70
87
|
(configuration_hash[:checkout_timeout] || 5).to_f
|
71
88
|
end
|
@@ -91,6 +108,15 @@ module ActiveRecord
|
|
91
108
|
def schema_cache_path
|
92
109
|
configuration_hash[:schema_cache_path]
|
93
110
|
end
|
111
|
+
|
112
|
+
# Determines whether to dump the schema for a database.
|
113
|
+
def schema_dump
|
114
|
+
configuration_hash.fetch(:schema_dump, true)
|
115
|
+
end
|
116
|
+
|
117
|
+
def database_tasks? # :nodoc:
|
118
|
+
!replica? && !!configuration_hash.fetch(:database_tasks, true)
|
119
|
+
end
|
94
120
|
end
|
95
121
|
end
|
96
122
|
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
|
@@ -20,7 +20,7 @@ module ActiveRecord
|
|
20
20
|
end
|
21
21
|
|
22
22
|
# Collects the configs for the environment and optionally the specification
|
23
|
-
# name passed in. To include replica configurations pass <tt>
|
23
|
+
# name passed in. To include replica configurations pass <tt>include_hidden: true</tt>.
|
24
24
|
#
|
25
25
|
# If a name is provided a single DatabaseConfig object will be
|
26
26
|
# returned, otherwise an array of DatabaseConfig objects will be
|
@@ -33,22 +33,31 @@ module ActiveRecord
|
|
33
33
|
# * <tt>name:</tt> The db config name (i.e. primary, animals, etc.). Defaults
|
34
34
|
# to +nil+. If no +env_name+ is specified the config for the default env and the
|
35
35
|
# passed +name+ will be returned.
|
36
|
-
# * <tt>include_replicas:</tt> Determines whether to include replicas in
|
36
|
+
# * <tt>include_replicas:</tt> Deprecated. Determines whether to include replicas in
|
37
37
|
# the returned list. Most of the time we're only iterating over the write
|
38
38
|
# connection (i.e. migrations don't need to run for the write and read connection).
|
39
39
|
# Defaults to +false+.
|
40
|
-
|
40
|
+
# * <tt>include_hidden:</tte Determines whether to include replicas and configurations
|
41
|
+
# hidden by +database_tasks: false+ in the returned list. Most of the time we're only
|
42
|
+
# iterating over the primary connections (i.e. migrations don't need to run for the
|
43
|
+
# write and read connection). Defaults to +false+.
|
44
|
+
def configs_for(env_name: nil, spec_name: nil, name: nil, include_replicas: false, include_hidden: false)
|
41
45
|
if spec_name
|
42
46
|
name = spec_name
|
43
|
-
ActiveSupport::Deprecation.warn("The kwarg `spec_name` is deprecated in favor of `name`. `spec_name` will be removed in Rails
|
47
|
+
ActiveSupport::Deprecation.warn("The kwarg `spec_name` is deprecated in favor of `name`. `spec_name` will be removed in Rails 7.0")
|
48
|
+
end
|
49
|
+
|
50
|
+
if include_replicas
|
51
|
+
include_hidden = include_replicas
|
52
|
+
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.")
|
44
53
|
end
|
45
54
|
|
46
55
|
env_name ||= default_env if name
|
47
56
|
configs = env_with_configs(env_name)
|
48
57
|
|
49
|
-
unless
|
58
|
+
unless include_hidden
|
50
59
|
configs = configs.select do |db_config|
|
51
|
-
|
60
|
+
db_config.database_tasks?
|
52
61
|
end
|
53
62
|
end
|
54
63
|
|
@@ -166,7 +175,7 @@ module ActiveRecord
|
|
166
175
|
return configs if configs.is_a?(Array)
|
167
176
|
|
168
177
|
db_configs = configs.flat_map do |env_name, config|
|
169
|
-
if config.is_a?(Hash) && config.all?
|
178
|
+
if config.is_a?(Hash) && config.values.all?(Hash)
|
170
179
|
walk_configs(env_name.to_s, config)
|
171
180
|
else
|
172
181
|
build_db_config_from_raw_config(env_name.to_s, "primary", config)
|
@@ -193,7 +202,7 @@ module ActiveRecord
|
|
193
202
|
raise AdapterNotSpecified, <<~MSG
|
194
203
|
The `#{name}` database is not configured for the `#{default_env}` environment.
|
195
204
|
|
196
|
-
Available
|
205
|
+
Available database configurations are:
|
197
206
|
|
198
207
|
#{build_configuration_sentence}
|
199
208
|
MSG
|
@@ -201,7 +210,7 @@ module ActiveRecord
|
|
201
210
|
end
|
202
211
|
|
203
212
|
def build_configuration_sentence
|
204
|
-
configs = configs_for(
|
213
|
+
configs = configs_for(include_hidden: true)
|
205
214
|
|
206
215
|
configs.group_by(&:env_name).map do |env, config|
|
207
216
|
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
|
@@ -156,8 +157,6 @@ module ActiveRecord
|
|
156
157
|
# Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
|
157
158
|
# Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
|
158
159
|
#
|
159
|
-
# The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
|
160
|
-
#
|
161
160
|
# You can also declare namespaced types:
|
162
161
|
#
|
163
162
|
# class Entry < ApplicationRecord
|
@@ -167,15 +166,38 @@ module ActiveRecord
|
|
167
166
|
# Entry.access_notice_messages
|
168
167
|
# entry.access_notice_message
|
169
168
|
# entry.access_notice_message?
|
169
|
+
#
|
170
|
+
# === Options
|
171
|
+
#
|
172
|
+
# The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
|
173
|
+
# The following options can be included to specialize the behavior of the delegated type convenience methods.
|
174
|
+
#
|
175
|
+
# [:foreign_key]
|
176
|
+
# Specify the foreign key used for the convenience methods. By default this is guessed to be the passed
|
177
|
+
# +role+ with an "_id" suffix. So a class that defines a
|
178
|
+
# <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_id" as
|
179
|
+
# the default <tt>:foreign_key</tt>.
|
180
|
+
# [:primary_key]
|
181
|
+
# Specify the method that returns the primary key of associated object used for the convenience methods.
|
182
|
+
# By default this is +id+.
|
183
|
+
#
|
184
|
+
# Option examples:
|
185
|
+
# class Entry < ApplicationRecord
|
186
|
+
# delegated_type :entryable, types: %w[ Message Comment ], primary_key: :uuid, foreign_key: :entryable_uuid
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# Entry#message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
|
190
|
+
# Entry#comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
|
170
191
|
def delegated_type(role, types:, **options)
|
171
192
|
belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
|
172
|
-
define_delegated_type_methods role, types: types
|
193
|
+
define_delegated_type_methods role, types: types, options: options
|
173
194
|
end
|
174
195
|
|
175
196
|
private
|
176
|
-
def define_delegated_type_methods(role, types:)
|
197
|
+
def define_delegated_type_methods(role, types:, options:)
|
198
|
+
primary_key = options[:primary_key] || "id"
|
177
199
|
role_type = "#{role}_type"
|
178
|
-
role_id = "#{role}_id"
|
200
|
+
role_id = options[:foreign_key] || "#{role}_id"
|
179
201
|
|
180
202
|
define_method "#{role}_class" do
|
181
203
|
public_send("#{role}_type").constantize
|
@@ -186,7 +208,7 @@ module ActiveRecord
|
|
186
208
|
end
|
187
209
|
|
188
210
|
types.each do |type|
|
189
|
-
scope_name = type.tableize.
|
211
|
+
scope_name = type.tableize.tr("/", "_")
|
190
212
|
singular = scope_name.singularize
|
191
213
|
query = "#{singular}?"
|
192
214
|
|
@@ -200,7 +222,7 @@ module ActiveRecord
|
|
200
222
|
public_send(role) if public_send(query)
|
201
223
|
end
|
202
224
|
|
203
|
-
define_method "#{singular}
|
225
|
+
define_method "#{singular}_#{primary_key}" do
|
204
226
|
public_send(role_id) if public_send(query)
|
205
227
|
end
|
206
228
|
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
|