activerecord 7.1.3.4 → 7.2.2.1
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 +652 -2032
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +15 -8
- data/lib/active_record/associations/belongs_to_association.rb +18 -11
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +11 -5
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +3 -3
- data/lib/active_record/associations/has_many_through_association.rb +7 -1
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -12
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +62 -289
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +2 -2
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +4 -16
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +89 -58
- data/lib/active_record/attributes.rb +61 -47
- data/lib/active_record/autosave_association.rb +17 -31
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -58
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +190 -75
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +23 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -19
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +18 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -77
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +93 -40
- data/lib/active_record/counter_cache.rb +23 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +19 -4
- data/lib/active_record/database_configurations/hash_config.rb +44 -36
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +30 -6
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +3 -3
- data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +8 -4
- data/lib/active_record/encryption.rb +2 -0
- data/lib/active_record/enum.rb +19 -2
- data/lib/active_record/errors.rb +46 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +17 -4
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +4 -1
- data/lib/active_record/message_pack.rb +2 -2
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +11 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +85 -76
- data/lib/active_record/model_schema.rb +38 -70
- data/lib/active_record/nested_attributes.rb +24 -5
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +32 -354
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/query_logs_formatter.rb +1 -1
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +50 -68
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +42 -45
- data/lib/active_record/reflection.rb +106 -38
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +96 -63
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +245 -65
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +500 -66
- data/lib/active_record/result.rb +32 -45
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/scoping/named.rb +1 -0
- data/lib/active_record/signed_id.rb +20 -1
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +98 -48
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +87 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +5 -3
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +70 -14
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +150 -41
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +31 -17
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +21 -15
@@ -7,6 +7,7 @@ module ActiveRecord
|
|
7
7
|
|
8
8
|
included do
|
9
9
|
class_attribute :_counter_cache_columns, instance_accessor: false, default: []
|
10
|
+
class_attribute :counter_cached_association_names, instance_writer: false, default: []
|
10
11
|
end
|
11
12
|
|
12
13
|
module ClassMethods
|
@@ -65,7 +66,7 @@ module ActiveRecord
|
|
65
66
|
updates.merge!(touch_updates)
|
66
67
|
end
|
67
68
|
|
68
|
-
unscoped.where(primary_key => object.id).update_all(updates) if updates.any?
|
69
|
+
unscoped.where(primary_key => [object.id]).update_all(updates) if updates.any?
|
69
70
|
|
70
71
|
true
|
71
72
|
end
|
@@ -112,6 +113,7 @@ module ActiveRecord
|
|
112
113
|
# # `updated_at` = '2016-10-13T09:59:23-05:00'
|
113
114
|
# # WHERE id IN (10, 15)
|
114
115
|
def update_counters(id, counters)
|
116
|
+
id = [id] if composite_primary_key? && id.is_a?(Array) && !id[0].is_a?(Array)
|
115
117
|
unscoped.where!(primary_key => id).update_counters(counters)
|
116
118
|
end
|
117
119
|
|
@@ -180,14 +182,26 @@ module ActiveRecord
|
|
180
182
|
def counter_cache_column?(name) # :nodoc:
|
181
183
|
_counter_cache_columns.include?(name)
|
182
184
|
end
|
185
|
+
|
186
|
+
def load_schema! # :nodoc:
|
187
|
+
super
|
188
|
+
|
189
|
+
association_names = _reflections.filter_map do |name, reflection|
|
190
|
+
next unless reflection.belongs_to? && reflection.counter_cache_column
|
191
|
+
|
192
|
+
name.to_sym
|
193
|
+
end
|
194
|
+
|
195
|
+
self.counter_cached_association_names |= association_names
|
196
|
+
end
|
183
197
|
end
|
184
198
|
|
185
199
|
private
|
186
200
|
def _create_record(attribute_names = self.attribute_names)
|
187
201
|
id = super
|
188
202
|
|
189
|
-
|
190
|
-
association.increment_counters
|
203
|
+
counter_cached_association_names.each do |association_name|
|
204
|
+
association(association_name).increment_counters
|
191
205
|
end
|
192
206
|
|
193
207
|
id
|
@@ -197,9 +211,10 @@ module ActiveRecord
|
|
197
211
|
affected_rows = super
|
198
212
|
|
199
213
|
if affected_rows > 0
|
200
|
-
|
201
|
-
|
202
|
-
|
214
|
+
counter_cached_association_names.each do |association_name|
|
215
|
+
association = association(association_name)
|
216
|
+
|
217
|
+
unless destroyed_by_association && _foreign_keys_equal?(destroyed_by_association.foreign_key, association.reflection.foreign_key)
|
203
218
|
association.decrement_counters
|
204
219
|
end
|
205
220
|
end
|
@@ -208,10 +223,8 @@ module ActiveRecord
|
|
208
223
|
affected_rows
|
209
224
|
end
|
210
225
|
|
211
|
-
def
|
212
|
-
|
213
|
-
yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column
|
214
|
-
end
|
226
|
+
def _foreign_keys_equal?(fkey1, fkey2)
|
227
|
+
fkey1 == fkey2 || Array(fkey1).map(&:to_sym) == Array(fkey2).map(&:to_sym)
|
215
228
|
end
|
216
229
|
end
|
217
230
|
end
|
@@ -25,8 +25,7 @@ module ActiveRecord
|
|
25
25
|
def initialize(url)
|
26
26
|
raise "Database URL cannot be empty" if url.blank?
|
27
27
|
@uri = uri_parser.parse(url)
|
28
|
-
@adapter =
|
29
|
-
@adapter = "postgresql" if @adapter == "postgres"
|
28
|
+
@adapter = resolved_adapter
|
30
29
|
|
31
30
|
if @uri.opaque
|
32
31
|
@uri.opaque, @query = @uri.opaque.split("?", 2)
|
@@ -46,7 +45,7 @@ module ActiveRecord
|
|
46
45
|
attr_reader :uri
|
47
46
|
|
48
47
|
def uri_parser
|
49
|
-
@uri_parser ||= URI::
|
48
|
+
@uri_parser ||= URI::RFC2396_Parser.new
|
50
49
|
end
|
51
50
|
|
52
51
|
# Converts the query parameters of the URI into a hash.
|
@@ -80,6 +79,12 @@ module ActiveRecord
|
|
80
79
|
end
|
81
80
|
end
|
82
81
|
|
82
|
+
def resolved_adapter
|
83
|
+
adapter = uri.scheme && @uri.scheme.tr("-", "_")
|
84
|
+
adapter = ActiveRecord.protocol_adapters[adapter] || adapter
|
85
|
+
adapter
|
86
|
+
end
|
87
|
+
|
83
88
|
# Returns name of the database.
|
84
89
|
def database_from_path
|
85
90
|
if @adapter == "sqlite3"
|
@@ -11,14 +11,25 @@ module ActiveRecord
|
|
11
11
|
def initialize(env_name, name)
|
12
12
|
@env_name = env_name
|
13
13
|
@name = name
|
14
|
+
@adapter_class = nil
|
14
15
|
end
|
15
16
|
|
16
|
-
def
|
17
|
-
|
17
|
+
def adapter_class
|
18
|
+
@adapter_class ||= ActiveRecord::ConnectionAdapters.resolve(adapter)
|
18
19
|
end
|
19
20
|
|
20
|
-
def
|
21
|
-
"
|
21
|
+
def inspect # :nodoc:
|
22
|
+
"#<#{self.class.name} env_name=#{@env_name} name=#{@name} adapter_class=#{adapter_class}>"
|
23
|
+
end
|
24
|
+
|
25
|
+
def new_connection
|
26
|
+
adapter_class.new(configuration_hash)
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate!
|
30
|
+
adapter_class if adapter
|
31
|
+
|
32
|
+
true
|
22
33
|
end
|
23
34
|
|
24
35
|
def host
|
@@ -84,6 +95,10 @@ module ActiveRecord
|
|
84
95
|
def schema_cache_path
|
85
96
|
raise NotImplementedError
|
86
97
|
end
|
98
|
+
|
99
|
+
def use_metadata_table?
|
100
|
+
raise NotImplementedError
|
101
|
+
end
|
87
102
|
end
|
88
103
|
end
|
89
104
|
end
|
@@ -1,53 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
class DatabaseConfigurations
|
5
|
-
#
|
7
|
+
# # Active Record Database Hash Config
|
6
8
|
#
|
7
|
-
# A
|
8
|
-
#
|
9
|
+
# A `HashConfig` object is created for each database configuration entry that is
|
10
|
+
# created from a hash.
|
9
11
|
#
|
10
12
|
# A hash config:
|
11
13
|
#
|
12
|
-
#
|
14
|
+
# { "development" => { "database" => "db_name" } }
|
13
15
|
#
|
14
16
|
# Becomes:
|
15
17
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
+
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
|
19
|
+
# @env_name="development", @name="primary", @config={database: "db_name"}>
|
18
20
|
#
|
19
21
|
# See ActiveRecord::DatabaseConfigurations for more info.
|
20
22
|
class HashConfig < DatabaseConfig
|
21
23
|
attr_reader :configuration_hash
|
22
24
|
|
23
|
-
|
24
|
-
# Initialize a new +HashConfig+ object
|
25
|
+
# Initialize a new `HashConfig` object
|
25
26
|
#
|
26
|
-
#
|
27
|
+
# #### Parameters
|
28
|
+
#
|
29
|
+
# * `env_name` - The Rails environment, i.e. "development".
|
30
|
+
# * `name` - The db config name. In a standard two-tier database configuration
|
31
|
+
# this will default to "primary". In a multiple database three-tier database
|
32
|
+
# configuration this corresponds to the name used in the second tier, for
|
33
|
+
# example "primary_readonly".
|
34
|
+
# * `configuration_hash` - The config hash. This is the hash that contains the
|
35
|
+
# database adapter, name, and other important information for database
|
36
|
+
# connections.
|
27
37
|
#
|
28
|
-
# * <tt>:env_name</tt> - The \Rails environment, i.e. "development".
|
29
|
-
# * <tt>:name</tt> - The db config name. In a standard two-tier
|
30
|
-
# database configuration this will default to "primary". In a multiple
|
31
|
-
# database three-tier database configuration this corresponds to the name
|
32
|
-
# used in the second tier, for example "primary_readonly".
|
33
|
-
# * <tt>:config</tt> - The config hash. This is the hash that contains the
|
34
|
-
# database adapter, name, and other important information for database
|
35
|
-
# connections.
|
36
38
|
def initialize(env_name, name, configuration_hash)
|
37
39
|
super(env_name, name)
|
38
40
|
@configuration_hash = configuration_hash.symbolize_keys.freeze
|
39
41
|
end
|
40
42
|
|
41
43
|
# Determines whether a database configuration is for a replica / readonly
|
42
|
-
# connection. If the
|
43
|
-
# return
|
44
|
+
# connection. If the `replica` key is present in the config, `replica?` will
|
45
|
+
# return `true`.
|
44
46
|
def replica?
|
45
47
|
configuration_hash[:replica]
|
46
48
|
end
|
47
49
|
|
48
|
-
# The migrations paths for a database configuration. If the
|
49
|
-
#
|
50
|
-
# will return its value.
|
50
|
+
# The migrations paths for a database configuration. If the `migrations_paths`
|
51
|
+
# key is present in the config, `migrations_paths` will return its value.
|
51
52
|
def migrations_paths
|
52
53
|
configuration_hash[:migrations_paths]
|
53
54
|
end
|
@@ -92,8 +93,8 @@ module ActiveRecord
|
|
92
93
|
(configuration_hash[:checkout_timeout] || 5).to_f
|
93
94
|
end
|
94
95
|
|
95
|
-
#
|
96
|
-
# also be useful if someone wants a very low
|
96
|
+
# `reaping_frequency` is configurable mostly for historical reasons, but it
|
97
|
+
# could also be useful if someone wants a very low `idle_timeout`.
|
97
98
|
def reaping_frequency
|
98
99
|
configuration_hash.fetch(:reaping_frequency, 60)&.to_f
|
99
100
|
end
|
@@ -104,18 +105,21 @@ module ActiveRecord
|
|
104
105
|
end
|
105
106
|
|
106
107
|
def adapter
|
107
|
-
configuration_hash[:adapter]
|
108
|
+
configuration_hash[:adapter]&.to_s
|
108
109
|
end
|
109
110
|
|
110
|
-
# The path to the schema cache dump file for a database.
|
111
|
-
#
|
112
|
-
# default will be derived.
|
111
|
+
# The path to the schema cache dump file for a database. If omitted, the
|
112
|
+
# filename will be read from ENV or a default will be derived.
|
113
113
|
def schema_cache_path
|
114
114
|
configuration_hash[:schema_cache_path]
|
115
115
|
end
|
116
116
|
|
117
|
-
def default_schema_cache_path
|
118
|
-
|
117
|
+
def default_schema_cache_path(db_dir = "db")
|
118
|
+
if primary?
|
119
|
+
File.join(db_dir, "schema_cache.yml")
|
120
|
+
else
|
121
|
+
File.join(db_dir, "#{name}_schema_cache.yml")
|
122
|
+
end
|
119
123
|
end
|
120
124
|
|
121
125
|
def lazy_schema_cache_path
|
@@ -126,14 +130,14 @@ module ActiveRecord
|
|
126
130
|
Base.configurations.primary?(name)
|
127
131
|
end
|
128
132
|
|
129
|
-
# Determines whether to dump the schema/structure files and the
|
130
|
-
#
|
133
|
+
# Determines whether to dump the schema/structure files and the filename that
|
134
|
+
# should be used.
|
131
135
|
#
|
132
|
-
# If
|
133
|
-
#
|
136
|
+
# If `configuration_hash[:schema_dump]` is set to `false` or `nil` the schema
|
137
|
+
# will not be dumped.
|
134
138
|
#
|
135
|
-
# If the config option is set that will be used. Otherwise
|
136
|
-
#
|
139
|
+
# If the config option is set that will be used. Otherwise Rails will generate
|
140
|
+
# the filename from the database config name.
|
137
141
|
def schema_dump(format = ActiveRecord.schema_format)
|
138
142
|
if configuration_hash.key?(:schema_dump)
|
139
143
|
if config = configuration_hash[:schema_dump]
|
@@ -150,6 +154,10 @@ module ActiveRecord
|
|
150
154
|
!replica? && !!configuration_hash.fetch(:database_tasks, true)
|
151
155
|
end
|
152
156
|
|
157
|
+
def use_metadata_table? # :nodoc:
|
158
|
+
configuration_hash.fetch(:use_metadata_table, true)
|
159
|
+
end
|
160
|
+
|
153
161
|
private
|
154
162
|
def schema_file_type(format)
|
155
163
|
case format
|
@@ -41,10 +41,29 @@ module ActiveRecord
|
|
41
41
|
super(env_name, name, configuration_hash)
|
42
42
|
|
43
43
|
@url = url
|
44
|
-
@configuration_hash = @configuration_hash.merge(build_url_hash)
|
44
|
+
@configuration_hash = @configuration_hash.merge(build_url_hash)
|
45
|
+
|
46
|
+
if @configuration_hash[:schema_dump] == "false"
|
47
|
+
@configuration_hash[:schema_dump] = false
|
48
|
+
end
|
49
|
+
|
50
|
+
if @configuration_hash[:query_cache] == "false"
|
51
|
+
@configuration_hash[:query_cache] = false
|
52
|
+
end
|
53
|
+
|
54
|
+
to_boolean!(@configuration_hash, :replica)
|
55
|
+
to_boolean!(@configuration_hash, :database_tasks)
|
56
|
+
|
57
|
+
@configuration_hash.freeze
|
45
58
|
end
|
46
59
|
|
47
60
|
private
|
61
|
+
def to_boolean!(configuration_hash, key)
|
62
|
+
if configuration_hash[key].is_a?(String)
|
63
|
+
configuration_hash[key] = configuration_hash[key] != "false"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
48
67
|
# Return a Hash that can be merged into the main config that represents
|
49
68
|
# the passed in url
|
50
69
|
def build_url_hash
|
@@ -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,12 +51,12 @@ module ActiveRecord
|
|
51
51
|
# end
|
52
52
|
# end
|
53
53
|
#
|
54
|
-
# # Schema: messages[ id, subject, body ]
|
54
|
+
# # Schema: messages[ id, subject, body, created_at, updated_at ]
|
55
55
|
# class Message < ApplicationRecord
|
56
56
|
# include Entryable
|
57
57
|
# end
|
58
58
|
#
|
59
|
-
# # Schema: comments[ id, content ]
|
59
|
+
# # Schema: comments[ id, content, created_at, updated_at ]
|
60
60
|
# class Comment < ApplicationRecord
|
61
61
|
# include Entryable
|
62
62
|
# end
|
@@ -102,17 +102,37 @@ module ActiveRecord
|
|
102
102
|
# You create a new record that uses delegated typing by creating the delegator and delegatee at the same time,
|
103
103
|
# like so:
|
104
104
|
#
|
105
|
-
# Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user
|
105
|
+
# Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user, account: Current.account
|
106
106
|
#
|
107
107
|
# If you need more complicated composition, or you need to perform dependent validation, you should build a factory
|
108
108
|
# method or class to take care of the complicated needs. This could be as simple as:
|
109
109
|
#
|
110
110
|
# class Entry < ApplicationRecord
|
111
|
-
# def self.create_with_comment(content, creator: Current.user)
|
112
|
-
# 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
|
113
113
|
# end
|
114
114
|
# end
|
115
115
|
#
|
116
|
+
# == Querying across records
|
117
|
+
#
|
118
|
+
# A consequence of delegated types is that querying attributes spread across multiple classes becomes slightly more
|
119
|
+
# tricky, but not impossible.
|
120
|
+
#
|
121
|
+
# The simplest method is to join the "superclass" to the "subclass" and apply the query parameters (i.e. <tt>#where</tt>)
|
122
|
+
# in appropriate places:
|
123
|
+
#
|
124
|
+
# Comment.joins(:entry).where(comments: { content: 'Hello!' }, entry: { creator: Current.user } )
|
125
|
+
#
|
126
|
+
# For convenience, add a scope on the concern. Now all classes that implement the concern will automatically include
|
127
|
+
# the method:
|
128
|
+
#
|
129
|
+
# # app/models/concerns/entryable.rb
|
130
|
+
# scope :with_entry, ->(attrs) { joins(:entry).where(entry: attrs) }
|
131
|
+
#
|
132
|
+
# Now the query can be shortened significantly:
|
133
|
+
#
|
134
|
+
# Comment.where(content: 'Hello!').with_entry(creator: Current.user)
|
135
|
+
#
|
116
136
|
# == Adding further delegation
|
117
137
|
#
|
118
138
|
# The delegated type shouldn't just answer the question of what the underlying class is called. In fact, that's
|
@@ -219,6 +239,10 @@ module ActiveRecord
|
|
219
239
|
role_type = options[:foreign_type] || "#{role}_type"
|
220
240
|
role_id = options[:foreign_key] || "#{role}_id"
|
221
241
|
|
242
|
+
define_singleton_method "#{role}_types" do
|
243
|
+
types.map(&:to_s)
|
244
|
+
end
|
245
|
+
|
222
246
|
define_method "#{role}_class" do
|
223
247
|
public_send(role_type).constantize
|
224
248
|
end
|
@@ -19,7 +19,7 @@ module ActiveRecord
|
|
19
19
|
)
|
20
20
|
association_model = association_class.constantize
|
21
21
|
owner_class = owner_model_name.constantize
|
22
|
-
owner = owner_class.find_by(owner_class.primary_key
|
22
|
+
owner = owner_class.find_by(owner_class.primary_key => [owner_id])
|
23
23
|
|
24
24
|
if !owner_destroyed?(owner, ensuring_owner_was_method)
|
25
25
|
raise DestroyAssociationAsyncError, "owner record not destroyed"
|
@@ -12,12 +12,12 @@ module ActiveRecord
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
def method_missing(name,
|
15
|
+
def method_missing(name, ...)
|
16
16
|
match = Method.match(self, name)
|
17
17
|
|
18
18
|
if match && match.valid?
|
19
19
|
match.define
|
20
|
-
send(name,
|
20
|
+
send(name, ...)
|
21
21
|
else
|
22
22
|
super
|
23
23
|
end
|
@@ -84,7 +84,7 @@ module ActiveRecord
|
|
84
84
|
def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
|
85
85
|
encrypted_attributes << name.to_sym
|
86
86
|
|
87
|
-
|
87
|
+
decorate_attributes([name]) do |name, cast_type|
|
88
88
|
scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, \
|
89
89
|
downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
|
90
90
|
|
@@ -123,7 +123,7 @@ module ActiveRecord
|
|
123
123
|
end)
|
124
124
|
end
|
125
125
|
|
126
|
-
def load_schema!
|
126
|
+
def load_schema! # :nodoc:
|
127
127
|
super
|
128
128
|
|
129
129
|
add_length_validation_for_encrypted_columns if ActiveRecord::Encryption.config.validate_column_size
|
@@ -221,7 +221,7 @@ module ActiveRecord
|
|
221
221
|
end
|
222
222
|
|
223
223
|
def cant_modify_encrypted_attributes_when_frozen
|
224
|
-
self.class
|
224
|
+
self.class.encrypted_attributes.each do |attribute|
|
225
225
|
errors.add(attribute.to_sym, "can't be modified because it is encrypted") if changed_attributes.include?(attribute)
|
226
226
|
end
|
227
227
|
end
|
@@ -7,13 +7,13 @@ module ActiveRecord
|
|
7
7
|
# This is the central piece that connects the encryption system with +encrypts+ declarations in the
|
8
8
|
# model classes. Whenever you declare an attribute as encrypted, it configures an +EncryptedAttributeType+
|
9
9
|
# for that attribute.
|
10
|
-
class EncryptedAttributeType < ::
|
10
|
+
class EncryptedAttributeType < ::ActiveModel::Type::Value
|
11
11
|
include ActiveModel::Type::Helpers::Mutable
|
12
12
|
|
13
13
|
attr_reader :scheme, :cast_type
|
14
14
|
|
15
15
|
delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme
|
16
|
-
delegate :accessor, to: :cast_type
|
16
|
+
delegate :accessor, :type, to: :cast_type
|
17
17
|
|
18
18
|
# === Options
|
19
19
|
#
|
@@ -81,7 +81,7 @@ module ActiveRecord
|
|
81
81
|
@previous_type
|
82
82
|
end
|
83
83
|
|
84
|
-
def
|
84
|
+
def decrypt_as_text(value)
|
85
85
|
with_context do
|
86
86
|
unless value.nil?
|
87
87
|
if @default && @default == value
|
@@ -99,6 +99,10 @@ module ActiveRecord
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
def decrypt(value)
|
103
|
+
text_to_database_type decrypt_as_text(value)
|
104
|
+
end
|
105
|
+
|
102
106
|
def try_to_deserialize_with_previous_encrypted_types(value)
|
103
107
|
previous_types.each.with_index do |type, index|
|
104
108
|
break type.deserialize(value)
|
@@ -129,27 +133,43 @@ module ActiveRecord
|
|
129
133
|
encrypt(casted_value.to_s) unless casted_value.nil?
|
130
134
|
end
|
131
135
|
|
132
|
-
def
|
136
|
+
def encrypt_as_text(value)
|
133
137
|
with_context do
|
138
|
+
if encryptor.binary? && !cast_type.binary?
|
139
|
+
raise Errors::Encoding, "Binary encoded data can only be stored in binary columns"
|
140
|
+
end
|
141
|
+
|
134
142
|
encryptor.encrypt(value, **encryption_options)
|
135
143
|
end
|
136
144
|
end
|
137
145
|
|
146
|
+
def encrypt(value)
|
147
|
+
text_to_database_type encrypt_as_text(value)
|
148
|
+
end
|
149
|
+
|
138
150
|
def encryptor
|
139
151
|
ActiveRecord::Encryption.encryptor
|
140
152
|
end
|
141
153
|
|
142
154
|
def encryption_options
|
143
|
-
|
155
|
+
{ key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
|
144
156
|
end
|
145
157
|
|
146
158
|
def decryption_options
|
147
|
-
|
159
|
+
{ key_provider: key_provider }.compact
|
148
160
|
end
|
149
161
|
|
150
162
|
def clean_text_scheme
|
151
163
|
@clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new)
|
152
164
|
end
|
165
|
+
|
166
|
+
def text_to_database_type(value)
|
167
|
+
if value && cast_type.binary?
|
168
|
+
ActiveModel::Type::Binary::Data.new(value)
|
169
|
+
else
|
170
|
+
value
|
171
|
+
end
|
172
|
+
end
|
153
173
|
end
|
154
174
|
end
|
155
175
|
end
|
@@ -12,6 +12,14 @@ module ActiveRecord
|
|
12
12
|
# It interacts with a KeyProvider for getting the keys, and delegate to
|
13
13
|
# ActiveRecord::Encryption::Cipher the actual encryption algorithm.
|
14
14
|
class Encryptor
|
15
|
+
# === Options
|
16
|
+
#
|
17
|
+
# * <tt>:compress</tt> - Boolean indicating whether records should be compressed before encryption.
|
18
|
+
# Defaults to +true+.
|
19
|
+
def initialize(compress: true)
|
20
|
+
@compress = compress
|
21
|
+
end
|
22
|
+
|
15
23
|
# Encrypts +clean_text+ and returns the encrypted result
|
16
24
|
#
|
17
25
|
# Internally, it will:
|
@@ -38,7 +46,7 @@ module ActiveRecord
|
|
38
46
|
serialize_message build_encrypted_message(clear_text, key_provider: key_provider, cipher_options: cipher_options)
|
39
47
|
end
|
40
48
|
|
41
|
-
# Decrypts
|
49
|
+
# Decrypts an +encrypted_text+ and returns the result as clean text
|
42
50
|
#
|
43
51
|
# === Options
|
44
52
|
#
|
@@ -66,6 +74,10 @@ module ActiveRecord
|
|
66
74
|
false
|
67
75
|
end
|
68
76
|
|
77
|
+
def binary?
|
78
|
+
serializer.binary?
|
79
|
+
end
|
80
|
+
|
69
81
|
private
|
70
82
|
DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
|
71
83
|
ENCODING_ERRORS = [EncodingError, Errors::Encoding]
|
@@ -100,7 +112,6 @@ module ActiveRecord
|
|
100
112
|
end
|
101
113
|
|
102
114
|
def deserialize_message(message)
|
103
|
-
raise Errors::Encoding unless message.is_a?(String)
|
104
115
|
serializer.load message
|
105
116
|
rescue ArgumentError, TypeError, Errors::ForbiddenClass
|
106
117
|
raise Errors::Encoding
|
@@ -112,13 +123,17 @@ module ActiveRecord
|
|
112
123
|
|
113
124
|
# Under certain threshold, ZIP compression is actually worse that not compressing
|
114
125
|
def compress_if_worth_it(string)
|
115
|
-
if string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
|
126
|
+
if compress? && string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
|
116
127
|
[compress(string), true]
|
117
128
|
else
|
118
129
|
[string, false]
|
119
130
|
end
|
120
131
|
end
|
121
132
|
|
133
|
+
def compress?
|
134
|
+
@compress
|
135
|
+
end
|
136
|
+
|
122
137
|
def compress(data)
|
123
138
|
Zlib::Deflate.deflate(data).tap do |compressed_data|
|
124
139
|
compressed_data.force_encoding(data.encoding)
|
@@ -12,7 +12,7 @@ module ActiveRecord
|
|
12
12
|
@keys = Array(keys)
|
13
13
|
end
|
14
14
|
|
15
|
-
# Returns the
|
15
|
+
# Returns the last key in the list as the active key to perform encryptions
|
16
16
|
#
|
17
17
|
# When +ActiveRecord::Encryption.config.store_key_references+ is true, the key will include
|
18
18
|
# a public tag referencing the key itself. That key will be stored in the public
|