activerecord 8.0.5.1 → 8.1.0.beta1
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 +421 -657
- 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 +0 -2
- 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 +4 -2
- data/lib/active_record/associations/preloader/batch.rb +1 -7
- 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.rb +1 -1
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +2 -2
- data/lib/active_record/base.rb +2 -3
- 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 +15 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +383 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +27 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -18
- 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 +88 -25
- data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +96 -49
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
- 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 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -23
- 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 +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +10 -12
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/column.rb +2 -8
- 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_dumper.rb +3 -15
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +57 -33
- 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 +8 -12
- 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 +50 -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/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +23 -7
- data/lib/active_record/explain_registry.rb +0 -1
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/future_result.rb +0 -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 +14 -9
- 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 +1 -5
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +15 -2
- 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 +10 -7
- 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/databases.rake +16 -4
- 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 +26 -12
- 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 +28 -12
- data/lib/active_record/relation/merger.rb +2 -2
- data/lib/active_record/relation/predicate_builder/array_handler.rb +1 -3
- data/lib/active_record/relation/predicate_builder.rb +2 -2
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +40 -31
- data/lib/active_record/relation/where_clause.rb +2 -11
- data/lib/active_record/relation.rb +26 -14
- data/lib/active_record/result.rb +44 -21
- 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/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +2 -21
- 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 +10 -2
- 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 -9
- 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/predications.rb +3 -1
- 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
- metadata +14 -10
- data/lib/active_record/normalization.rb +0 -163
|
@@ -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.
|
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
|
|
@@ -13,7 +12,7 @@ module ActiveRecord
|
|
|
13
12
|
|
|
14
13
|
# Raised when the single-table inheritance mechanism fails to locate the subclass
|
|
15
14
|
# (for example due to improper usage of column that
|
|
16
|
-
# {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema
|
|
15
|
+
# {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column]
|
|
17
16
|
# points to).
|
|
18
17
|
class SubclassNotFound < ActiveRecordError
|
|
19
18
|
end
|
|
@@ -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
|
|
@@ -444,7 +451,7 @@ module ActiveRecord
|
|
|
444
451
|
UnknownAttributeError = ActiveModel::UnknownAttributeError
|
|
445
452
|
|
|
446
453
|
# Raised when an error occurred while doing a mass assignment to an attribute through the
|
|
447
|
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:
|
|
454
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
|
|
448
455
|
# The exception has an +attribute+ property that is the name of the offending attribute.
|
|
449
456
|
class AttributeAssignmentError < ActiveRecordError
|
|
450
457
|
attr_reader :exception, :attribute
|
|
@@ -457,7 +464,7 @@ module ActiveRecord
|
|
|
457
464
|
end
|
|
458
465
|
|
|
459
466
|
# Raised when there are multiple errors while doing a mass assignment through the
|
|
460
|
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:
|
|
467
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=]
|
|
461
468
|
# method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
|
|
462
469
|
# objects, each corresponding to the error while assigning to an attribute.
|
|
463
470
|
class MultiparameterAssignmentErrors < ActiveRecordError
|
|
@@ -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"
|
|
@@ -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
|
|
@@ -105,7 +105,6 @@ module ActiveRecord
|
|
|
105
105
|
|
|
106
106
|
@pool.with_connection do |connection|
|
|
107
107
|
return unless @mutex.try_lock
|
|
108
|
-
previous_instrumenter = ActiveSupport::IsolatedExecutionState[:active_record_instrumenter]
|
|
109
108
|
begin
|
|
110
109
|
if pending?
|
|
111
110
|
@event_buffer = EventBuffer.new(self, @instrumenter)
|
|
@@ -114,7 +113,6 @@ module ActiveRecord
|
|
|
114
113
|
execute_query(connection, async: true)
|
|
115
114
|
end
|
|
116
115
|
ensure
|
|
117
|
-
ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = previous_instrumenter
|
|
118
116
|
@mutex.unlock
|
|
119
117
|
end
|
|
120
118
|
end
|
|
@@ -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
|
|
|
@@ -39,7 +39,7 @@ module ActiveRecord
|
|
|
39
39
|
@returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
|
|
40
40
|
@returning = false if @returning == []
|
|
41
41
|
|
|
42
|
-
@unique_by = find_unique_index_for(@unique_by)
|
|
42
|
+
@unique_by = find_unique_index_for(@unique_by)
|
|
43
43
|
|
|
44
44
|
configure_on_duplicate_update_logic
|
|
45
45
|
ensure_valid_options_for_connection!
|
|
@@ -48,7 +48,7 @@ module ActiveRecord
|
|
|
48
48
|
def execute
|
|
49
49
|
return ActiveRecord::Result.empty if inserts.empty?
|
|
50
50
|
|
|
51
|
-
message = +"#{model
|
|
51
|
+
message = +"#{model} "
|
|
52
52
|
message << "Bulk " if inserts.many?
|
|
53
53
|
message << (on_duplicate == :update ? "Upsert" : "Insert")
|
|
54
54
|
connection.exec_insert_all to_sql, message
|
|
@@ -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
|
|
@@ -101,6 +101,13 @@ module ActiveRecord
|
|
|
101
101
|
attribute_names = attribute_names.dup if attribute_names.frozen?
|
|
102
102
|
attribute_names << locking_column
|
|
103
103
|
|
|
104
|
+
if self[locking_column].nil?
|
|
105
|
+
raise(<<-MSG.squish)
|
|
106
|
+
For optimistic locking, locking_column ('#{locking_column}') can't be nil.
|
|
107
|
+
Are you missing a default value or validation on '#{locking_column}'?
|
|
108
|
+
MSG
|
|
109
|
+
end
|
|
110
|
+
|
|
104
111
|
self[locking_column] += 1
|
|
105
112
|
|
|
106
113
|
affected_rows = self.class._update_record(
|
|
@@ -67,6 +67,10 @@ module ActiveRecord
|
|
|
67
67
|
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
|
|
68
68
|
# the locked record.
|
|
69
69
|
def lock!(lock = true)
|
|
70
|
+
if self.class.current_preventing_writes
|
|
71
|
+
raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
|
|
72
|
+
end
|
|
73
|
+
|
|
70
74
|
if persisted?
|
|
71
75
|
if has_changes_to_save?
|
|
72
76
|
raise(<<-MSG.squish)
|
|
@@ -79,6 +83,7 @@ module ActiveRecord
|
|
|
79
83
|
|
|
80
84
|
reload(lock: lock)
|
|
81
85
|
end
|
|
86
|
+
|
|
82
87
|
self
|
|
83
88
|
end
|
|
84
89
|
|
|
@@ -127,11 +127,7 @@ module ActiveRecord
|
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
def query_source_location
|
|
130
|
-
|
|
131
|
-
frame = backtrace_cleaner.clean_frame(location)
|
|
132
|
-
return frame if frame
|
|
133
|
-
end
|
|
134
|
-
nil
|
|
130
|
+
backtrace_cleaner.first_clean_frame
|
|
135
131
|
end
|
|
136
132
|
|
|
137
133
|
def filter(name, value)
|