activerecord 6.1.7 → 7.1.5
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 +2030 -1020
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -18
- data/lib/active_record/aggregations.rb +17 -14
- data/lib/active_record/association_relation.rb +1 -11
- data/lib/active_record/associations/association.rb +51 -19
- data/lib/active_record/associations/association_scope.rb +17 -12
- data/lib/active_record/associations/belongs_to_association.rb +28 -9
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
- data/lib/active_record/associations/builder/association.rb +11 -5
- data/lib/active_record/associations/builder/belongs_to.rb +40 -14
- data/lib/active_record/associations/builder/collection_association.rb +10 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- 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 +6 -2
- data/lib/active_record/associations/collection_association.rb +39 -35
- data/lib/active_record/associations/collection_proxy.rb +30 -15
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +28 -18
- data/lib/active_record/associations/has_many_through_association.rb +12 -7
- data/lib/active_record/associations/has_one_association.rb +20 -10
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +28 -20
- data/lib/active_record/associations/preloader/association.rb +210 -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 +50 -14
- data/lib/active_record/associations/preloader.rb +50 -121
- data/lib/active_record/associations/singular_association.rb +9 -3
- data/lib/active_record/associations/through_association.rb +25 -14
- data/lib/active_record/associations.rb +446 -306
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +1 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
- data/lib/active_record/attribute_methods/dirty.rb +73 -22
- data/lib/active_record/attribute_methods/primary_key.rb +78 -26
- data/lib/active_record/attribute_methods/query.rb +31 -19
- data/lib/active_record/attribute_methods/read.rb +27 -12
- data/lib/active_record/attribute_methods/serialization.rb +194 -37
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
- data/lib/active_record/attribute_methods/write.rb +12 -15
- data/lib/active_record/attribute_methods.rb +161 -40
- data/lib/active_record/attributes.rb +27 -38
- data/lib/active_record/autosave_association.rb +65 -31
- data/lib/active_record/base.rb +25 -2
- data/lib/active_record/callbacks.rb +18 -34
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -46
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +113 -597
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -27
- data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +367 -141
- data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
- data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -150
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +317 -164
- data/lib/active_record/connection_adapters/column.rb +13 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
- data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +39 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +112 -55
- data/lib/active_record/connection_adapters/pool_config.rb +20 -11
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -52
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- 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/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
- 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 +89 -56
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +397 -75
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +508 -246
- data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +296 -104
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
- data/lib/active_record/connection_adapters.rb +9 -6
- data/lib/active_record/connection_handling.rb +108 -137
- data/lib/active_record/core.rb +242 -233
- data/lib/active_record/counter_cache.rb +52 -27
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -2
- data/lib/active_record/database_configurations/database_config.rb +21 -12
- data/lib/active_record/database_configurations/hash_config.rb +88 -16
- data/lib/active_record/database_configurations/url_config.rb +18 -12
- data/lib/active_record/database_configurations.rb +95 -59
- data/lib/active_record/delegated_type.rb +66 -20
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +4 -2
- 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/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +68 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +230 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +155 -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 +157 -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 +53 -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 +92 -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 +100 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +154 -63
- data/lib/active_record/errors.rb +172 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +15 -1
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +70 -14
- data/lib/active_record/fixture_set/table_rows.rb +4 -4
- data/lib/active_record/fixtures.rb +147 -86
- data/lib/active_record/future_result.rb +174 -0
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +81 -29
- data/lib/active_record/insert_all.rb +135 -22
- data/lib/active_record/integration.rb +11 -10
- data/lib/active_record/internal_metadata.rb +119 -33
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/optimistic.rb +37 -22
- data/lib/active_record/locking/pessimistic.rb +15 -6
- data/lib/active_record/log_subscriber.rb +52 -19
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
- data/lib/active_record/middleware/database_selector.rb +23 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +112 -14
- data/lib/active_record/migration/compatibility.rb +233 -46
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +361 -173
- data/lib/active_record/model_schema.rb +125 -101
- data/lib/active_record/nested_attributes.rb +50 -20
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +409 -88
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +4 -22
- data/lib/active_record/query_logs.rb +174 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +29 -6
- data/lib/active_record/railtie.rb +220 -44
- data/lib/active_record/railties/controller_runtime.rb +15 -10
- data/lib/active_record/railties/databases.rake +188 -252
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +41 -3
- data/lib/active_record/reflection.rb +248 -81
- data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
- data/lib/active_record/relation/batches.rb +192 -63
- data/lib/active_record/relation/calculations.rb +246 -90
- data/lib/active_record/relation/delegation.rb +28 -14
- data/lib/active_record/relation/finder_methods.rb +108 -51
- data/lib/active_record/relation/merger.rb +22 -13
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +27 -20
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +670 -129
- data/lib/active_record/relation/record_fetch_warning.rb +7 -9
- data/lib/active_record/relation/spawn_methods.rb +20 -3
- data/lib/active_record/relation/where_clause.rb +10 -19
- data/lib/active_record/relation.rb +287 -120
- data/lib/active_record/result.rb +37 -11
- data/lib/active_record/runtime_registry.rb +32 -13
- data/lib/active_record/sanitization.rb +65 -20
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +73 -24
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +72 -15
- data/lib/active_record/scoping/named.rb +5 -13
- data/lib/active_record/scoping.rb +65 -34
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +6 -1
- data/lib/active_record/signed_id.rb +10 -8
- data/lib/active_record/store.rb +10 -10
- data/lib/active_record/suppressor.rb +13 -15
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +251 -140
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
- data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +117 -96
- data/lib/active_record/timestamp.rb +32 -19
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +48 -27
- data/lib/active_record/translation.rb +3 -3
- data/lib/active_record/type/adapter_specific_registry.rb +32 -14
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +9 -5
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type/type_map.rb +17 -20
- data/lib/active_record/type.rb +1 -2
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +4 -4
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +51 -6
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +335 -32
- 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/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/and.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/casted.rb +1 -1
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +12 -13
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes/update_statement.rb +8 -3
- data/lib/arel/nodes.rb +5 -0
- data/lib/arel/predications.rb +13 -3
- data/lib/arel/select_manager.rb +10 -4
- data/lib/arel/table.rb +9 -6
- data/lib/arel/tree_manager.rb +5 -13
- data/lib/arel/update_manager.rb +18 -4
- data/lib/arel/visitors/dot.rb +80 -90
- data/lib/arel/visitors/mysql.rb +16 -3
- data/lib/arel/visitors/postgresql.rb +0 -10
- data/lib/arel/visitors/to_sql.rb +141 -20
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +18 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- 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 +96 -16
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -67
@@ -3,7 +3,7 @@
|
|
3
3
|
require "active_support/core_ext/string/inquiry"
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
|
-
#
|
6
|
+
# = Delegated types
|
7
7
|
#
|
8
8
|
# Class hierarchies can map to relational database tables in many ways. Active Record, for example, offers
|
9
9
|
# purely abstract classes, where the superclass doesn't persist any attributes, and single-table inheritance,
|
@@ -36,7 +36,7 @@ module ActiveRecord
|
|
36
36
|
#
|
37
37
|
# Let's look at that entry/message/comment example using delegated types:
|
38
38
|
#
|
39
|
-
# # Schema: entries[ id, account_id, creator_id,
|
39
|
+
# # Schema: entries[ id, account_id, creator_id, entryable_type, entryable_id, created_at, updated_at ]
|
40
40
|
# class Entry < ApplicationRecord
|
41
41
|
# belongs_to :account
|
42
42
|
# belongs_to :creator
|
@@ -51,13 +51,12 @@ module ActiveRecord
|
|
51
51
|
# end
|
52
52
|
# end
|
53
53
|
#
|
54
|
-
# # Schema: messages[ id, subject ]
|
54
|
+
# # Schema: messages[ id, subject, body, created_at, updated_at ]
|
55
55
|
# class Message < ApplicationRecord
|
56
56
|
# include Entryable
|
57
|
-
# has_rich_text :content
|
58
57
|
# end
|
59
58
|
#
|
60
|
-
# # Schema: comments[ id, content ]
|
59
|
+
# # Schema: comments[ id, content, created_at, updated_at ]
|
61
60
|
# class Comment < ApplicationRecord
|
62
61
|
# include Entryable
|
63
62
|
# end
|
@@ -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
|
@@ -101,14 +102,14 @@ module ActiveRecord
|
|
101
102
|
# You create a new record that uses delegated typing by creating the delegator and delegatee at the same time,
|
102
103
|
# like so:
|
103
104
|
#
|
104
|
-
# Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user
|
105
|
+
# Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user, account: Current.account
|
105
106
|
#
|
106
107
|
# If you need more complicated composition, or you need to perform dependent validation, you should build a factory
|
107
108
|
# method or class to take care of the complicated needs. This could be as simple as:
|
108
109
|
#
|
109
110
|
# class Entry < ApplicationRecord
|
110
|
-
# def self.create_with_comment(content, creator: Current.user)
|
111
|
-
# create! entryable: Comment.new(content: content), creator: creator
|
111
|
+
# def self.create_with_comment(content, creator: Current.user, account: Current.account)
|
112
|
+
# create! entryable: Comment.new(content: content), creator: creator, account: account
|
112
113
|
# end
|
113
114
|
# end
|
114
115
|
#
|
@@ -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,26 +181,58 @@ 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
|
+
# [:foreign_type]
|
196
|
+
# Specify the column used to store the associated object's type. By default this is inferred to be the passed
|
197
|
+
# +role+ with a "_type" suffix. A class that defines a
|
198
|
+
# <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_type" as
|
199
|
+
# the default <tt>:foreign_type</tt>.
|
200
|
+
# [:primary_key]
|
201
|
+
# Specify the method that returns the primary key of associated object used for the convenience methods.
|
202
|
+
# By default this is +id+.
|
203
|
+
#
|
204
|
+
# Option examples:
|
205
|
+
# class Entry < ApplicationRecord
|
206
|
+
# delegated_type :entryable, types: %w[ Message Comment ], primary_key: :uuid, foreign_key: :entryable_uuid
|
207
|
+
# end
|
208
|
+
#
|
209
|
+
# Entry#message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
|
210
|
+
# Entry#comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
|
170
211
|
def delegated_type(role, types:, **options)
|
171
212
|
belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
|
172
|
-
define_delegated_type_methods role, types: types
|
213
|
+
define_delegated_type_methods role, types: types, options: options
|
173
214
|
end
|
174
215
|
|
175
216
|
private
|
176
|
-
def define_delegated_type_methods(role, types:)
|
177
|
-
|
178
|
-
|
217
|
+
def define_delegated_type_methods(role, types:, options:)
|
218
|
+
primary_key = options[:primary_key] || "id"
|
219
|
+
role_type = options[:foreign_type] || "#{role}_type"
|
220
|
+
role_id = options[:foreign_key] || "#{role}_id"
|
179
221
|
|
180
222
|
define_method "#{role}_class" do
|
181
|
-
public_send(
|
223
|
+
public_send(role_type).constantize
|
182
224
|
end
|
183
225
|
|
184
226
|
define_method "#{role}_name" do
|
185
227
|
public_send("#{role}_class").model_name.singular.inquiry
|
186
228
|
end
|
187
229
|
|
230
|
+
define_method "build_#{role}" do |*params|
|
231
|
+
public_send("#{role}=", public_send("#{role}_class").new(*params))
|
232
|
+
end
|
233
|
+
|
188
234
|
types.each do |type|
|
189
|
-
scope_name = type.tableize.
|
235
|
+
scope_name = type.tableize.tr("/", "_")
|
190
236
|
singular = scope_name.singularize
|
191
237
|
query = "#{singular}?"
|
192
238
|
|
@@ -200,7 +246,7 @@ module ActiveRecord
|
|
200
246
|
public_send(role) if public_send(query)
|
201
247
|
end
|
202
248
|
|
203
|
-
define_method "#{singular}
|
249
|
+
define_method "#{singular}_#{primary_key}" do
|
204
250
|
public_send(role_id) if public_send(query)
|
205
251
|
end
|
206
252
|
end
|
@@ -4,9 +4,11 @@ module ActiveRecord
|
|
4
4
|
class DestroyAssociationAsyncError < StandardError
|
5
5
|
end
|
6
6
|
|
7
|
+
# = Active Record Destroy Association Async Job
|
8
|
+
#
|
7
9
|
# Job to destroy the records associated with a destroyed record in background.
|
8
10
|
class DestroyAssociationAsyncJob < ActiveJob::Base
|
9
|
-
queue_as { ActiveRecord
|
11
|
+
queue_as { ActiveRecord.queues[:destroy] }
|
10
12
|
|
11
13
|
discard_on ActiveJob::DeserializationError
|
12
14
|
|
@@ -17,7 +19,7 @@ module ActiveRecord
|
|
17
19
|
)
|
18
20
|
association_model = association_class.constantize
|
19
21
|
owner_class = owner_model_name.constantize
|
20
|
-
owner = owner_class.find_by(owner_class.primary_key
|
22
|
+
owner = owner_class.find_by(owner_class.primary_key => [owner_id])
|
21
23
|
|
22
24
|
if !owner_destroyed?(owner, ensuring_owner_was_method)
|
23
25
|
raise DestroyAssociationAsyncError, "owner record not destroyed"
|
@@ -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] }
|
34
|
+
records.compact!
|
35
|
+
|
36
|
+
@records = records
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
class AutoFilteredParameters
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
@attributes_by_class = Concurrent::Map.new
|
9
|
+
@collecting = true
|
10
|
+
|
11
|
+
install_collecting_hook
|
12
|
+
end
|
13
|
+
|
14
|
+
def enable
|
15
|
+
apply_collected_attributes
|
16
|
+
@collecting = false
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
attr_reader :app
|
21
|
+
|
22
|
+
def install_collecting_hook
|
23
|
+
ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, attribute|
|
24
|
+
attribute_was_declared(klass, attribute)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def attribute_was_declared(klass, attribute)
|
29
|
+
if collecting?
|
30
|
+
collect_for_later(klass, attribute)
|
31
|
+
else
|
32
|
+
apply_filter(klass, attribute)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def apply_collected_attributes
|
37
|
+
@attributes_by_class.each do |klass, attributes|
|
38
|
+
attributes.each do |attribute|
|
39
|
+
apply_filter(klass, attribute)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def collecting?
|
45
|
+
@collecting
|
46
|
+
end
|
47
|
+
|
48
|
+
def collect_for_later(klass, attribute)
|
49
|
+
@attributes_by_class[klass] ||= Concurrent::Array.new
|
50
|
+
@attributes_by_class[klass] << attribute
|
51
|
+
end
|
52
|
+
|
53
|
+
def apply_filter(klass, attribute)
|
54
|
+
filter = [("#{klass.model_name.element}" if klass.name), attribute.to_s].compact.join(".")
|
55
|
+
unless excluded_from_filter_parameters?(filter)
|
56
|
+
app.config.filter_parameters << filter unless app.config.filter_parameters.include?(filter)
|
57
|
+
klass.filter_attributes += [ attribute ]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
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
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Encryption
|
7
|
+
class Cipher
|
8
|
+
# A 256-GCM cipher.
|
9
|
+
#
|
10
|
+
# By default it will use random initialization vectors. For deterministic encryption, it will use a SHA-256 hash of
|
11
|
+
# the text to encrypt and the secret.
|
12
|
+
#
|
13
|
+
# See +Encryptor+
|
14
|
+
class Aes256Gcm
|
15
|
+
CIPHER_TYPE = "aes-256-gcm"
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def key_length
|
19
|
+
OpenSSL::Cipher.new(CIPHER_TYPE).key_len
|
20
|
+
end
|
21
|
+
|
22
|
+
def iv_length
|
23
|
+
OpenSSL::Cipher.new(CIPHER_TYPE).iv_len
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# When iv not provided, it will generate a random iv on each encryption operation (default and
|
28
|
+
# recommended operation)
|
29
|
+
def initialize(secret, deterministic: false)
|
30
|
+
@secret = secret
|
31
|
+
@deterministic = deterministic
|
32
|
+
end
|
33
|
+
|
34
|
+
def encrypt(clear_text)
|
35
|
+
# This code is extracted from +ActiveSupport::MessageEncryptor+. Not using it directly because we want to control
|
36
|
+
# the message format and only serialize things once at the +ActiveRecord::Encryption::Message+ level. Also, this
|
37
|
+
# cipher is prepared to deal with deterministic/non deterministic encryption modes.
|
38
|
+
|
39
|
+
cipher = OpenSSL::Cipher.new(CIPHER_TYPE)
|
40
|
+
cipher.encrypt
|
41
|
+
cipher.key = @secret
|
42
|
+
|
43
|
+
iv = generate_iv(cipher, clear_text)
|
44
|
+
cipher.iv = iv
|
45
|
+
|
46
|
+
encrypted_data = clear_text.empty? ? clear_text.dup : cipher.update(clear_text)
|
47
|
+
encrypted_data << cipher.final
|
48
|
+
|
49
|
+
ActiveRecord::Encryption::Message.new(payload: encrypted_data).tap do |message|
|
50
|
+
message.headers.iv = iv
|
51
|
+
message.headers.auth_tag = cipher.auth_tag
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def decrypt(encrypted_message)
|
56
|
+
encrypted_data = encrypted_message.payload
|
57
|
+
iv = encrypted_message.headers.iv
|
58
|
+
auth_tag = encrypted_message.headers.auth_tag
|
59
|
+
|
60
|
+
# Currently the OpenSSL bindings do not raise an error if auth_tag is
|
61
|
+
# truncated, which would allow an attacker to easily forge it. See
|
62
|
+
# https://github.com/ruby/openssl/issues/63
|
63
|
+
raise ActiveRecord::Encryption::Errors::EncryptedContentIntegrity if auth_tag.nil? || auth_tag.bytes.length != 16
|
64
|
+
|
65
|
+
cipher = OpenSSL::Cipher.new(CIPHER_TYPE)
|
66
|
+
|
67
|
+
cipher.decrypt
|
68
|
+
cipher.key = @secret
|
69
|
+
cipher.iv = iv
|
70
|
+
|
71
|
+
cipher.auth_tag = auth_tag
|
72
|
+
cipher.auth_data = ""
|
73
|
+
|
74
|
+
decrypted_data = encrypted_data.empty? ? encrypted_data : cipher.update(encrypted_data)
|
75
|
+
decrypted_data << cipher.final
|
76
|
+
|
77
|
+
decrypted_data
|
78
|
+
rescue OpenSSL::Cipher::CipherError, TypeError, ArgumentError
|
79
|
+
raise ActiveRecord::Encryption::Errors::Decryption
|
80
|
+
end
|
81
|
+
|
82
|
+
def inspect # :nodoc:
|
83
|
+
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def generate_iv(cipher, clear_text)
|
88
|
+
if @deterministic
|
89
|
+
generate_deterministic_iv(clear_text)
|
90
|
+
else
|
91
|
+
cipher.random_iv
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def generate_deterministic_iv(clear_text)
|
96
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, @secret, clear_text)[0, ActiveRecord::Encryption.cipher.iv_length]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
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,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Encryption
|
7
|
+
# Container of configuration options
|
8
|
+
class Config
|
9
|
+
attr_accessor :primary_key, :deterministic_key, :store_key_references, :key_derivation_salt, :hash_digest_class,
|
10
|
+
:support_unencrypted_data, :encrypt_fixtures, :validate_column_size, :add_to_filter_parameters,
|
11
|
+
:excluded_from_filter_parameters, :extend_queries, :previous_schemes, :forced_encoding_for_deterministic_encryption
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
set_defaults
|
15
|
+
end
|
16
|
+
|
17
|
+
# Configure previous encryption schemes.
|
18
|
+
#
|
19
|
+
# config.active_record.encryption.previous = [ { key_provider: MyOldKeyProvider.new } ]
|
20
|
+
def previous=(previous_schemes_properties)
|
21
|
+
previous_schemes_properties.each do |properties|
|
22
|
+
add_previous_scheme(**properties)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def support_sha1_for_non_deterministic_encryption=(value)
|
27
|
+
if value && has_primary_key?
|
28
|
+
sha1_key_generator = ActiveRecord::Encryption::KeyGenerator.new(hash_digest_class: OpenSSL::Digest::SHA1)
|
29
|
+
sha1_key_provider = ActiveRecord::Encryption::DerivedSecretKeyProvider.new(primary_key, key_generator: sha1_key_generator)
|
30
|
+
add_previous_scheme key_provider: sha1_key_provider
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
%w(key_derivation_salt primary_key deterministic_key).each do |key|
|
35
|
+
silence_redefinition_of_method "has_#{key}?"
|
36
|
+
define_method("has_#{key}?") do
|
37
|
+
instance_variable_get(:"@#{key}").presence
|
38
|
+
end
|
39
|
+
|
40
|
+
silence_redefinition_of_method key
|
41
|
+
define_method(key) do
|
42
|
+
public_send("has_#{key}?") or
|
43
|
+
raise Errors::Configuration, "Missing Active Record encryption credential: active_record_encryption.#{key}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def set_defaults
|
49
|
+
self.store_key_references = false
|
50
|
+
self.support_unencrypted_data = false
|
51
|
+
self.encrypt_fixtures = false
|
52
|
+
self.validate_column_size = true
|
53
|
+
self.add_to_filter_parameters = true
|
54
|
+
self.excluded_from_filter_parameters = []
|
55
|
+
self.previous_schemes = []
|
56
|
+
self.forced_encoding_for_deterministic_encryption = Encoding::UTF_8
|
57
|
+
self.hash_digest_class = OpenSSL::Digest::SHA1
|
58
|
+
|
59
|
+
# TODO: Setting to false for now as the implementation is a bit experimental
|
60
|
+
self.extend_queries = false
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_previous_scheme(**properties)
|
64
|
+
previous_schemes << ActiveRecord::Encryption::Scheme.new(**properties)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
# Configuration API for ActiveRecord::Encryption
|
6
|
+
module Configurable
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
mattr_reader :config, default: Config.new
|
11
|
+
mattr_accessor :encrypted_attribute_declaration_listeners
|
12
|
+
end
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
# Expose getters for context properties
|
16
|
+
Context::PROPERTIES.each do |name|
|
17
|
+
delegate name, to: :context
|
18
|
+
end
|
19
|
+
|
20
|
+
def configure(primary_key: nil, deterministic_key: nil, key_derivation_salt: nil, **properties) # :nodoc:
|
21
|
+
config.primary_key = primary_key
|
22
|
+
config.deterministic_key = deterministic_key
|
23
|
+
config.key_derivation_salt = key_derivation_salt
|
24
|
+
|
25
|
+
# Set the default for this property here instead of in +Config#set_defaults+ as this needs
|
26
|
+
# to happen *after* the keys have been set.
|
27
|
+
properties[:support_sha1_for_non_deterministic_encryption] = true if properties[:support_sha1_for_non_deterministic_encryption].nil?
|
28
|
+
|
29
|
+
properties.each do |name, value|
|
30
|
+
ActiveRecord::Encryption.config.send "#{name}=", value if ActiveRecord::Encryption.config.respond_to?("#{name}=")
|
31
|
+
end
|
32
|
+
|
33
|
+
ActiveRecord::Encryption.reset_default_context
|
34
|
+
|
35
|
+
properties.each do |name, value|
|
36
|
+
ActiveRecord::Encryption.context.send "#{name}=", value if ActiveRecord::Encryption.context.respond_to?("#{name}=")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Register callback to be invoked when an encrypted attribute is declared.
|
41
|
+
#
|
42
|
+
# === Example
|
43
|
+
#
|
44
|
+
# ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, attribute_name|
|
45
|
+
# ...
|
46
|
+
# end
|
47
|
+
def on_encrypted_attribute_declared(&block)
|
48
|
+
self.encrypted_attribute_declaration_listeners ||= Concurrent::Array.new
|
49
|
+
self.encrypted_attribute_declaration_listeners << block
|
50
|
+
end
|
51
|
+
|
52
|
+
def encrypted_attribute_was_declared(klass, name) # :nodoc:
|
53
|
+
self.encrypted_attribute_declaration_listeners&.each do |block|
|
54
|
+
block.call(klass, name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Encryption
|
5
|
+
# An encryption context configures the different entities used to perform encryption:
|
6
|
+
#
|
7
|
+
# * A key provider
|
8
|
+
# * A key generator
|
9
|
+
# * An encryptor, the facade to encrypt data
|
10
|
+
# * A cipher, the encryption algorithm
|
11
|
+
# * A message serializer
|
12
|
+
class Context
|
13
|
+
PROPERTIES = %i[ key_provider key_generator cipher message_serializer encryptor frozen_encryption ]
|
14
|
+
|
15
|
+
attr_accessor(*PROPERTIES)
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
set_defaults
|
19
|
+
end
|
20
|
+
|
21
|
+
alias frozen_encryption? frozen_encryption
|
22
|
+
|
23
|
+
silence_redefinition_of_method :key_provider
|
24
|
+
def key_provider
|
25
|
+
@key_provider ||= build_default_key_provider
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def set_defaults
|
30
|
+
self.frozen_encryption = false
|
31
|
+
self.key_generator = ActiveRecord::Encryption::KeyGenerator.new
|
32
|
+
self.cipher = ActiveRecord::Encryption::Cipher.new
|
33
|
+
self.encryptor = ActiveRecord::Encryption::Encryptor.new
|
34
|
+
self.message_serializer = ActiveRecord::Encryption::MessageSerializer.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_default_key_provider
|
38
|
+
ActiveRecord::Encryption::DerivedSecretKeyProvider.new(ActiveRecord::Encryption.config.primary_key)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|