activerecord 8.0.3 → 8.1.0.rc1
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 +520 -514
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +2 -0
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/join_dependency.rb +2 -0
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +0 -2
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +405 -72
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -3
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -22
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -20
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -13
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +17 -15
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +66 -31
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +81 -48
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +23 -7
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -30
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +2 -1
- data/lib/active_record/core.rb +5 -4
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +53 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/encryptor.rb +12 -0
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +20 -4
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +2 -6
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +14 -1
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +26 -16
- data/lib/active_record/model_schema.rb +36 -10
- data/lib/active_record/nested_attributes.rb +2 -0
- data/lib/active_record/persistence.rb +34 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +3 -7
- data/lib/active_record/railtie.rb +32 -3
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +15 -3
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +42 -3
- data/lib/active_record/relation/batches.rb +25 -11
- data/lib/active_record/relation/calculations.rb +20 -9
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +27 -11
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +9 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +38 -28
- data/lib/active_record/relation/where_clause.rb +1 -8
- data/lib/active_record/relation.rb +24 -12
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +41 -58
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +12 -10
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +25 -34
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +27 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +32 -10
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +11 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +65 -3
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +3 -21
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +14 -10
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
|
@@ -47,9 +47,8 @@ module ActiveRecord
|
|
|
47
47
|
@configuration_hash[:schema_dump] = false
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
end
|
|
50
|
+
query_cache = parse_query_cache
|
|
51
|
+
@configuration_hash[:query_cache] = query_cache unless query_cache.nil?
|
|
53
52
|
|
|
54
53
|
to_boolean!(@configuration_hash, :replica)
|
|
55
54
|
to_boolean!(@configuration_hash, :database_tasks)
|
|
@@ -58,6 +57,17 @@ module ActiveRecord
|
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
private
|
|
60
|
+
def parse_query_cache
|
|
61
|
+
case value = @configuration_hash[:query_cache]
|
|
62
|
+
when /\A\d+\z/
|
|
63
|
+
value.to_i
|
|
64
|
+
when "false"
|
|
65
|
+
false
|
|
66
|
+
else
|
|
67
|
+
value
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
61
71
|
def to_boolean!(configuration_hash, key)
|
|
62
72
|
if configuration_hash[key].is_a?(String)
|
|
63
73
|
configuration_hash[key] = configuration_hash[key] != "false"
|
|
@@ -36,9 +36,11 @@ module ActiveRecord
|
|
|
36
36
|
# to respond to `sharded?`. To implement this define the following in an
|
|
37
37
|
# initializer:
|
|
38
38
|
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
39
|
+
# ActiveSupport.on_load(:active_record_database_configurations) do
|
|
40
|
+
# ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
|
|
41
|
+
# next unless config.key?(:vitess)
|
|
42
|
+
# VitessConfig.new(env_name, name, config)
|
|
43
|
+
# end
|
|
42
44
|
# end
|
|
43
45
|
#
|
|
44
46
|
# Note: applications must handle the condition in which custom config should be
|
|
@@ -306,4 +308,6 @@ module ActiveRecord
|
|
|
306
308
|
url
|
|
307
309
|
end
|
|
308
310
|
end
|
|
311
|
+
|
|
312
|
+
ActiveSupport.run_load_hooks(:active_record_database_configurations, DatabaseConfigurations)
|
|
309
313
|
end
|
|
@@ -229,7 +229,7 @@ module ActiveRecord
|
|
|
229
229
|
# @entry.message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
|
|
230
230
|
# @entry.comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
|
|
231
231
|
def delegated_type(role, types:, **options)
|
|
232
|
-
belongs_to role, options.delete(:scope), **options
|
|
232
|
+
belongs_to role, options.delete(:scope), **options, polymorphic: true
|
|
233
233
|
define_delegated_type_methods role, types: types, options: options
|
|
234
234
|
end
|
|
235
235
|
|
|
@@ -7,16 +7,18 @@ module ActiveRecord
|
|
|
7
7
|
if self == Base
|
|
8
8
|
super
|
|
9
9
|
else
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
super || begin
|
|
11
|
+
match = Method.match(name)
|
|
12
|
+
match && match.valid?(self, name)
|
|
13
|
+
end
|
|
12
14
|
end
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def method_missing(name, ...)
|
|
16
|
-
match = Method.match(
|
|
18
|
+
match = Method.match(name)
|
|
17
19
|
|
|
18
|
-
if match && match.valid?
|
|
19
|
-
match.define
|
|
20
|
+
if match && match.valid?(self, name)
|
|
21
|
+
match.define(self, name)
|
|
20
22
|
send(name, ...)
|
|
21
23
|
else
|
|
22
24
|
super
|
|
@@ -24,97 +26,80 @@ module ActiveRecord
|
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
class Method
|
|
27
|
-
@matchers = []
|
|
28
|
-
|
|
29
29
|
class << self
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def match(model, name)
|
|
33
|
-
klass = matchers.find { |k| k.pattern.match?(name) }
|
|
34
|
-
klass.new(model, name) if klass
|
|
30
|
+
def match(name)
|
|
31
|
+
FindBy.match?(name) || FindByBang.match?(name)
|
|
35
32
|
end
|
|
36
33
|
|
|
37
|
-
def
|
|
38
|
-
|
|
34
|
+
def valid?(model, name)
|
|
35
|
+
attribute_names(model, name.to_s).all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
|
|
39
36
|
end
|
|
40
37
|
|
|
41
|
-
def
|
|
42
|
-
|
|
38
|
+
def define(model, name)
|
|
39
|
+
model.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
40
|
+
def self.#{name}(#{signature(model, name)})
|
|
41
|
+
#{body(model, name)}
|
|
42
|
+
end
|
|
43
|
+
CODE
|
|
43
44
|
end
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
private
|
|
47
|
+
def make_pattern(prefix, suffix)
|
|
48
|
+
/\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/
|
|
49
|
+
end
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
def attribute_names(model, name)
|
|
52
|
+
attribute_names = name.match(pattern)[1].split("_and_")
|
|
53
|
+
attribute_names.map! { |name| model.attribute_aliases[name] || name }
|
|
54
|
+
end
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@attribute_names = @name.match(self.class.pattern)[1].split("_and_")
|
|
56
|
-
@attribute_names.map! { |name| @model.attribute_aliases[name] || name }
|
|
57
|
-
end
|
|
56
|
+
def body(model, method_name)
|
|
57
|
+
"#{finder}(#{attributes_hash(model, method_name)})"
|
|
58
|
+
end
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
# The parameters in the signature may have reserved Ruby words, in order
|
|
61
|
+
# to prevent errors, we start each param name with `_`.
|
|
62
|
+
def signature(model, method_name)
|
|
63
|
+
attribute_names(model, method_name.to_s).map { |name| "_#{name}" }.join(", ")
|
|
64
|
+
end
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def
|
|
66
|
-
#{
|
|
66
|
+
# Given that the parameters starts with `_`, the finder needs to use the
|
|
67
|
+
# same parameter name.
|
|
68
|
+
def attributes_hash(model, method_name)
|
|
69
|
+
"{" + attribute_names(model, method_name).map { |name| ":#{name} => _#{name}" }.join(",") + "}"
|
|
67
70
|
end
|
|
68
|
-
CODE
|
|
69
71
|
end
|
|
72
|
+
end
|
|
70
73
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"#{finder}(#{attributes_hash})"
|
|
74
|
-
end
|
|
74
|
+
class FindBy < Method
|
|
75
|
+
@pattern = make_pattern("find_by", "")
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def signature
|
|
79
|
-
attribute_names.map { |name| "_#{name}" }.join(", ")
|
|
80
|
-
end
|
|
77
|
+
class << self
|
|
78
|
+
attr_reader :pattern
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def attributes_hash
|
|
85
|
-
"{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(",") + "}"
|
|
80
|
+
def match?(name)
|
|
81
|
+
pattern.match?(name) && self
|
|
86
82
|
end
|
|
87
83
|
|
|
88
84
|
def finder
|
|
89
|
-
|
|
85
|
+
"find_by"
|
|
90
86
|
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
class FindBy < Method
|
|
94
|
-
Method.matchers << self
|
|
95
|
-
|
|
96
|
-
def self.prefix
|
|
97
|
-
"find_by"
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def finder
|
|
101
|
-
"find_by"
|
|
102
87
|
end
|
|
103
88
|
end
|
|
104
89
|
|
|
105
90
|
class FindByBang < Method
|
|
106
|
-
|
|
91
|
+
@pattern = make_pattern("find_by", "!")
|
|
107
92
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
end
|
|
93
|
+
class << self
|
|
94
|
+
attr_reader :pattern
|
|
111
95
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
96
|
+
def match?(name)
|
|
97
|
+
pattern.match?(name) && self
|
|
98
|
+
end
|
|
115
99
|
|
|
116
|
-
|
|
117
|
-
|
|
100
|
+
def finder
|
|
101
|
+
"find_by!"
|
|
102
|
+
end
|
|
118
103
|
end
|
|
119
104
|
end
|
|
120
105
|
end
|
|
@@ -30,10 +30,10 @@ module ActiveRecord
|
|
|
30
30
|
# will use the oldest encryption scheme to encrypt new data by default. You can change this by setting
|
|
31
31
|
# <tt>deterministic: { fixed: false }</tt>. That will make it use the newest encryption scheme for encrypting new
|
|
32
32
|
# data.
|
|
33
|
-
# * <tt>:support_unencrypted_data</tt> -
|
|
34
|
-
#
|
|
35
|
-
# scenarios where you encrypt one column, and want to disable support for unencrypted data
|
|
36
|
-
# the global setting.
|
|
33
|
+
# * <tt>:support_unencrypted_data</tt> - When true, unencrypted data can be read normally. When false, it will raise errors.
|
|
34
|
+
# Falls back to +config.active_record.encryption.support_unencrypted_data+ if no value is provided.
|
|
35
|
+
# This is useful for scenarios where you encrypt one column, and want to disable support for unencrypted data
|
|
36
|
+
# without having to tweak the global setting.
|
|
37
37
|
# * <tt>:downcase</tt> - When true, it converts the encrypted content to downcase automatically. This allows to
|
|
38
38
|
# effectively ignore case when querying data. Notice that the case is lost. Use +:ignore_case+ if you are interested
|
|
39
39
|
# in preserving it.
|
|
@@ -94,6 +94,18 @@ module ActiveRecord
|
|
|
94
94
|
private
|
|
95
95
|
DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
|
|
96
96
|
ENCODING_ERRORS = [EncodingError, Errors::Encoding]
|
|
97
|
+
|
|
98
|
+
# This threshold cannot be changed.
|
|
99
|
+
#
|
|
100
|
+
# Users can search for attributes encrypted with `deterministic: true`.
|
|
101
|
+
# That is possible because we are able to generate the message for the
|
|
102
|
+
# given clear text deterministically, and with that perform a regular
|
|
103
|
+
# string lookup in SQL.
|
|
104
|
+
#
|
|
105
|
+
# Problem is, messages may have a "c" header that is present or not
|
|
106
|
+
# depending on whether compression was applied on encryption. If this
|
|
107
|
+
# threshold was modified, the message generated for lookup could vary
|
|
108
|
+
# for the same clear text, and searches on exisiting data could fail.
|
|
97
109
|
THRESHOLD_TO_JUSTIFY_COMPRESSION = 140.bytes
|
|
98
110
|
|
|
99
111
|
def default_key_provider
|
data/lib/active_record/enum.rb
CHANGED
|
@@ -221,7 +221,7 @@ module ActiveRecord
|
|
|
221
221
|
|
|
222
222
|
private
|
|
223
223
|
def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
|
|
224
|
-
assert_valid_enum_definition_values(values)
|
|
224
|
+
values = assert_valid_enum_definition_values(values)
|
|
225
225
|
assert_valid_enum_options(options)
|
|
226
226
|
|
|
227
227
|
# statuses = { }
|
|
@@ -342,6 +342,20 @@ module ActiveRecord
|
|
|
342
342
|
if values.keys.any?(&:blank?)
|
|
343
343
|
raise ArgumentError, "Enum values #{values} must not contain a blank name."
|
|
344
344
|
end
|
|
345
|
+
|
|
346
|
+
values = values.transform_values do |value|
|
|
347
|
+
value.is_a?(Symbol) ? value.name : value
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
values.each_value do |value|
|
|
351
|
+
case value
|
|
352
|
+
when String, Integer, true, false, nil
|
|
353
|
+
# noop
|
|
354
|
+
else
|
|
355
|
+
raise ArgumentError, "Enum values #{values} must be only booleans, integers, symbols or strings, got: #{value.class}"
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
345
359
|
when Array
|
|
346
360
|
if values.empty?
|
|
347
361
|
raise ArgumentError, "Enum values #{values} must not be empty."
|
|
@@ -357,6 +371,8 @@ module ActiveRecord
|
|
|
357
371
|
else
|
|
358
372
|
raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array."
|
|
359
373
|
end
|
|
374
|
+
|
|
375
|
+
values
|
|
360
376
|
end
|
|
361
377
|
|
|
362
378
|
def assert_valid_enum_options(options)
|
|
@@ -368,25 +384,25 @@ module ActiveRecord
|
|
|
368
384
|
|
|
369
385
|
ENUM_CONFLICT_MESSAGE = \
|
|
370
386
|
"You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
|
|
371
|
-
"this will generate
|
|
387
|
+
"this will generate %{type} method \"%{method}\", which is already defined " \
|
|
372
388
|
"by %{source}."
|
|
373
389
|
private_constant :ENUM_CONFLICT_MESSAGE
|
|
374
390
|
|
|
375
391
|
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
|
|
376
392
|
if klass_method && dangerous_class_method?(method_name)
|
|
377
|
-
raise_conflict_error(enum_name, method_name,
|
|
393
|
+
raise_conflict_error(enum_name, method_name, "a class")
|
|
378
394
|
elsif klass_method && method_defined_within?(method_name, Relation)
|
|
379
|
-
raise_conflict_error(enum_name, method_name,
|
|
395
|
+
raise_conflict_error(enum_name, method_name, "a class", source: Relation.name)
|
|
380
396
|
elsif klass_method && method_name.to_sym == :id
|
|
381
|
-
raise_conflict_error(enum_name, method_name)
|
|
397
|
+
raise_conflict_error(enum_name, method_name, "an instance")
|
|
382
398
|
elsif !klass_method && dangerous_attribute_method?(method_name)
|
|
383
|
-
raise_conflict_error(enum_name, method_name)
|
|
399
|
+
raise_conflict_error(enum_name, method_name, "an instance")
|
|
384
400
|
elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
|
|
385
|
-
raise_conflict_error(enum_name, method_name, source: "another enum")
|
|
401
|
+
raise_conflict_error(enum_name, method_name, "an instance", source: "another enum")
|
|
386
402
|
end
|
|
387
403
|
end
|
|
388
404
|
|
|
389
|
-
def raise_conflict_error(enum_name, method_name, type
|
|
405
|
+
def raise_conflict_error(enum_name, method_name, type, source: "Active Record")
|
|
390
406
|
raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
|
|
391
407
|
enum: enum_name,
|
|
392
408
|
klass: name,
|
data/lib/active_record/errors.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "active_support/deprecation"
|
|
4
3
|
|
|
5
4
|
module ActiveRecord
|
|
6
5
|
include ActiveSupport::Deprecation::DeprecatedConstantAccessor
|
|
@@ -293,6 +292,14 @@ module ActiveRecord
|
|
|
293
292
|
class NotNullViolation < StatementInvalid
|
|
294
293
|
end
|
|
295
294
|
|
|
295
|
+
# Raised when a record cannot be inserted or updated because it would violate a check constraint.
|
|
296
|
+
class CheckViolation < StatementInvalid
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Raised when a record cannot be inserted or updated because it would violate an exclusion constraint.
|
|
300
|
+
class ExclusionViolation < StatementInvalid
|
|
301
|
+
end
|
|
302
|
+
|
|
296
303
|
# Raised when a record cannot be inserted or updated because a value too long for a column type.
|
|
297
304
|
class ValueTooLong < StatementInvalid
|
|
298
305
|
end
|
|
@@ -339,15 +346,15 @@ module ActiveRecord
|
|
|
339
346
|
class << self
|
|
340
347
|
def db_error(db_name)
|
|
341
348
|
NoDatabaseError.new(<<~MSG)
|
|
342
|
-
|
|
349
|
+
Database not found: #{db_name}. Available database configurations can be found in config/database.yml.
|
|
343
350
|
|
|
344
351
|
To resolve this error:
|
|
345
352
|
|
|
346
|
-
-
|
|
353
|
+
- Create the database by running:
|
|
347
354
|
|
|
348
355
|
bin/rails db:create
|
|
349
356
|
|
|
350
|
-
-
|
|
357
|
+
- Verify that config/database.yml contains the correct database name.
|
|
351
358
|
MSG
|
|
352
359
|
end
|
|
353
360
|
end
|
|
@@ -490,6 +497,7 @@ module ActiveRecord
|
|
|
490
497
|
# end
|
|
491
498
|
#
|
|
492
499
|
# relation = Task.all
|
|
500
|
+
# relation.load
|
|
493
501
|
# relation.loaded? # => true
|
|
494
502
|
#
|
|
495
503
|
# # Methods which try to mutate a loaded relation fail.
|
|
@@ -552,6 +560,11 @@ module ActiveRecord
|
|
|
552
560
|
class Deadlocked < TransactionRollbackError
|
|
553
561
|
end
|
|
554
562
|
|
|
563
|
+
# MissingRequiredOrderError is raised when a relation requires ordering but
|
|
564
|
+
# lacks any +order+ values in scope or any model order columns to use.
|
|
565
|
+
class MissingRequiredOrderError < ActiveRecordError
|
|
566
|
+
end
|
|
567
|
+
|
|
555
568
|
# IrreversibleOrderError is raised when a relation's order is too complex for
|
|
556
569
|
# +reverse_order+ to automatically reverse.
|
|
557
570
|
class IrreversibleOrderError < ActiveRecordError
|
|
@@ -609,6 +622,9 @@ module ActiveRecord
|
|
|
609
622
|
# the database version cannot be determined.
|
|
610
623
|
class DatabaseVersionError < ActiveRecordError
|
|
611
624
|
end
|
|
625
|
+
|
|
626
|
+
class DeprecatedAssociationError < ActiveRecordError
|
|
627
|
+
end
|
|
612
628
|
end
|
|
613
629
|
|
|
614
630
|
require "active_record/associations/errors"
|
|
@@ -7,7 +7,7 @@ module ActiveRecord
|
|
|
7
7
|
# Executes the block with the collect flag enabled. Queries are collected
|
|
8
8
|
# asynchronously by the subscriber and returned.
|
|
9
9
|
def collecting_queries_for_explain # :nodoc:
|
|
10
|
-
ExplainRegistry.
|
|
10
|
+
ExplainRegistry.start
|
|
11
11
|
yield
|
|
12
12
|
ExplainRegistry.queries
|
|
13
13
|
ensure
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "active_support/core_ext/module/delegation"
|
|
4
3
|
|
|
5
4
|
module ActiveRecord
|
|
6
5
|
# This is a thread locals registry for EXPLAIN. For example
|
|
@@ -9,8 +8,53 @@ module ActiveRecord
|
|
|
9
8
|
#
|
|
10
9
|
# returns the collected queries local to the current thread.
|
|
11
10
|
class ExplainRegistry # :nodoc:
|
|
11
|
+
class Subscriber
|
|
12
|
+
MUTEX = Mutex.new
|
|
13
|
+
@subscribed = false
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def ensure_subscribed
|
|
17
|
+
return if @subscribed
|
|
18
|
+
MUTEX.synchronize do
|
|
19
|
+
return if @subscribed
|
|
20
|
+
|
|
21
|
+
ActiveSupport::Notifications.subscribe("sql.active_record", new)
|
|
22
|
+
@subscribed = true
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def start(name, id, payload)
|
|
28
|
+
# unused
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def finish(name, id, payload)
|
|
32
|
+
if ExplainRegistry.collect? && !ignore_payload?(payload)
|
|
33
|
+
ExplainRegistry.queries << payload.values_at(:sql, :binds)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def silenced?(_name)
|
|
38
|
+
!ExplainRegistry.collect?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
|
|
42
|
+
# our own EXPLAINs no matter how loopingly beautiful that would be.
|
|
43
|
+
#
|
|
44
|
+
# On the other hand, we want to monitor the performance of our real database
|
|
45
|
+
# queries, not the performance of the access to the query cache.
|
|
46
|
+
IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN)
|
|
47
|
+
EXPLAINED_SQLS = /\A\s*(\/\*.*\*\/)?\s*(with|select|update|delete|insert)\b/i
|
|
48
|
+
def ignore_payload?(payload)
|
|
49
|
+
payload[:exception] ||
|
|
50
|
+
payload[:cached] ||
|
|
51
|
+
IGNORED_PAYLOADS.include?(payload[:name]) ||
|
|
52
|
+
!payload[:sql].match?(EXPLAINED_SQLS)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
12
56
|
class << self
|
|
13
|
-
delegate :reset, :collect, :collect=, :collect?, :queries, to: :instance
|
|
57
|
+
delegate :start, :reset, :collect, :collect=, :collect?, :queries, to: :instance
|
|
14
58
|
|
|
15
59
|
private
|
|
16
60
|
def instance
|
|
@@ -25,6 +69,11 @@ module ActiveRecord
|
|
|
25
69
|
reset
|
|
26
70
|
end
|
|
27
71
|
|
|
72
|
+
def start
|
|
73
|
+
Subscriber.ensure_subscribed
|
|
74
|
+
@collect = true
|
|
75
|
+
end
|
|
76
|
+
|
|
28
77
|
def collect?
|
|
29
78
|
@collect
|
|
30
79
|
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
class FilterAttributeHandler # :nodoc:
|
|
5
|
+
class << self
|
|
6
|
+
def on_sensitive_attribute_declared(&block)
|
|
7
|
+
@sensitive_attribute_declaration_listeners ||= Concurrent::Array.new
|
|
8
|
+
@sensitive_attribute_declaration_listeners << block
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def sensitive_attribute_was_declared(klass, list)
|
|
12
|
+
@sensitive_attribute_declaration_listeners&.each do |block|
|
|
13
|
+
block.call(klass, list)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(app)
|
|
19
|
+
@app = app
|
|
20
|
+
@attributes_by_class = Concurrent::Map.new
|
|
21
|
+
@collecting = true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def enable
|
|
25
|
+
install_collecting_hook
|
|
26
|
+
|
|
27
|
+
apply_collected_attributes
|
|
28
|
+
@collecting = false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
attr_reader :app
|
|
33
|
+
|
|
34
|
+
def install_collecting_hook
|
|
35
|
+
self.class.on_sensitive_attribute_declared do |klass, list|
|
|
36
|
+
attribute_was_declared(klass, list)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def attribute_was_declared(klass, list)
|
|
41
|
+
if collecting?
|
|
42
|
+
collect_for_later(klass, list)
|
|
43
|
+
else
|
|
44
|
+
apply_filter(klass, list)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def apply_collected_attributes
|
|
49
|
+
@attributes_by_class.each do |klass, list|
|
|
50
|
+
apply_filter(klass, list)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def collecting?
|
|
55
|
+
@collecting
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def collect_for_later(klass, list)
|
|
59
|
+
@attributes_by_class[klass] ||= Concurrent::Array.new
|
|
60
|
+
@attributes_by_class[klass] += list
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def apply_filter(klass, list)
|
|
64
|
+
list.each do |attribute|
|
|
65
|
+
next if klass.abstract_class? || klass == Base
|
|
66
|
+
|
|
67
|
+
klass_name = klass.name ? klass.model_name.element : nil
|
|
68
|
+
filter = [klass_name, attribute.to_s].compact.join(".")
|
|
69
|
+
app.config.filter_parameters << filter unless app.config.filter_parameters.include?(filter)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -242,10 +242,10 @@ module ActiveRecord
|
|
|
242
242
|
# and one for the humans. Why don't we generate the primary key instead?
|
|
243
243
|
# Hashing each fixture's label yields a consistent ID:
|
|
244
244
|
#
|
|
245
|
-
# george: # generated id:
|
|
245
|
+
# george: # generated id: 380982691
|
|
246
246
|
# name: George the Monkey
|
|
247
247
|
#
|
|
248
|
-
# reginald: # generated id:
|
|
248
|
+
# reginald: # generated id: 41001176
|
|
249
249
|
# name: Reginald the Pirate
|
|
250
250
|
#
|
|
251
251
|
# Active Record looks at the fixture's model class, discovers the correct
|
|
@@ -97,7 +97,7 @@ module ActiveRecord
|
|
|
97
97
|
# Returns the first class in the inheritance hierarchy that descends from either an
|
|
98
98
|
# abstract class or from <tt>ActiveRecord::Base</tt>.
|
|
99
99
|
#
|
|
100
|
-
# Consider the following
|
|
100
|
+
# Consider the following behavior:
|
|
101
101
|
#
|
|
102
102
|
# class ApplicationRecord < ActiveRecord::Base
|
|
103
103
|
# self.abstract_class = true
|
|
@@ -11,7 +11,7 @@ module ActiveRecord
|
|
|
11
11
|
def execute(relation, ...)
|
|
12
12
|
relation.model.with_connection do |c|
|
|
13
13
|
new(relation, c, ...).execute
|
|
14
|
-
end
|
|
14
|
+
end.tap { relation.reset }
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -225,7 +225,7 @@ module ActiveRecord
|
|
|
225
225
|
class Builder # :nodoc:
|
|
226
226
|
attr_reader :model
|
|
227
227
|
|
|
228
|
-
delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, to: :insert_all
|
|
228
|
+
delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, :primary_keys, to: :insert_all
|
|
229
229
|
|
|
230
230
|
def initialize(insert_all)
|
|
231
231
|
@insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
|
|
@@ -236,11 +236,16 @@ module ActiveRecord
|
|
|
236
236
|
end
|
|
237
237
|
|
|
238
238
|
def values_list
|
|
239
|
-
types =
|
|
239
|
+
types = extract_types_for(keys_including_timestamps)
|
|
240
240
|
|
|
241
241
|
values_list = insert_all.map_key_with_value do |key, value|
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
if Arel::Nodes::SqlLiteral === value
|
|
243
|
+
value
|
|
244
|
+
elsif primary_keys.include?(key) && value.nil?
|
|
245
|
+
connection.default_insert_value(model.columns_hash[key])
|
|
246
|
+
else
|
|
247
|
+
ActiveModel::Type::SerializeCastValue.serialize(type = types[key], type.cast(value))
|
|
248
|
+
end
|
|
244
249
|
end
|
|
245
250
|
|
|
246
251
|
connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list))
|
|
@@ -303,8 +308,8 @@ module ActiveRecord
|
|
|
303
308
|
format_columns(insert_all.keys_including_timestamps)
|
|
304
309
|
end
|
|
305
310
|
|
|
306
|
-
def
|
|
307
|
-
columns = @model.
|
|
311
|
+
def extract_types_for(keys)
|
|
312
|
+
columns = @model.columns_hash
|
|
308
313
|
|
|
309
314
|
unknown_column = (keys - columns.keys).first
|
|
310
315
|
raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
|