activerecord 7.1.3.4 → 7.2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +652 -2032
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +15 -8
- data/lib/active_record/associations/belongs_to_association.rb +18 -11
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +11 -5
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +3 -3
- data/lib/active_record/associations/has_many_through_association.rb +7 -1
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -12
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +62 -289
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +2 -2
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +4 -16
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +89 -58
- data/lib/active_record/attributes.rb +61 -47
- data/lib/active_record/autosave_association.rb +17 -31
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -58
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +190 -75
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +23 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -19
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +18 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -77
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +93 -40
- data/lib/active_record/counter_cache.rb +23 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +19 -4
- data/lib/active_record/database_configurations/hash_config.rb +44 -36
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +30 -6
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +3 -3
- data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +8 -4
- data/lib/active_record/encryption.rb +2 -0
- data/lib/active_record/enum.rb +19 -2
- data/lib/active_record/errors.rb +46 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +17 -4
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +4 -1
- data/lib/active_record/message_pack.rb +2 -2
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +11 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +85 -76
- data/lib/active_record/model_schema.rb +38 -70
- data/lib/active_record/nested_attributes.rb +24 -5
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +32 -354
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/query_logs_formatter.rb +1 -1
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +50 -68
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +42 -45
- data/lib/active_record/reflection.rb +106 -38
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +96 -63
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +245 -65
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +500 -66
- data/lib/active_record/result.rb +32 -45
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/scoping/named.rb +1 -0
- data/lib/active_record/signed_id.rb +20 -1
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +98 -48
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +87 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +5 -3
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +70 -14
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +150 -41
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +31 -17
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +21 -15
@@ -6,12 +6,13 @@ module ActiveRecord
|
|
6
6
|
# See ActiveRecord::Attributes::ClassMethods for documentation
|
7
7
|
module Attributes
|
8
8
|
extend ActiveSupport::Concern
|
9
|
+
include ActiveModel::AttributeRegistration
|
9
10
|
|
10
|
-
included do
|
11
|
-
class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
|
12
|
-
end
|
13
11
|
# = Active Record \Attributes
|
14
12
|
module ClassMethods
|
13
|
+
# :method: attribute
|
14
|
+
# :call-seq: attribute(name, cast_type = nil, **options)
|
15
|
+
#
|
15
16
|
# Defines an attribute with a type on this model. It will override the
|
16
17
|
# type of existing attributes if needed. This allows control over how
|
17
18
|
# values are converted to and from SQL when assigned to a model. It also
|
@@ -24,15 +25,17 @@ module ActiveRecord
|
|
24
25
|
# column which this will persist to.
|
25
26
|
#
|
26
27
|
# +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
|
27
|
-
# to be used for this attribute.
|
28
|
-
#
|
28
|
+
# to be used for this attribute. If this parameter is not passed, the previously
|
29
|
+
# defined type (if any) will be used.
|
30
|
+
# Otherwise, the type will be ActiveModel::Type::Value.
|
31
|
+
# See the examples below for more information about providing custom type objects.
|
29
32
|
#
|
30
33
|
# ==== Options
|
31
34
|
#
|
32
35
|
# The following options are accepted:
|
33
36
|
#
|
34
37
|
# +default+ The default value to use when no value is provided. If this option
|
35
|
-
# is not passed, the
|
38
|
+
# is not passed, the previously defined default value (if any) on the superclass or in the schema will be used.
|
36
39
|
# Otherwise, the default will be +nil+.
|
37
40
|
#
|
38
41
|
# +array+ (PostgreSQL only) specifies that the type should be an array (see the
|
@@ -134,7 +137,7 @@ module ActiveRecord
|
|
134
137
|
# expected API. It is recommended that your type objects inherit from an
|
135
138
|
# existing type, or from ActiveRecord::Type::Value
|
136
139
|
#
|
137
|
-
# class
|
140
|
+
# class PriceType < ActiveRecord::Type::Integer
|
138
141
|
# def cast(value)
|
139
142
|
# if !value.kind_of?(Numeric) && value.include?('$')
|
140
143
|
# price_in_dollars = value.gsub(/\$/, '').to_f
|
@@ -146,11 +149,11 @@ module ActiveRecord
|
|
146
149
|
# end
|
147
150
|
#
|
148
151
|
# # config/initializers/types.rb
|
149
|
-
# ActiveRecord::Type.register(:
|
152
|
+
# ActiveRecord::Type.register(:price, PriceType)
|
150
153
|
#
|
151
154
|
# # app/models/store_listing.rb
|
152
155
|
# class StoreListing < ActiveRecord::Base
|
153
|
-
# attribute :price_in_cents, :
|
156
|
+
# attribute :price_in_cents, :price
|
154
157
|
# end
|
155
158
|
#
|
156
159
|
# store_listing = StoreListing.new(price_in_cents: '$10.00')
|
@@ -170,7 +173,7 @@ module ActiveRecord
|
|
170
173
|
# class Money < Struct.new(:amount, :currency)
|
171
174
|
# end
|
172
175
|
#
|
173
|
-
# class
|
176
|
+
# class PriceType < ActiveRecord::Type::Value
|
174
177
|
# def initialize(currency_converter:)
|
175
178
|
# @currency_converter = currency_converter
|
176
179
|
# end
|
@@ -185,12 +188,12 @@ module ActiveRecord
|
|
185
188
|
# end
|
186
189
|
#
|
187
190
|
# # config/initializers/types.rb
|
188
|
-
# ActiveRecord::Type.register(:
|
191
|
+
# ActiveRecord::Type.register(:price, PriceType)
|
189
192
|
#
|
190
193
|
# # app/models/product.rb
|
191
194
|
# class Product < ActiveRecord::Base
|
192
195
|
# currency_converter = ConversionRatesFromTheInternet.new
|
193
|
-
# attribute :price_in_bitcoins, :
|
196
|
+
# attribute :price_in_bitcoins, :price, currency_converter: currency_converter
|
194
197
|
# end
|
195
198
|
#
|
196
199
|
# Product.where(price_in_bitcoins: Money.new(5, "USD"))
|
@@ -205,37 +208,12 @@ module ActiveRecord
|
|
205
208
|
# tracking is performed. The methods +changed?+ and +changed_in_place?+
|
206
209
|
# will be called from ActiveModel::Dirty. See the documentation for those
|
207
210
|
# methods in ActiveModel::Type::Value for more details.
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
reload_schema_from_cache
|
213
|
-
|
214
|
-
case cast_type
|
215
|
-
when Symbol
|
216
|
-
cast_type = Type.lookup(cast_type, **options, adapter: Type.adapter_name_from(self))
|
217
|
-
when nil
|
218
|
-
if (prev_cast_type, prev_default = attributes_to_define_after_schema_loads[name])
|
219
|
-
default = prev_default if default == NO_DEFAULT_PROVIDED
|
220
|
-
else
|
221
|
-
prev_cast_type = -> subtype { subtype }
|
222
|
-
end
|
223
|
-
|
224
|
-
cast_type = if block_given?
|
225
|
-
-> subtype { yield Proc === prev_cast_type ? prev_cast_type[subtype] : prev_cast_type }
|
226
|
-
else
|
227
|
-
prev_cast_type
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
self.attributes_to_define_after_schema_loads =
|
232
|
-
attributes_to_define_after_schema_loads.merge(name => [cast_type, default])
|
233
|
-
end
|
211
|
+
#
|
212
|
+
#--
|
213
|
+
# Implemented by ActiveModel::AttributeRegistration#attribute.
|
234
214
|
|
235
|
-
# This
|
236
|
-
#
|
237
|
-
# waiting for the schema to load. Automatic schema detection and
|
238
|
-
# ClassMethods#attribute both call this under the hood. While this method
|
215
|
+
# This API only accepts type objects, and will do its work immediately instead of
|
216
|
+
# waiting for the schema to load. While this method
|
239
217
|
# is provided so it can be used by plugin authors, application code
|
240
218
|
# should probably use ClassMethods#attribute.
|
241
219
|
#
|
@@ -260,14 +238,38 @@ module ActiveRecord
|
|
260
238
|
define_default_attribute(name, default, cast_type, from_user: user_provided_default)
|
261
239
|
end
|
262
240
|
|
263
|
-
def
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
241
|
+
def _default_attributes # :nodoc:
|
242
|
+
@default_attributes ||= begin
|
243
|
+
attributes_hash = with_connection do |connection|
|
244
|
+
columns_hash.transform_values do |column|
|
245
|
+
ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
attribute_set = ActiveModel::AttributeSet.new(attributes_hash)
|
250
|
+
apply_pending_attribute_modifications(attribute_set)
|
251
|
+
attribute_set
|
268
252
|
end
|
269
253
|
end
|
270
254
|
|
255
|
+
##
|
256
|
+
# :method: type_for_attribute
|
257
|
+
# :call-seq: type_for_attribute(attribute_name, &block)
|
258
|
+
#
|
259
|
+
# See ActiveModel::Attributes::ClassMethods#type_for_attribute.
|
260
|
+
#
|
261
|
+
# This method will access the database and load the model's schema if
|
262
|
+
# necessary.
|
263
|
+
#--
|
264
|
+
# Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute.
|
265
|
+
|
266
|
+
##
|
267
|
+
protected
|
268
|
+
def reload_schema_from_cache(*)
|
269
|
+
reset_default_attributes!
|
270
|
+
super
|
271
|
+
end
|
272
|
+
|
271
273
|
private
|
272
274
|
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
273
275
|
private_constant :NO_DEFAULT_PROVIDED
|
@@ -287,6 +289,18 @@ module ActiveRecord
|
|
287
289
|
end
|
288
290
|
_default_attributes[name] = default_attribute
|
289
291
|
end
|
292
|
+
|
293
|
+
def reset_default_attributes
|
294
|
+
reload_schema_from_cache
|
295
|
+
end
|
296
|
+
|
297
|
+
def resolve_type_name(name, **options)
|
298
|
+
Type.lookup(name, **options, adapter: Type.adapter_name_from(self))
|
299
|
+
end
|
300
|
+
|
301
|
+
def type_for_column(connection, column)
|
302
|
+
hook_attribute_type(column.name, super)
|
303
|
+
end
|
290
304
|
end
|
291
305
|
end
|
292
306
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_record/associations/nested_error"
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
# = Active Record Autosave Association
|
5
7
|
#
|
@@ -315,7 +317,7 @@ module ActiveRecord
|
|
315
317
|
def validate_single_association(reflection)
|
316
318
|
association = association_instance_get(reflection.name)
|
317
319
|
record = association && association.reader
|
318
|
-
association_valid?(
|
320
|
+
association_valid?(association, record) if record && (record.changed_for_autosave? || custom_validation_context?)
|
319
321
|
end
|
320
322
|
|
321
323
|
# Validate the associated records if <tt>:validate</tt> or
|
@@ -324,7 +326,7 @@ module ActiveRecord
|
|
324
326
|
def validate_collection_association(reflection)
|
325
327
|
if association = association_instance_get(reflection.name)
|
326
328
|
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
|
327
|
-
records.
|
329
|
+
records.each { |record| association_valid?(association, record) }
|
328
330
|
end
|
329
331
|
end
|
330
332
|
end
|
@@ -332,40 +334,25 @@ module ActiveRecord
|
|
332
334
|
# Returns whether or not the association is valid and applies any errors to
|
333
335
|
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
|
334
336
|
# enabled records if they're marked_for_destruction? or destroyed.
|
335
|
-
def association_valid?(
|
336
|
-
return true if record.destroyed? || (
|
337
|
+
def association_valid?(association, record)
|
338
|
+
return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
|
337
339
|
|
338
340
|
context = validation_context if custom_validation_context?
|
339
341
|
|
340
342
|
unless valid = record.valid?(context)
|
341
|
-
if
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
errors.each { |error|
|
348
|
-
self.errors.import(
|
349
|
-
error,
|
350
|
-
attribute: attribute
|
351
|
-
)
|
352
|
-
}
|
343
|
+
if association.options[:autosave]
|
344
|
+
record.errors.each { |error|
|
345
|
+
self.errors.objects.append(
|
346
|
+
Associations::NestedError.new(association, error)
|
347
|
+
)
|
353
348
|
}
|
354
349
|
else
|
355
|
-
errors.add(reflection.name)
|
350
|
+
errors.add(association.reflection.name)
|
356
351
|
end
|
357
352
|
end
|
358
353
|
valid
|
359
354
|
end
|
360
355
|
|
361
|
-
def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
|
362
|
-
if indexed_attribute
|
363
|
-
"#{reflection.name}[#{index}].#{attribute}"
|
364
|
-
else
|
365
|
-
"#{reflection.name}.#{attribute}"
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
356
|
# Is used as an around_save callback to check while saving a collection
|
370
357
|
# association whether or not the parent was a new record before saving.
|
371
358
|
def around_save_collection_association
|
@@ -441,7 +428,9 @@ module ActiveRecord
|
|
441
428
|
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
442
429
|
def save_has_one_association(reflection)
|
443
430
|
association = association_instance_get(reflection.name)
|
444
|
-
|
431
|
+
return unless association && association.loaded?
|
432
|
+
|
433
|
+
record = association.load_target
|
445
434
|
|
446
435
|
if record && !record.destroyed?
|
447
436
|
autosave = reflection.options[:autosave]
|
@@ -458,7 +447,8 @@ module ActiveRecord
|
|
458
447
|
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
459
448
|
|
460
449
|
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
|
461
|
-
|
450
|
+
association_id = _read_attribute(primary_key)
|
451
|
+
record[foreign_key] = association_id unless record[foreign_key] == association_id
|
462
452
|
end
|
463
453
|
association.set_inverse_instance(record)
|
464
454
|
end
|
@@ -547,10 +537,6 @@ module ActiveRecord
|
|
547
537
|
end
|
548
538
|
end
|
549
539
|
|
550
|
-
def custom_validation_context?
|
551
|
-
validation_context && [:create, :update].exclude?(validation_context)
|
552
|
-
end
|
553
|
-
|
554
540
|
def _ensure_no_duplicate_errors
|
555
541
|
errors.uniq!
|
556
542
|
end
|
data/lib/active_record/base.rb
CHANGED
@@ -233,7 +233,7 @@ module ActiveRecord # :nodoc:
|
|
233
233
|
#
|
234
234
|
# Connections are usually created through
|
235
235
|
# {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] and retrieved
|
236
|
-
# by ActiveRecord::Base.
|
236
|
+
# by ActiveRecord::Base.lease_connection. All classes inheriting from ActiveRecord::Base will use this
|
237
237
|
# connection. But you can also set a class-specific connection. For example, if Course is an
|
238
238
|
# ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
|
239
239
|
# and Course and all of its subclasses will use this connection instead.
|
@@ -280,7 +280,7 @@ module ActiveRecord # :nodoc:
|
|
280
280
|
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
|
281
281
|
# instances in the current object space.
|
282
282
|
class Base
|
283
|
-
|
283
|
+
include ActiveModel::API
|
284
284
|
|
285
285
|
extend ActiveSupport::Benchmarkable
|
286
286
|
extend ActiveSupport::DescendantsTracker
|
@@ -304,7 +304,6 @@ module ActiveRecord # :nodoc:
|
|
304
304
|
include Scoping
|
305
305
|
include Sanitization
|
306
306
|
include AttributeAssignment
|
307
|
-
include ActiveModel::Conversion
|
308
307
|
include Integration
|
309
308
|
include Validations
|
310
309
|
include CounterCache
|
@@ -418,7 +418,7 @@ module ActiveRecord
|
|
418
418
|
|
419
419
|
def destroy # :nodoc:
|
420
420
|
@_destroy_callback_already_called ||= false
|
421
|
-
return if @_destroy_callback_already_called
|
421
|
+
return true if @_destroy_callback_already_called
|
422
422
|
@_destroy_callback_already_called = true
|
423
423
|
_run_destroy_callbacks { super }
|
424
424
|
rescue RecordNotDestroyed => e
|
@@ -55,9 +55,6 @@ module ActiveRecord
|
|
55
55
|
# about the model. The model needs to pass a connection specification name to the handler,
|
56
56
|
# in order to look up the correct connection pool.
|
57
57
|
class ConnectionHandler
|
58
|
-
FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
|
59
|
-
private_constant :FINALIZER
|
60
|
-
|
61
58
|
class StringConnectionName # :nodoc:
|
62
59
|
attr_reader :name
|
63
60
|
|
@@ -77,9 +74,6 @@ module ActiveRecord
|
|
77
74
|
def initialize
|
78
75
|
# These caches are keyed by pool_config.connection_name (PoolConfig#connection_name).
|
79
76
|
@connection_name_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
|
80
|
-
|
81
|
-
# Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
|
82
|
-
ObjectSpace.define_finalizer self, FINALIZER
|
83
77
|
end
|
84
78
|
|
85
79
|
def prevent_writes # :nodoc:
|
@@ -94,22 +88,10 @@ module ActiveRecord
|
|
94
88
|
connection_name_to_pool_manager.keys
|
95
89
|
end
|
96
90
|
|
97
|
-
|
98
|
-
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
99
|
-
The `all_connection_pools` method is deprecated in favor of `connection_pool_list`.
|
100
|
-
Call `connection_pool_list(:all)` to get the same behavior as `all_connection_pools`.
|
101
|
-
MSG
|
102
|
-
connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
|
103
|
-
end
|
104
|
-
|
105
|
-
# Returns the pools for a connection handler and given role. If +:all+ is passed,
|
91
|
+
# Returns the pools for a connection handler and given role. If +:all+ is passed,
|
106
92
|
# all pools belonging to the connection handler will be returned.
|
107
93
|
def connection_pool_list(role = nil)
|
108
|
-
if role.nil?
|
109
|
-
deprecation_for_pool_handling(__method__)
|
110
|
-
role = ActiveRecord::Base.current_role
|
111
|
-
connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
|
112
|
-
elsif role == :all
|
94
|
+
if role.nil? || role == :all
|
113
95
|
connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
|
114
96
|
else
|
115
97
|
connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
|
@@ -171,11 +153,6 @@ module ActiveRecord
|
|
171
153
|
# Returns true if there are any active connections among the connection
|
172
154
|
# pools that the ConnectionHandler is managing.
|
173
155
|
def active_connections?(role = nil)
|
174
|
-
if role.nil?
|
175
|
-
deprecation_for_pool_handling(__method__)
|
176
|
-
role = ActiveRecord::Base.current_role
|
177
|
-
end
|
178
|
-
|
179
156
|
each_connection_pool(role).any?(&:active_connection?)
|
180
157
|
end
|
181
158
|
|
@@ -183,32 +160,20 @@ module ActiveRecord
|
|
183
160
|
# and also returns connections to the pool cached by threads that are no
|
184
161
|
# longer alive.
|
185
162
|
def clear_active_connections!(role = nil)
|
186
|
-
|
187
|
-
|
188
|
-
|
163
|
+
each_connection_pool(role).each do |pool|
|
164
|
+
pool.release_connection
|
165
|
+
pool.disable_query_cache!
|
189
166
|
end
|
190
|
-
|
191
|
-
each_connection_pool(role).each(&:release_connection)
|
192
167
|
end
|
193
168
|
|
194
169
|
# Clears the cache which maps classes.
|
195
170
|
#
|
196
171
|
# See ConnectionPool#clear_reloadable_connections! for details.
|
197
172
|
def clear_reloadable_connections!(role = nil)
|
198
|
-
if role.nil?
|
199
|
-
deprecation_for_pool_handling(__method__)
|
200
|
-
role = ActiveRecord::Base.current_role
|
201
|
-
end
|
202
|
-
|
203
173
|
each_connection_pool(role).each(&:clear_reloadable_connections!)
|
204
174
|
end
|
205
175
|
|
206
176
|
def clear_all_connections!(role = nil)
|
207
|
-
if role.nil?
|
208
|
-
deprecation_for_pool_handling(__method__)
|
209
|
-
role = ActiveRecord::Base.current_role
|
210
|
-
end
|
211
|
-
|
212
177
|
each_connection_pool(role).each(&:disconnect!)
|
213
178
|
end
|
214
179
|
|
@@ -216,11 +181,6 @@ module ActiveRecord
|
|
216
181
|
#
|
217
182
|
# See ConnectionPool#flush! for details.
|
218
183
|
def flush_idle_connections!(role = nil)
|
219
|
-
if role.nil?
|
220
|
-
deprecation_for_pool_handling(__method__)
|
221
|
-
role = ActiveRecord::Base.current_role
|
222
|
-
end
|
223
|
-
|
224
184
|
each_connection_pool(role).each(&:flush!)
|
225
185
|
end
|
226
186
|
|
@@ -229,21 +189,8 @@ module ActiveRecord
|
|
229
189
|
# opened and set as the active connection for the class it was defined
|
230
190
|
# for (not necessarily the current class).
|
231
191
|
def retrieve_connection(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
|
232
|
-
pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
|
233
|
-
|
234
|
-
unless pool
|
235
|
-
if shard != ActiveRecord::Base.default_shard
|
236
|
-
message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
|
237
|
-
elsif role != ActiveRecord::Base.default_role
|
238
|
-
message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
|
239
|
-
else
|
240
|
-
message = "No connection pool for '#{connection_name}' found."
|
241
|
-
end
|
242
|
-
|
243
|
-
raise ConnectionNotEstablished, message
|
244
|
-
end
|
245
|
-
|
246
|
-
pool.connection
|
192
|
+
pool = retrieve_connection_pool(connection_name, role: role, shard: shard, strict: true)
|
193
|
+
pool.lease_connection
|
247
194
|
end
|
248
195
|
|
249
196
|
# Returns true if a connection that's accessible to this class has
|
@@ -262,9 +209,22 @@ module ActiveRecord
|
|
262
209
|
# Retrieving the connection pool happens a lot, so we cache it in @connection_name_to_pool_manager.
|
263
210
|
# This makes retrieving the connection pool O(1) once the process is warm.
|
264
211
|
# When a connection is established or removed, we invalidate the cache.
|
265
|
-
def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
|
266
|
-
|
267
|
-
|
212
|
+
def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
|
213
|
+
pool = get_pool_manager(connection_name)&.get_pool_config(role, shard)&.pool
|
214
|
+
|
215
|
+
if strict && !pool
|
216
|
+
if shard != ActiveRecord::Base.default_shard
|
217
|
+
message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
|
218
|
+
elsif role != ActiveRecord::Base.default_role
|
219
|
+
message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
|
220
|
+
else
|
221
|
+
message = "No connection pool for '#{connection_name}' found."
|
222
|
+
end
|
223
|
+
|
224
|
+
raise ConnectionNotEstablished, message
|
225
|
+
end
|
226
|
+
|
227
|
+
pool
|
268
228
|
end
|
269
229
|
|
270
230
|
private
|
@@ -284,23 +244,6 @@ module ActiveRecord
|
|
284
244
|
connection_name_to_pool_manager.values
|
285
245
|
end
|
286
246
|
|
287
|
-
def deprecation_for_pool_handling(method)
|
288
|
-
roles = []
|
289
|
-
pool_managers.each do |pool_manager|
|
290
|
-
roles << pool_manager.role_names
|
291
|
-
end
|
292
|
-
|
293
|
-
if roles.flatten.uniq.count > 1
|
294
|
-
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
295
|
-
`#{method}` currently only applies to connection pools in the current
|
296
|
-
role (`#{ActiveRecord::Base.current_role}`). In Rails 7.2, this method
|
297
|
-
will apply to all known pools, regardless of role. To affect only those
|
298
|
-
connections belonging to a specific role, pass the role name as an
|
299
|
-
argument. To switch to the new behavior, pass `:all` as the role name.
|
300
|
-
MSG
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
247
|
def disconnect_pool_from_pool_manager(pool_manager, role, shard)
|
305
248
|
pool_config = pool_manager.remove_pool_config(role, shard)
|
306
249
|
|
@@ -322,34 +265,8 @@ module ActiveRecord
|
|
322
265
|
#
|
323
266
|
def resolve_pool_config(config, connection_name, role, shard)
|
324
267
|
db_config = Base.configurations.resolve(config)
|
325
|
-
|
268
|
+
db_config.validate!
|
326
269
|
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
|
327
|
-
|
328
|
-
# Require the adapter itself and give useful feedback about
|
329
|
-
# 1. Missing adapter gems and
|
330
|
-
# 2. Adapter gems' missing dependencies.
|
331
|
-
path_to_adapter = "active_record/connection_adapters/#{db_config.adapter}_adapter"
|
332
|
-
begin
|
333
|
-
require path_to_adapter
|
334
|
-
rescue LoadError => e
|
335
|
-
# We couldn't require the adapter itself. Raise an exception that
|
336
|
-
# points out config typos and missing gems.
|
337
|
-
if e.path == path_to_adapter
|
338
|
-
# We can assume that a non-builtin adapter was specified, so it's
|
339
|
-
# either misspelled or missing from Gemfile.
|
340
|
-
raise LoadError, "Could not load the '#{db_config.adapter}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
|
341
|
-
|
342
|
-
# Bubbled up from the adapter require. Prefix the exception message
|
343
|
-
# with some guidance about how to address it and reraise.
|
344
|
-
else
|
345
|
-
raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
|
346
|
-
end
|
347
|
-
end
|
348
|
-
|
349
|
-
unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
|
350
|
-
raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
|
351
|
-
end
|
352
|
-
|
353
270
|
ConnectionAdapters::PoolConfig.new(connection_name, db_config, role, shard)
|
354
271
|
end
|
355
272
|
|
@@ -43,6 +43,7 @@ module ActiveRecord
|
|
43
43
|
# Advise multi-threaded app servers to ignore this thread for
|
44
44
|
# the purposes of fork safety warnings
|
45
45
|
Thread.current.thread_variable_set(:fork_safe, true)
|
46
|
+
Thread.current.name = "AR Pool Reaper"
|
46
47
|
running = true
|
47
48
|
while running
|
48
49
|
sleep t
|