activerecord 7.1.5.1 → 8.0.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 +369 -2484
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +21 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +7 -6
- 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 +17 -9
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- 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 +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +92 -295
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- 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/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +63 -49
- data/lib/active_record/autosave_association.rb +92 -57
- 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 +48 -122
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
- data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
- data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
- data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- 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/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
- data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +65 -0
- data/lib/active_record/connection_handling.rb +74 -37
- data/lib/active_record/core.rb +132 -51
- data/lib/active_record/counter_cache.rb +19 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +23 -4
- data/lib/active_record/database_configurations/hash_config.rb +46 -34
- 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 +41 -17
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +7 -7
- data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
- data/lib/active_record/encryption/encryptor.rb +28 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- 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 -1
- data/lib/active_record/enum.rb +20 -16
- data/lib/active_record/errors.rb +54 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -33
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +19 -16
- 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 +5 -32
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +33 -14
- data/lib/active_record/migration/compatibility.rb +8 -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 +104 -98
- data/lib/active_record/model_schema.rb +32 -70
- data/lib/active_record/nested_attributes.rb +15 -9
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +127 -451
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +104 -37
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +24 -12
- data/lib/active_record/railtie.rb +26 -68
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +43 -61
- data/lib/active_record/reflection.rb +112 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +138 -72
- data/lib/active_record/relation/calculations.rb +122 -82
- data/lib/active_record/relation/delegation.rb +30 -22
- data/lib/active_record/relation/finder_methods.rb +32 -18
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +16 -3
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +317 -101
- data/lib/active_record/relation/spawn_methods.rb +3 -19
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +561 -119
- data/lib/active_record/result.rb +95 -46
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +31 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +53 -20
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/signed_id.rb +24 -4
- data/lib/active_record/statement_cache.rb +19 -19
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +2 -13
- data/lib/active_record/tasks/database_tasks.rb +87 -58
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
- data/lib/active_record/test_fixtures.rb +98 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +2 -2
- 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 +72 -17
- 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 +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +138 -57
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/nodes/binary.rb +1 -7
- 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 +5 -4
- data/lib/arel/nodes/sql_literal.rb +8 -1
- 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/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- 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 +29 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +18 -16
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -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,13 +173,13 @@ 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
|
177
180
|
#
|
178
|
-
# # value will be the result of
|
179
|
-
# #
|
181
|
+
# # value will be the result of #deserialize or
|
182
|
+
# # #cast. Assumed to be an instance of Money in
|
180
183
|
# # this case.
|
181
184
|
# def serialize(value)
|
182
185
|
# value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
|
@@ -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
|
#
|
@@ -219,8 +221,10 @@ module ActiveRecord
|
|
219
221
|
if reflection.validate? && !method_defined?(validation_method)
|
220
222
|
if reflection.collection?
|
221
223
|
method = :validate_collection_association
|
224
|
+
elsif reflection.has_one?
|
225
|
+
method = :validate_has_one_association
|
222
226
|
else
|
223
|
-
method = :
|
227
|
+
method = :validate_belongs_to_association
|
224
228
|
end
|
225
229
|
|
226
230
|
define_non_cyclic_method(validation_method) { send(method, reflection) }
|
@@ -272,6 +276,16 @@ module ActiveRecord
|
|
272
276
|
new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
|
273
277
|
end
|
274
278
|
|
279
|
+
def validating_belongs_to_for?(association)
|
280
|
+
@validating_belongs_to_for ||= {}
|
281
|
+
@validating_belongs_to_for[association]
|
282
|
+
end
|
283
|
+
|
284
|
+
def autosaving_belongs_to_for?(association)
|
285
|
+
@autosaving_belongs_to_for ||= {}
|
286
|
+
@autosaving_belongs_to_for[association]
|
287
|
+
end
|
288
|
+
|
275
289
|
private
|
276
290
|
def init_internals
|
277
291
|
super
|
@@ -311,11 +325,33 @@ module ActiveRecord
|
|
311
325
|
end
|
312
326
|
|
313
327
|
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
|
314
|
-
# turned on for the association.
|
315
|
-
def
|
328
|
+
# turned on for the has_one association.
|
329
|
+
def validate_has_one_association(reflection)
|
316
330
|
association = association_instance_get(reflection.name)
|
317
331
|
record = association && association.reader
|
318
|
-
|
332
|
+
return unless record && (record.changed_for_autosave? || custom_validation_context?)
|
333
|
+
|
334
|
+
inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
|
335
|
+
return if inverse_association && (record.validating_belongs_to_for?(inverse_association) ||
|
336
|
+
record.autosaving_belongs_to_for?(inverse_association))
|
337
|
+
|
338
|
+
association_valid?(association, record)
|
339
|
+
end
|
340
|
+
|
341
|
+
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
|
342
|
+
# turned on for the belongs_to association.
|
343
|
+
def validate_belongs_to_association(reflection)
|
344
|
+
association = association_instance_get(reflection.name)
|
345
|
+
record = association && association.reader
|
346
|
+
return unless record && (record.changed_for_autosave? || custom_validation_context?)
|
347
|
+
|
348
|
+
begin
|
349
|
+
@validating_belongs_to_for ||= {}
|
350
|
+
@validating_belongs_to_for[association] = true
|
351
|
+
association_valid?(association, record)
|
352
|
+
ensure
|
353
|
+
@validating_belongs_to_for[association] = false
|
354
|
+
end
|
319
355
|
end
|
320
356
|
|
321
357
|
# Validate the associated records if <tt>:validate</tt> or
|
@@ -324,7 +360,7 @@ module ActiveRecord
|
|
324
360
|
def validate_collection_association(reflection)
|
325
361
|
if association = association_instance_get(reflection.name)
|
326
362
|
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
|
327
|
-
records.
|
363
|
+
records.each { |record| association_valid?(association, record) }
|
328
364
|
end
|
329
365
|
end
|
330
366
|
end
|
@@ -332,38 +368,33 @@ module ActiveRecord
|
|
332
368
|
# Returns whether or not the association is valid and applies any errors to
|
333
369
|
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
|
334
370
|
# enabled records if they're marked_for_destruction? or destroyed.
|
335
|
-
def association_valid?(
|
336
|
-
return true if record.destroyed? || (
|
371
|
+
def association_valid?(association, record)
|
372
|
+
return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
|
337
373
|
|
338
374
|
context = validation_context if custom_validation_context?
|
375
|
+
return true if record.valid?(context)
|
339
376
|
|
340
|
-
|
341
|
-
|
342
|
-
|
377
|
+
if record.changed? || record.new_record? || context
|
378
|
+
associated_errors = record.errors.objects
|
379
|
+
else
|
380
|
+
# If there are existing invalid records in the DB, we should still be able to reference them.
|
381
|
+
# Unless a record (no matter where in the association chain) is invalid and is being changed.
|
382
|
+
associated_errors = record.errors.objects.select { |error| error.is_a?(Associations::NestedError) }
|
383
|
+
end
|
343
384
|
|
344
|
-
|
345
|
-
|
385
|
+
if association.options[:autosave]
|
386
|
+
return if equal?(record)
|
346
387
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
else
|
355
|
-
errors.add(reflection.name)
|
356
|
-
end
|
388
|
+
associated_errors.each { |error|
|
389
|
+
errors.objects.append(
|
390
|
+
Associations::NestedError.new(association, error)
|
391
|
+
)
|
392
|
+
}
|
393
|
+
elsif associated_errors.any?
|
394
|
+
errors.add(association.reflection.name)
|
357
395
|
end
|
358
|
-
valid
|
359
|
-
end
|
360
396
|
|
361
|
-
|
362
|
-
if indexed_attribute
|
363
|
-
"#{reflection.name}[#{index}].#{attribute}"
|
364
|
-
else
|
365
|
-
"#{reflection.name}.#{attribute}"
|
366
|
-
end
|
397
|
+
errors.any?
|
367
398
|
end
|
368
399
|
|
369
400
|
# Is used as an around_save callback to check while saving a collection
|
@@ -444,33 +475,34 @@ module ActiveRecord
|
|
444
475
|
return unless association && association.loaded?
|
445
476
|
|
446
477
|
record = association.load_target
|
478
|
+
return unless record && !record.destroyed?
|
447
479
|
|
448
|
-
|
449
|
-
autosave = reflection.options[:autosave]
|
480
|
+
autosave = reflection.options[:autosave]
|
450
481
|
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
if (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
|
458
|
-
unless reflection.through_reflection
|
459
|
-
foreign_key = Array(reflection.foreign_key)
|
460
|
-
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
482
|
+
if autosave && record.marked_for_destruction?
|
483
|
+
record.destroy
|
484
|
+
elsif autosave != false
|
485
|
+
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
|
486
|
+
primary_key_value = primary_key.map { |key| _read_attribute(key) }
|
487
|
+
return unless (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
|
461
488
|
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
end
|
466
|
-
association.set_inverse_instance(record)
|
467
|
-
end
|
489
|
+
unless reflection.through_reflection
|
490
|
+
foreign_key = Array(reflection.foreign_key)
|
491
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
468
492
|
|
469
|
-
|
470
|
-
|
471
|
-
|
493
|
+
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
|
494
|
+
association_id = _read_attribute(primary_key)
|
495
|
+
record[foreign_key] = association_id unless record[foreign_key] == association_id
|
472
496
|
end
|
497
|
+
association.set_inverse_instance(record)
|
473
498
|
end
|
499
|
+
|
500
|
+
inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
|
501
|
+
return if inverse_association && record.autosaving_belongs_to_for?(inverse_association)
|
502
|
+
|
503
|
+
saved = record.save(validate: !autosave)
|
504
|
+
raise ActiveRecord::Rollback if !saved && autosave
|
505
|
+
saved
|
474
506
|
end
|
475
507
|
end
|
476
508
|
|
@@ -495,7 +527,6 @@ module ActiveRecord
|
|
495
527
|
return false unless reflection.inverse_of&.polymorphic?
|
496
528
|
|
497
529
|
class_name = record._read_attribute(reflection.inverse_of.foreign_type)
|
498
|
-
|
499
530
|
reflection.active_record != record.class.polymorphic_class_for(class_name)
|
500
531
|
end
|
501
532
|
|
@@ -515,7 +546,15 @@ module ActiveRecord
|
|
515
546
|
foreign_key.each { |key| self[key] = nil }
|
516
547
|
record.destroy
|
517
548
|
elsif autosave != false
|
518
|
-
saved =
|
549
|
+
saved = if record.new_record? || (autosave && record.changed_for_autosave?)
|
550
|
+
begin
|
551
|
+
@autosaving_belongs_to_for ||= {}
|
552
|
+
@autosaving_belongs_to_for[association] = true
|
553
|
+
record.save(validate: !autosave)
|
554
|
+
ensure
|
555
|
+
@autosaving_belongs_to_for[association] = false
|
556
|
+
end
|
557
|
+
end
|
519
558
|
|
520
559
|
if association.updated?
|
521
560
|
primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
|
@@ -550,10 +589,6 @@ module ActiveRecord
|
|
550
589
|
end
|
551
590
|
end
|
552
591
|
|
553
|
-
def custom_validation_context?
|
554
|
-
validation_context && [:create, :update].exclude?(validation_context)
|
555
|
-
end
|
556
|
-
|
557
592
|
def _ensure_no_duplicate_errors
|
558
593
|
errors.uniq!
|
559
594
|
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
|