activerecord 7.2.2.1 → 8.1.2
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 +564 -753
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +35 -11
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- 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_association.rb +10 -8
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +17 -4
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +40 -26
- data/lib/active_record/autosave_association.rb +91 -39
- data/lib/active_record/base.rb +3 -4
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
- 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/quoting.rb +7 -9
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- 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/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +37 -10
- data/lib/active_record/core.rb +61 -25
- data/lib/active_record/counter_cache.rb +34 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +67 -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 +19 -19
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +9 -9
- data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
- data/lib/active_record/encryption/encryptor.rb +49 -28
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +46 -42
- data/lib/active_record/errors.rb +36 -12
- 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/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +13 -9
- 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 +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -13
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +44 -11
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +50 -43
- data/lib/active_record/model_schema.rb +38 -13
- data/lib/active_record/nested_attributes.rb +6 -6
- data/lib/active_record/persistence.rb +162 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +104 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +12 -12
- data/lib/active_record/railtie.rb +37 -32
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -37
- 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 +53 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +80 -63
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +54 -37
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +22 -7
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +156 -95
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +10 -11
- data/lib/active_record/relation.rb +122 -80
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema_dumper.rb +47 -22
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +47 -18
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +39 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +39 -16
- 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/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +85 -50
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +13 -4
- data/lib/arel/table.rb +3 -7
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +6 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +17 -17
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
data/lib/active_record/enum.rb
CHANGED
|
@@ -119,7 +119,18 @@ module ActiveRecord
|
|
|
119
119
|
# enum :status, [ :active, :archived ], instance_methods: false
|
|
120
120
|
# end
|
|
121
121
|
#
|
|
122
|
-
#
|
|
122
|
+
# By default, an +ArgumentError+ will be raised when assigning an invalid value:
|
|
123
|
+
#
|
|
124
|
+
# class Conversation < ActiveRecord::Base
|
|
125
|
+
# enum :status, [ :active, :archived ]
|
|
126
|
+
# end
|
|
127
|
+
#
|
|
128
|
+
# conversation = Conversation.new
|
|
129
|
+
#
|
|
130
|
+
# conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)
|
|
131
|
+
#
|
|
132
|
+
# If, instead, you want the enum value to be validated before saving, use the
|
|
133
|
+
# +:validate+ option:
|
|
123
134
|
#
|
|
124
135
|
# class Conversation < ActiveRecord::Base
|
|
125
136
|
# enum :status, [ :active, :archived ], validate: true
|
|
@@ -136,7 +147,7 @@ module ActiveRecord
|
|
|
136
147
|
# conversation.status = :active
|
|
137
148
|
# conversation.valid? # => true
|
|
138
149
|
#
|
|
139
|
-
#
|
|
150
|
+
# You may also pass additional validation options:
|
|
140
151
|
#
|
|
141
152
|
# class Conversation < ActiveRecord::Base
|
|
142
153
|
# enum :status, [ :active, :archived ], validate: { allow_nil: true }
|
|
@@ -152,16 +163,6 @@ module ActiveRecord
|
|
|
152
163
|
#
|
|
153
164
|
# conversation.status = :active
|
|
154
165
|
# conversation.valid? # => true
|
|
155
|
-
#
|
|
156
|
-
# Otherwise +ArgumentError+ will raise:
|
|
157
|
-
#
|
|
158
|
-
# class Conversation < ActiveRecord::Base
|
|
159
|
-
# enum :status, [ :active, :archived ]
|
|
160
|
-
# end
|
|
161
|
-
#
|
|
162
|
-
# conversation = Conversation.new
|
|
163
|
-
#
|
|
164
|
-
# conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)
|
|
165
166
|
module Enum
|
|
166
167
|
def self.extended(base) # :nodoc:
|
|
167
168
|
base.class_attribute(:defined_enums, instance_writer: false, default: {})
|
|
@@ -213,34 +214,16 @@ module ActiveRecord
|
|
|
213
214
|
attr_reader :name, :mapping
|
|
214
215
|
end
|
|
215
216
|
|
|
216
|
-
def enum(name
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return _enum(name, values, **options)
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default, :_instance_methods)
|
|
223
|
-
options.transform_keys! { |key| :"#{key[1..-1]}" }
|
|
224
|
-
|
|
225
|
-
definitions.each { |name, values| _enum(name, values, **options) }
|
|
226
|
-
|
|
227
|
-
ActiveRecord.deprecator.warn(<<~MSG)
|
|
228
|
-
Defining enums with keyword arguments is deprecated and will be removed
|
|
229
|
-
in Rails 8.0. Positional arguments should be used instead:
|
|
230
|
-
|
|
231
|
-
#{definitions.map { |name, values| "enum :#{name}, #{values}" }.join("\n")}
|
|
232
|
-
MSG
|
|
217
|
+
def enum(name, values = nil, **options)
|
|
218
|
+
values, options = options, {} unless values
|
|
219
|
+
_enum(name, values, **options)
|
|
233
220
|
end
|
|
234
221
|
|
|
235
222
|
private
|
|
236
|
-
def inherited(base)
|
|
237
|
-
base.defined_enums = defined_enums.deep_dup
|
|
238
|
-
super
|
|
239
|
-
end
|
|
240
|
-
|
|
241
223
|
def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
|
|
242
|
-
assert_valid_enum_definition_values(values)
|
|
224
|
+
values = assert_valid_enum_definition_values(values)
|
|
243
225
|
assert_valid_enum_options(options)
|
|
226
|
+
|
|
244
227
|
# statuses = { }
|
|
245
228
|
enum_values = ActiveSupport::HashWithIndifferentAccess.new
|
|
246
229
|
name = name.to_s
|
|
@@ -304,6 +287,11 @@ module ActiveRecord
|
|
|
304
287
|
enum_values.freeze
|
|
305
288
|
end
|
|
306
289
|
|
|
290
|
+
def inherited(base)
|
|
291
|
+
base.defined_enums = defined_enums.deep_dup
|
|
292
|
+
super
|
|
293
|
+
end
|
|
294
|
+
|
|
307
295
|
class EnumMethods < Module # :nodoc:
|
|
308
296
|
def initialize(klass)
|
|
309
297
|
@klass = klass
|
|
@@ -354,6 +342,20 @@ module ActiveRecord
|
|
|
354
342
|
if values.keys.any?(&:blank?)
|
|
355
343
|
raise ArgumentError, "Enum values #{values} must not contain a blank name."
|
|
356
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, Float, true, false, nil
|
|
353
|
+
# noop
|
|
354
|
+
else
|
|
355
|
+
raise ArgumentError, "Enum values #{values} must be only booleans, integers, floats, symbols or strings, got: #{value.class}"
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
357
359
|
when Array
|
|
358
360
|
if values.empty?
|
|
359
361
|
raise ArgumentError, "Enum values #{values} must not be empty."
|
|
@@ -369,6 +371,8 @@ module ActiveRecord
|
|
|
369
371
|
else
|
|
370
372
|
raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array."
|
|
371
373
|
end
|
|
374
|
+
|
|
375
|
+
values
|
|
372
376
|
end
|
|
373
377
|
|
|
374
378
|
def assert_valid_enum_options(options)
|
|
@@ -380,25 +384,25 @@ module ActiveRecord
|
|
|
380
384
|
|
|
381
385
|
ENUM_CONFLICT_MESSAGE = \
|
|
382
386
|
"You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
|
|
383
|
-
"this will generate
|
|
387
|
+
"this will generate %{type} method \"%{method}\", which is already defined " \
|
|
384
388
|
"by %{source}."
|
|
385
389
|
private_constant :ENUM_CONFLICT_MESSAGE
|
|
386
390
|
|
|
387
391
|
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
|
|
388
392
|
if klass_method && dangerous_class_method?(method_name)
|
|
389
|
-
raise_conflict_error(enum_name, method_name,
|
|
393
|
+
raise_conflict_error(enum_name, method_name, "a class")
|
|
390
394
|
elsif klass_method && method_defined_within?(method_name, Relation)
|
|
391
|
-
raise_conflict_error(enum_name, method_name,
|
|
395
|
+
raise_conflict_error(enum_name, method_name, "a class", source: Relation.name)
|
|
392
396
|
elsif klass_method && method_name.to_sym == :id
|
|
393
|
-
raise_conflict_error(enum_name, method_name)
|
|
397
|
+
raise_conflict_error(enum_name, method_name, "an instance")
|
|
394
398
|
elsif !klass_method && dangerous_attribute_method?(method_name)
|
|
395
|
-
raise_conflict_error(enum_name, method_name)
|
|
399
|
+
raise_conflict_error(enum_name, method_name, "an instance")
|
|
396
400
|
elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
|
|
397
|
-
raise_conflict_error(enum_name, method_name, source: "another enum")
|
|
401
|
+
raise_conflict_error(enum_name, method_name, "an instance", source: "another enum")
|
|
398
402
|
end
|
|
399
403
|
end
|
|
400
404
|
|
|
401
|
-
def raise_conflict_error(enum_name, method_name, type
|
|
405
|
+
def raise_conflict_error(enum_name, method_name, type, source: "Active Record")
|
|
402
406
|
raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
|
|
403
407
|
enum: enum_name,
|
|
404
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.inheritance_column]
|
|
17
16
|
# points to).
|
|
18
17
|
class SubclassNotFound < ActiveRecordError
|
|
19
18
|
end
|
|
@@ -84,6 +83,19 @@ module ActiveRecord
|
|
|
84
83
|
class ConnectionTimeoutError < ConnectionNotEstablished
|
|
85
84
|
end
|
|
86
85
|
|
|
86
|
+
# Raised when a database connection pool is requested but
|
|
87
|
+
# has not been defined.
|
|
88
|
+
class ConnectionNotDefined < ConnectionNotEstablished
|
|
89
|
+
def initialize(message = nil, connection_name: nil, role: nil, shard: nil)
|
|
90
|
+
super(message)
|
|
91
|
+
@connection_name = connection_name
|
|
92
|
+
@role = role
|
|
93
|
+
@shard = shard
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
attr_reader :connection_name, :role, :shard
|
|
97
|
+
end
|
|
98
|
+
|
|
87
99
|
# Raised when connection to the database could not been established because it was not
|
|
88
100
|
# able to connect to the host or when the authorization failed.
|
|
89
101
|
class DatabaseConnectionError < ConnectionNotEstablished
|
|
@@ -280,6 +292,14 @@ module ActiveRecord
|
|
|
280
292
|
class NotNullViolation < StatementInvalid
|
|
281
293
|
end
|
|
282
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
|
+
|
|
283
303
|
# Raised when a record cannot be inserted or updated because a value too long for a column type.
|
|
284
304
|
class ValueTooLong < StatementInvalid
|
|
285
305
|
end
|
|
@@ -326,15 +346,15 @@ module ActiveRecord
|
|
|
326
346
|
class << self
|
|
327
347
|
def db_error(db_name)
|
|
328
348
|
NoDatabaseError.new(<<~MSG)
|
|
329
|
-
|
|
349
|
+
Database not found: #{db_name}. Available database configurations can be found in config/database.yml.
|
|
330
350
|
|
|
331
351
|
To resolve this error:
|
|
332
352
|
|
|
333
|
-
-
|
|
353
|
+
- Create the database by running:
|
|
334
354
|
|
|
335
355
|
bin/rails db:create
|
|
336
356
|
|
|
337
|
-
-
|
|
357
|
+
- Verify that config/database.yml contains the correct database name.
|
|
338
358
|
MSG
|
|
339
359
|
end
|
|
340
360
|
end
|
|
@@ -431,7 +451,7 @@ module ActiveRecord
|
|
|
431
451
|
UnknownAttributeError = ActiveModel::UnknownAttributeError
|
|
432
452
|
|
|
433
453
|
# Raised when an error occurred while doing a mass assignment to an attribute through the
|
|
434
|
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
|
|
454
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=] method.
|
|
435
455
|
# The exception has an +attribute+ property that is the name of the offending attribute.
|
|
436
456
|
class AttributeAssignmentError < ActiveRecordError
|
|
437
457
|
attr_reader :exception, :attribute
|
|
@@ -444,7 +464,7 @@ module ActiveRecord
|
|
|
444
464
|
end
|
|
445
465
|
|
|
446
466
|
# Raised when there are multiple errors while doing a mass assignment through the
|
|
447
|
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=]
|
|
467
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=]
|
|
448
468
|
# method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
|
|
449
469
|
# objects, each corresponding to the error while assigning to an attribute.
|
|
450
470
|
class MultiparameterAssignmentErrors < ActiveRecordError
|
|
@@ -477,6 +497,7 @@ module ActiveRecord
|
|
|
477
497
|
# end
|
|
478
498
|
#
|
|
479
499
|
# relation = Task.all
|
|
500
|
+
# relation.load
|
|
480
501
|
# relation.loaded? # => true
|
|
481
502
|
#
|
|
482
503
|
# # Methods which try to mutate a loaded relation fail.
|
|
@@ -484,11 +505,6 @@ module ActiveRecord
|
|
|
484
505
|
# relation.limit!(5) # => ActiveRecord::UnmodifiableRelation
|
|
485
506
|
class UnmodifiableRelation < ActiveRecordError
|
|
486
507
|
end
|
|
487
|
-
deprecate_constant(
|
|
488
|
-
:ImmutableRelation,
|
|
489
|
-
"ActiveRecord::UnmodifiableRelation",
|
|
490
|
-
deprecator: ActiveRecord.deprecator
|
|
491
|
-
)
|
|
492
508
|
|
|
493
509
|
# TransactionIsolationError will be raised under the following conditions:
|
|
494
510
|
#
|
|
@@ -544,6 +560,11 @@ module ActiveRecord
|
|
|
544
560
|
class Deadlocked < TransactionRollbackError
|
|
545
561
|
end
|
|
546
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
|
+
|
|
547
568
|
# IrreversibleOrderError is raised when a relation's order is too complex for
|
|
548
569
|
# +reverse_order+ to automatically reverse.
|
|
549
570
|
class IrreversibleOrderError < ActiveRecordError
|
|
@@ -601,6 +622,9 @@ module ActiveRecord
|
|
|
601
622
|
# the database version cannot be determined.
|
|
602
623
|
class DatabaseVersionError < ActiveRecordError
|
|
603
624
|
end
|
|
625
|
+
|
|
626
|
+
class DeprecatedAssociationError < ActiveRecordError
|
|
627
|
+
end
|
|
604
628
|
end
|
|
605
629
|
|
|
606
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
|
|
@@ -193,8 +193,25 @@ module ActiveRecord
|
|
|
193
193
|
|
|
194
194
|
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
|
|
195
195
|
joins = targets.map do |target|
|
|
196
|
-
join = {
|
|
197
|
-
|
|
196
|
+
join = {}
|
|
197
|
+
|
|
198
|
+
if rhs_key.is_a?(Array)
|
|
199
|
+
composite_key = ActiveRecord::FixtureSet.composite_identify(target, rhs_key)
|
|
200
|
+
composite_key.each do |column, value|
|
|
201
|
+
join[column] = value
|
|
202
|
+
end
|
|
203
|
+
else
|
|
204
|
+
join[rhs_key] = ActiveRecord::FixtureSet.identify(target, column_type)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
if lhs_key.is_a?(Array)
|
|
208
|
+
lhs_key.zip(model_metadata.primary_key_name).each do |fkey, pkey|
|
|
209
|
+
join[fkey] = @row[pkey]
|
|
210
|
+
end
|
|
211
|
+
else
|
|
212
|
+
join[lhs_key] = @row[model_metadata.primary_key_name]
|
|
213
|
+
end
|
|
214
|
+
|
|
198
215
|
association.timestamp_column_names.each do |col|
|
|
199
216
|
join[col] = @now
|
|
200
217
|
end
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "erb"
|
|
4
4
|
require "yaml"
|
|
5
|
-
require "zlib"
|
|
6
|
-
require "set"
|
|
7
5
|
require "active_support/dependencies"
|
|
8
6
|
require "active_support/core_ext/digest/uuid"
|
|
9
7
|
require "active_record/test_fixtures"
|
|
@@ -244,10 +242,10 @@ module ActiveRecord
|
|
|
244
242
|
# and one for the humans. Why don't we generate the primary key instead?
|
|
245
243
|
# Hashing each fixture's label yields a consistent ID:
|
|
246
244
|
#
|
|
247
|
-
# george: # generated id:
|
|
245
|
+
# george: # generated id: 380982691
|
|
248
246
|
# name: George the Monkey
|
|
249
247
|
#
|
|
250
|
-
# reginald: # generated id:
|
|
248
|
+
# reginald: # generated id: 41001176
|
|
251
249
|
# name: Reginald the Pirate
|
|
252
250
|
#
|
|
253
251
|
# Active Record looks at the fixture's model class, discovers the correct
|
|
@@ -100,17 +100,21 @@ module ActiveRecord
|
|
|
100
100
|
def execute_or_skip
|
|
101
101
|
return unless pending?
|
|
102
102
|
|
|
103
|
-
@
|
|
104
|
-
return unless
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
@session.synchronize do
|
|
104
|
+
return unless pending?
|
|
105
|
+
|
|
106
|
+
@pool.with_connection do |connection|
|
|
107
|
+
return unless @mutex.try_lock
|
|
108
|
+
begin
|
|
109
|
+
if pending?
|
|
110
|
+
@event_buffer = EventBuffer.new(self, @instrumenter)
|
|
111
|
+
ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer
|
|
112
|
+
|
|
109
113
|
execute_query(connection, async: true)
|
|
110
114
|
end
|
|
115
|
+
ensure
|
|
116
|
+
@mutex.unlock
|
|
111
117
|
end
|
|
112
|
-
ensure
|
|
113
|
-
@mutex.unlock
|
|
114
118
|
end
|
|
115
119
|
end
|
|
116
120
|
end
|
|
@@ -163,7 +167,7 @@ module ActiveRecord
|
|
|
163
167
|
end
|
|
164
168
|
|
|
165
169
|
def exec_query(connection, *args, **kwargs)
|
|
166
|
-
connection.
|
|
170
|
+
connection.raw_exec_query(*args, **kwargs)
|
|
167
171
|
end
|
|
168
172
|
|
|
169
173
|
class SelectAll < FutureResult # :nodoc:
|
|
@@ -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
|
|
@@ -9,7 +9,7 @@ module ActiveRecord
|
|
|
9
9
|
# it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred
|
|
10
10
|
# and the update is ignored.
|
|
11
11
|
#
|
|
12
|
-
# Check out
|
|
12
|
+
# Check out ActiveRecord::Locking::Pessimistic for an alternative.
|
|
13
13
|
#
|
|
14
14
|
# == Usage
|
|
15
15
|
#
|
|
@@ -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
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
|
-
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
4
|
+
class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
|
|
5
5
|
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
|
|
6
6
|
|
|
7
7
|
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
|
@@ -126,18 +126,8 @@ module ActiveRecord
|
|
|
126
126
|
end
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
Thread.each_caller_location do |location|
|
|
132
|
-
frame = backtrace_cleaner.clean_frame(location)
|
|
133
|
-
return frame if frame
|
|
134
|
-
end
|
|
135
|
-
nil
|
|
136
|
-
end
|
|
137
|
-
else
|
|
138
|
-
def query_source_location
|
|
139
|
-
backtrace_cleaner.clean(caller(1).lazy).first
|
|
140
|
-
end
|
|
129
|
+
def query_source_location
|
|
130
|
+
backtrace_cleaner.first_clean_frame
|
|
141
131
|
end
|
|
142
132
|
|
|
143
133
|
def filter(name, value)
|