activerecord 6.1.4.1 → 7.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1132 -936
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/association_relation.rb +0 -10
- data/lib/active_record/associations/association.rb +33 -17
- data/lib/active_record/associations/association_scope.rb +1 -3
- data/lib/active_record/associations/belongs_to_association.rb +15 -4
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
- data/lib/active_record/associations/builder/association.rb +8 -2
- data/lib/active_record/associations/builder/belongs_to.rb +19 -6
- data/lib/active_record/associations/builder/collection_association.rb +10 -3
- data/lib/active_record/associations/builder/has_many.rb +3 -2
- data/lib/active_record/associations/builder/has_one.rb +2 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -2
- data/lib/active_record/associations/collection_association.rb +34 -27
- data/lib/active_record/associations/collection_proxy.rb +8 -3
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +2 -1
- data/lib/active_record/associations/has_one_association.rb +10 -7
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +6 -2
- data/lib/active_record/associations/preloader/association.rb +187 -55
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +147 -0
- data/lib/active_record/associations/preloader/through_association.rb +49 -13
- data/lib/active_record/associations/preloader.rb +39 -113
- data/lib/active_record/associations/singular_association.rb +8 -2
- data/lib/active_record/associations/through_association.rb +3 -3
- data/lib/active_record/associations.rb +118 -90
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +1 -1
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
- data/lib/active_record/attribute_methods/dirty.rb +49 -16
- data/lib/active_record/attribute_methods/primary_key.rb +2 -2
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +7 -5
- data/lib/active_record/attribute_methods/serialization.rb +66 -12
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
- data/lib/active_record/attribute_methods/write.rb +7 -10
- data/lib/active_record/attribute_methods.rb +13 -14
- data/lib/active_record/attributes.rb +24 -35
- data/lib/active_record/autosave_association.rb +6 -21
- data/lib/active_record/base.rb +19 -1
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
- data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +34 -9
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +78 -22
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
- data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +97 -81
- data/lib/active_record/connection_adapters/column.rb +4 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +37 -23
- data/lib/active_record/connection_adapters/mysql/quoting.rb +35 -21
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +5 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -12
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +50 -50
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +29 -18
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +205 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +29 -4
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +27 -19
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +16 -14
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +89 -30
- data/lib/active_record/connection_adapters.rb +6 -5
- data/lib/active_record/connection_handling.rb +47 -53
- data/lib/active_record/core.rb +122 -132
- data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
- data/lib/active_record/database_configurations/database_config.rb +12 -9
- data/lib/active_record/database_configurations/hash_config.rb +63 -5
- data/lib/active_record/database_configurations/url_config.rb +2 -2
- data/lib/active_record/database_configurations.rb +16 -32
- data/lib/active_record/delegated_type.rb +52 -11
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +44 -0
- data/lib/active_record/encryption/configurable.rb +61 -0
- data/lib/active_record/encryption/context.rb +35 -0
- data/lib/active_record/encryption/contexts.rb +72 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +208 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +155 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +42 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_serializer.rb +90 -0
- data/lib/active_record/encryption/null_encryptor.rb +21 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
- data/lib/active_record/encryption/scheme.rb +99 -0
- data/lib/active_record/encryption.rb +55 -0
- data/lib/active_record/enum.rb +49 -42
- data/lib/active_record/errors.rb +67 -4
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/fixture_set/file.rb +15 -1
- data/lib/active_record/fixture_set/table_row.rb +41 -6
- data/lib/active_record/fixture_set/table_rows.rb +4 -4
- data/lib/active_record/fixtures.rb +17 -20
- data/lib/active_record/future_result.rb +139 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +55 -17
- data/lib/active_record/insert_all.rb +80 -14
- data/lib/active_record/integration.rb +4 -3
- data/lib/active_record/internal_metadata.rb +3 -5
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/optimistic.rb +10 -9
- data/lib/active_record/locking/pessimistic.rb +9 -3
- data/lib/active_record/log_subscriber.rb +14 -3
- data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
- data/lib/active_record/middleware/database_selector.rb +8 -3
- data/lib/active_record/middleware/shard_selector.rb +60 -0
- data/lib/active_record/migration/command_recorder.rb +4 -4
- data/lib/active_record/migration/compatibility.rb +107 -3
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration.rb +109 -79
- data/lib/active_record/model_schema.rb +45 -58
- data/lib/active_record/nested_attributes.rb +13 -12
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/null_relation.rb +2 -6
- data/lib/active_record/persistence.rb +219 -52
- data/lib/active_record/query_cache.rb +2 -2
- data/lib/active_record/query_logs.rb +138 -0
- data/lib/active_record/querying.rb +15 -5
- data/lib/active_record/railtie.rb +127 -17
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +66 -129
- data/lib/active_record/readonly_attributes.rb +11 -0
- data/lib/active_record/reflection.rb +67 -50
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +43 -38
- data/lib/active_record/relation/delegation.rb +6 -6
- data/lib/active_record/relation/finder_methods.rb +31 -35
- data/lib/active_record/relation/merger.rb +20 -13
- data/lib/active_record/relation/predicate_builder.rb +1 -6
- data/lib/active_record/relation/query_attribute.rb +5 -11
- data/lib/active_record/relation/query_methods.rb +243 -61
- data/lib/active_record/relation/record_fetch_warning.rb +7 -9
- data/lib/active_record/relation/spawn_methods.rb +2 -2
- data/lib/active_record/relation/where_clause.rb +10 -19
- data/lib/active_record/relation.rb +184 -84
- data/lib/active_record/result.rb +17 -7
- data/lib/active_record/runtime_registry.rb +9 -13
- data/lib/active_record/sanitization.rb +11 -7
- data/lib/active_record/schema_dumper.rb +10 -3
- data/lib/active_record/schema_migration.rb +4 -4
- data/lib/active_record/scoping/default.rb +61 -12
- data/lib/active_record/scoping/named.rb +3 -11
- data/lib/active_record/scoping.rb +64 -34
- data/lib/active_record/serialization.rb +1 -1
- data/lib/active_record/signed_id.rb +1 -1
- data/lib/active_record/suppressor.rb +11 -15
- data/lib/active_record/tasks/database_tasks.rb +120 -58
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -12
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +4 -4
- data/lib/active_record/timestamp.rb +3 -4
- data/lib/active_record/transactions.rb +9 -14
- data/lib/active_record/translation.rb +2 -2
- data/lib/active_record/type/adapter_specific_registry.rb +32 -7
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
- data/lib/active_record/type/internal/timezone.rb +2 -2
- data/lib/active_record/type/serialized.rb +1 -1
- data/lib/active_record/type/type_map.rb +17 -20
- data/lib/active_record/type.rb +1 -2
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +1 -1
- data/lib/active_record.rb +204 -28
- data/lib/arel/attributes/attribute.rb +0 -8
- data/lib/arel/crud.rb +28 -22
- data/lib/arel/delete_manager.rb +18 -4
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/casted.rb +1 -1
- data/lib/arel/nodes/delete_statement.rb +12 -13
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/update_statement.rb +8 -3
- data/lib/arel/nodes.rb +1 -0
- data/lib/arel/predications.rb +11 -3
- data/lib/arel/select_manager.rb +10 -4
- data/lib/arel/table.rb +0 -1
- data/lib/arel/tree_manager.rb +0 -12
- data/lib/arel/update_manager.rb +18 -4
- data/lib/arel/visitors/dot.rb +80 -90
- data/lib/arel/visitors/mysql.rb +8 -2
- data/lib/arel/visitors/postgresql.rb +0 -10
- data/lib/arel/visitors/to_sql.rb +58 -2
- data/lib/arel.rb +2 -1
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +59 -14
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -140,7 +140,7 @@ This would also define the following accessors: <tt>Product#name</tt> and
|
|
140
140
|
|
141
141
|
* Database agnostic schema management with Migrations.
|
142
142
|
|
143
|
-
class AddSystemSettings < ActiveRecord::Migration[
|
143
|
+
class AddSystemSettings < ActiveRecord::Migration[7.0]
|
144
144
|
def up
|
145
145
|
create_table :system_settings do |t|
|
146
146
|
t.string :name
|
@@ -264,7 +264,7 @@ module ActiveRecord
|
|
264
264
|
end
|
265
265
|
|
266
266
|
hash_from_multiparameter_assignment = part.is_a?(Hash) &&
|
267
|
-
part.
|
267
|
+
part.keys.all?(Integer)
|
268
268
|
if hash_from_multiparameter_assignment
|
269
269
|
raise ArgumentError unless part.size == part.each_key.max
|
270
270
|
part = klass.new(*part.sort.map(&:last))
|
@@ -27,16 +27,6 @@ module ActiveRecord
|
|
27
27
|
RUBY
|
28
28
|
end
|
29
29
|
|
30
|
-
def build(attributes = nil, &block)
|
31
|
-
if attributes.is_a?(Array)
|
32
|
-
attributes.collect { |attr| build(attr, &block) }
|
33
|
-
else
|
34
|
-
block = current_scope_restoring_block(&block)
|
35
|
-
scoping { _new(attributes, &block) }
|
36
|
-
end
|
37
|
-
end
|
38
|
-
alias new build
|
39
|
-
|
40
30
|
private
|
41
31
|
def _new(attributes, &block)
|
42
32
|
@association.build(attributes, &block)
|
@@ -32,8 +32,8 @@ module ActiveRecord
|
|
32
32
|
# The association of <tt>blog.posts</tt> has the object +blog+ as its
|
33
33
|
# <tt>owner</tt>, the collection of its posts as <tt>target</tt>, and
|
34
34
|
# the <tt>reflection</tt> object represents a <tt>:has_many</tt> macro.
|
35
|
-
class Association
|
36
|
-
attr_reader :owner, :target, :reflection
|
35
|
+
class Association # :nodoc:
|
36
|
+
attr_reader :owner, :target, :reflection, :disable_joins
|
37
37
|
|
38
38
|
delegate :options, to: :reflection
|
39
39
|
|
@@ -41,6 +41,7 @@ module ActiveRecord
|
|
41
41
|
reflection.check_validity!
|
42
42
|
|
43
43
|
@owner, @reflection = owner, reflection
|
44
|
+
@disable_joins = @reflection.options[:disable_joins] || false
|
44
45
|
|
45
46
|
reset
|
46
47
|
reset_scope
|
@@ -51,7 +52,6 @@ module ActiveRecord
|
|
51
52
|
@loaded = false
|
52
53
|
@target = nil
|
53
54
|
@stale_state = nil
|
54
|
-
@inversed = false
|
55
55
|
end
|
56
56
|
|
57
57
|
def reset_negative_cache # :nodoc:
|
@@ -77,7 +77,6 @@ module ActiveRecord
|
|
77
77
|
def loaded!
|
78
78
|
@loaded = true
|
79
79
|
@stale_state = stale_state
|
80
|
-
@inversed = false
|
81
80
|
end
|
82
81
|
|
83
82
|
# The target is stale if the target no longer points to the record(s) that the
|
@@ -87,7 +86,7 @@ module ActiveRecord
|
|
87
86
|
#
|
88
87
|
# Note that if the target has not been loaded, it is not considered stale.
|
89
88
|
def stale_target?
|
90
|
-
|
89
|
+
loaded? && @stale_state != stale_state
|
91
90
|
end
|
92
91
|
|
93
92
|
# Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
|
@@ -97,8 +96,12 @@ module ActiveRecord
|
|
97
96
|
end
|
98
97
|
|
99
98
|
def scope
|
100
|
-
if
|
99
|
+
if disable_joins
|
100
|
+
DisableJoinsAssociationScope.create.scope(self)
|
101
|
+
elsif (scope = klass.current_scope) && scope.try(:proxy_association) == self
|
101
102
|
scope.spawn
|
103
|
+
elsif scope = klass.global_current_scope
|
104
|
+
target_scope.merge!(association_scope).merge!(scope)
|
102
105
|
else
|
103
106
|
target_scope.merge!(association_scope)
|
104
107
|
end
|
@@ -132,15 +135,11 @@ module ActiveRecord
|
|
132
135
|
|
133
136
|
def inversed_from(record)
|
134
137
|
self.target = record
|
135
|
-
@inversed = !!record
|
136
138
|
end
|
137
139
|
|
138
140
|
def inversed_from_queries(record)
|
139
141
|
if inversable?(record)
|
140
142
|
self.target = record
|
141
|
-
@inversed = true
|
142
|
-
else
|
143
|
-
@inversed = false
|
144
143
|
end
|
145
144
|
end
|
146
145
|
|
@@ -191,7 +190,7 @@ module ActiveRecord
|
|
191
190
|
@reflection = @owner.class._reflect_on_association(reflection_name)
|
192
191
|
end
|
193
192
|
|
194
|
-
def initialize_attributes(record, except_from_scope_attributes = nil)
|
193
|
+
def initialize_attributes(record, except_from_scope_attributes = nil) # :nodoc:
|
195
194
|
except_from_scope_attributes ||= {}
|
196
195
|
skip_assign = [reflection.foreign_key, reflection.type].compact
|
197
196
|
assigned_keys = record.changed_attribute_names_to_save
|
@@ -210,8 +209,14 @@ module ActiveRecord
|
|
210
209
|
end
|
211
210
|
|
212
211
|
private
|
212
|
+
# Reader and writer methods call this so that consistent errors are presented
|
213
|
+
# when the association target class does not exist.
|
214
|
+
def ensure_klass_exists!
|
215
|
+
klass
|
216
|
+
end
|
217
|
+
|
213
218
|
def find_target
|
214
|
-
if
|
219
|
+
if violates_strict_loading? && owner.validation_context.nil?
|
215
220
|
Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
|
216
221
|
end
|
217
222
|
|
@@ -224,13 +229,20 @@ module ActiveRecord
|
|
224
229
|
end
|
225
230
|
|
226
231
|
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
227
|
-
sc.execute(binds, klass.connection)
|
232
|
+
sc.execute(binds, klass.connection) do |record|
|
233
|
+
set_inverse_instance(record)
|
234
|
+
if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
|
235
|
+
record.strict_loading!
|
236
|
+
else
|
237
|
+
record.strict_loading!(false, mode: owner.strict_loading_mode)
|
238
|
+
end
|
239
|
+
end
|
228
240
|
end
|
229
241
|
|
230
|
-
def
|
242
|
+
def violates_strict_loading?
|
231
243
|
return reflection.strict_loading? if reflection.options.key?(:strict_loading)
|
232
244
|
|
233
|
-
owner.strict_loading?
|
245
|
+
owner.strict_loading? && !owner.strict_loading_n_plus_one_only?
|
234
246
|
end
|
235
247
|
|
236
248
|
# The scope for this association.
|
@@ -241,7 +253,11 @@ module ActiveRecord
|
|
241
253
|
# actually gets built.
|
242
254
|
def association_scope
|
243
255
|
if klass
|
244
|
-
@association_scope ||=
|
256
|
+
@association_scope ||= if disable_joins
|
257
|
+
DisableJoinsAssociationScope.scope(self)
|
258
|
+
else
|
259
|
+
AssociationScope.scope(self)
|
260
|
+
end
|
245
261
|
end
|
246
262
|
end
|
247
263
|
|
@@ -273,7 +289,7 @@ module ActiveRecord
|
|
273
289
|
|
274
290
|
# Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
|
275
291
|
# the kind of the class of the associated objects. Meant to be used as
|
276
|
-
# a
|
292
|
+
# a safety check when you are about to assign an associated record.
|
277
293
|
def raise_on_type_mismatch!(record)
|
278
294
|
unless record.is_a?(reflection.klass)
|
279
295
|
fresh_class = reflection.class_name.safe_constantize
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
|
-
class AssociationScope
|
5
|
+
class AssociationScope # :nodoc:
|
6
6
|
def self.scope(association)
|
7
7
|
INSTANCE.scope(association)
|
8
8
|
end
|
@@ -123,8 +123,6 @@ module ActiveRecord
|
|
123
123
|
|
124
124
|
chain_head = chain.first
|
125
125
|
chain.reverse_each do |reflection|
|
126
|
-
# Exclude the scope of the association itself, because that
|
127
|
-
# was already merged in the #scope method.
|
128
126
|
reflection.constraints.each do |scope_chain_item|
|
129
127
|
item = eval_scope(reflection, scope_chain_item, owner)
|
130
128
|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
5
|
# = Active Record Belongs To Association
|
6
|
-
class BelongsToAssociation < SingularAssociation
|
6
|
+
class BelongsToAssociation < SingularAssociation # :nodoc:
|
7
7
|
def handle_dependency
|
8
8
|
return unless load_target
|
9
9
|
|
@@ -55,7 +55,8 @@ module ActiveRecord
|
|
55
55
|
|
56
56
|
def decrement_counters_before_last_save
|
57
57
|
if reflection.polymorphic?
|
58
|
-
|
58
|
+
model_type_was = owner.attribute_before_last_save(reflection.foreign_type)
|
59
|
+
model_was = owner.class.polymorphic_class_for(model_type_was) if model_type_was
|
59
60
|
else
|
60
61
|
model_was = klass
|
61
62
|
end
|
@@ -68,6 +69,14 @@ module ActiveRecord
|
|
68
69
|
end
|
69
70
|
|
70
71
|
def target_changed?
|
72
|
+
owner.attribute_changed?(reflection.foreign_key) || (!foreign_key_present? && target&.new_record?)
|
73
|
+
end
|
74
|
+
|
75
|
+
def target_previously_changed?
|
76
|
+
owner.attribute_previously_changed?(reflection.foreign_key)
|
77
|
+
end
|
78
|
+
|
79
|
+
def saved_change_to_target?
|
71
80
|
owner.saved_change_to_attribute?(reflection.foreign_key)
|
72
81
|
end
|
73
82
|
|
@@ -77,6 +86,8 @@ module ActiveRecord
|
|
77
86
|
raise_on_type_mismatch!(record)
|
78
87
|
set_inverse_instance(record)
|
79
88
|
@updated = true
|
89
|
+
elsif target
|
90
|
+
remove_inverse_instance(target)
|
80
91
|
end
|
81
92
|
|
82
93
|
replace_keys(record, force: true)
|
@@ -110,7 +121,7 @@ module ActiveRecord
|
|
110
121
|
def replace_keys(record, force: false)
|
111
122
|
target_key = record ? record._read_attribute(primary_key(record.class)) : nil
|
112
123
|
|
113
|
-
if force || owner
|
124
|
+
if force || owner._read_attribute(reflection.foreign_key) != target_key
|
114
125
|
owner[reflection.foreign_key] = target_key
|
115
126
|
end
|
116
127
|
end
|
@@ -125,7 +136,7 @@ module ActiveRecord
|
|
125
136
|
|
126
137
|
def invertible_for?(record)
|
127
138
|
inverse = inverse_reflection_for(record)
|
128
|
-
inverse && (inverse.has_one? ||
|
139
|
+
inverse && (inverse.has_one? || inverse.klass.has_many_inversing)
|
129
140
|
end
|
130
141
|
|
131
142
|
def stale_state
|
@@ -3,13 +3,21 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
5
|
# = Active Record Belongs To Polymorphic Association
|
6
|
-
class BelongsToPolymorphicAssociation < BelongsToAssociation
|
6
|
+
class BelongsToPolymorphicAssociation < BelongsToAssociation # :nodoc:
|
7
7
|
def klass
|
8
8
|
type = owner[reflection.foreign_type]
|
9
9
|
type.presence && owner.class.polymorphic_class_for(type)
|
10
10
|
end
|
11
11
|
|
12
12
|
def target_changed?
|
13
|
+
super || owner.attribute_changed?(reflection.foreign_type)
|
14
|
+
end
|
15
|
+
|
16
|
+
def target_previously_changed?
|
17
|
+
super || owner.attribute_previously_changed?(reflection.foreign_type)
|
18
|
+
end
|
19
|
+
|
20
|
+
def saved_change_to_target?
|
13
21
|
super || owner.saved_change_to_attribute?(reflection.foreign_type)
|
14
22
|
end
|
15
23
|
|
@@ -19,7 +27,7 @@ module ActiveRecord
|
|
19
27
|
|
20
28
|
target_type = record ? record.class.polymorphic_name : nil
|
21
29
|
|
22
|
-
if force || owner
|
30
|
+
if force || owner._read_attribute(reflection.foreign_type) != target_type
|
23
31
|
owner[reflection.foreign_type] = target_type
|
24
32
|
end
|
25
33
|
end
|
@@ -12,7 +12,7 @@
|
|
12
12
|
# - HasManyAssociation
|
13
13
|
|
14
14
|
module ActiveRecord::Associations::Builder # :nodoc:
|
15
|
-
class Association
|
15
|
+
class Association # :nodoc:
|
16
16
|
class << self
|
17
17
|
attr_accessor :extensions
|
18
18
|
end
|
@@ -33,6 +33,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
33
33
|
define_accessors model, reflection
|
34
34
|
define_callbacks model, reflection
|
35
35
|
define_validations model, reflection
|
36
|
+
define_change_tracking_methods model, reflection
|
36
37
|
reflection
|
37
38
|
end
|
38
39
|
|
@@ -117,6 +118,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
117
118
|
# noop
|
118
119
|
end
|
119
120
|
|
121
|
+
def self.define_change_tracking_methods(model, reflection)
|
122
|
+
# noop
|
123
|
+
end
|
124
|
+
|
120
125
|
def self.valid_dependent_options
|
121
126
|
raise NotImplementedError
|
122
127
|
end
|
@@ -158,6 +163,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
158
163
|
|
159
164
|
private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions,
|
160
165
|
:define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations,
|
161
|
-
:
|
166
|
+
:define_change_tracking_methods, :valid_dependent_options, :check_dependent_options,
|
167
|
+
:add_destroy_callbacks, :add_after_commit_jobs_callback
|
162
168
|
end
|
163
169
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveRecord::Associations::Builder # :nodoc:
|
4
|
-
class BelongsTo < SingularAssociation
|
4
|
+
class BelongsTo < SingularAssociation # :nodoc:
|
5
5
|
def self.macro
|
6
6
|
:belongs_to
|
7
7
|
end
|
@@ -30,7 +30,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
30
30
|
model.after_update lambda { |record|
|
31
31
|
association = association(reflection.name)
|
32
32
|
|
33
|
-
if association.
|
33
|
+
if association.saved_change_to_target?
|
34
34
|
association.increment_counters
|
35
35
|
association.decrement_counters_before_last_save
|
36
36
|
end
|
@@ -49,7 +49,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
49
49
|
if reflection.polymorphic?
|
50
50
|
foreign_type = reflection.foreign_type
|
51
51
|
klass = changes[foreign_type] && changes[foreign_type].first || o.public_send(foreign_type)
|
52
|
-
klass = klass
|
52
|
+
klass = o.class.polymorphic_class_for(klass)
|
53
53
|
else
|
54
54
|
klass = association.klass
|
55
55
|
end
|
@@ -87,7 +87,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
87
87
|
if reflection.counter_cache_column
|
88
88
|
touch_callback = callback.(:saved_changes)
|
89
89
|
update_callback = lambda { |record|
|
90
|
-
instance_exec(record, &touch_callback) unless association(reflection.name).
|
90
|
+
instance_exec(record, &touch_callback) unless association(reflection.name).saved_change_to_target?
|
91
91
|
}
|
92
92
|
model.after_update update_callback, if: :saved_changes?
|
93
93
|
else
|
@@ -127,7 +127,20 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
130
|
-
|
131
|
-
|
130
|
+
def self.define_change_tracking_methods(model, reflection)
|
131
|
+
model.generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
132
|
+
def #{reflection.name}_changed?
|
133
|
+
association(:#{reflection.name}).target_changed?
|
134
|
+
end
|
135
|
+
|
136
|
+
def #{reflection.name}_previously_changed?
|
137
|
+
association(:#{reflection.name}).target_previously_changed?
|
138
|
+
end
|
139
|
+
CODE
|
140
|
+
end
|
141
|
+
|
142
|
+
private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks,
|
143
|
+
:define_validations, :define_change_tracking_methods, :add_counter_cache_callbacks,
|
144
|
+
:add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks
|
132
145
|
end
|
133
146
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
require "active_record/associations"
|
4
4
|
|
5
5
|
module ActiveRecord::Associations::Builder # :nodoc:
|
6
|
-
class CollectionAssociation < Association
|
6
|
+
class CollectionAssociation < Association # :nodoc:
|
7
7
|
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
|
8
8
|
|
9
9
|
def self.valid_options(options)
|
@@ -30,11 +30,18 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
30
30
|
def self.define_callback(model, callback_name, name, options)
|
31
31
|
full_callback_name = "#{callback_name}_for_#{name}"
|
32
32
|
|
33
|
-
|
33
|
+
callback_values = Array(options[callback_name.to_sym])
|
34
|
+
method_defined = model.respond_to?(full_callback_name)
|
35
|
+
|
36
|
+
# If there are no callbacks, we must also check if a superclass had
|
37
|
+
# previously defined this association
|
38
|
+
return if callback_values.empty? && !method_defined
|
39
|
+
|
40
|
+
unless method_defined
|
34
41
|
model.class_attribute(full_callback_name, instance_accessor: false, instance_predicate: false)
|
35
42
|
end
|
36
43
|
|
37
|
-
callbacks =
|
44
|
+
callbacks = callback_values.map do |callback|
|
38
45
|
case callback
|
39
46
|
when Symbol
|
40
47
|
->(method, owner, record) { owner.send(callback, record) }
|
@@ -1,16 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveRecord::Associations::Builder # :nodoc:
|
4
|
-
class HasMany < CollectionAssociation
|
4
|
+
class HasMany < CollectionAssociation # :nodoc:
|
5
5
|
def self.macro
|
6
6
|
:has_many
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.valid_options(options)
|
10
|
-
valid = super + [:counter_cache, :join_table, :index_errors
|
10
|
+
valid = super + [:counter_cache, :join_table, :index_errors]
|
11
11
|
valid += [:as, :foreign_type] if options[:as]
|
12
12
|
valid += [:through, :source, :source_type] if options[:through]
|
13
13
|
valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
|
14
|
+
valid += [:disable_joins] if options[:disable_joins] && options[:through]
|
14
15
|
valid
|
15
16
|
end
|
16
17
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveRecord::Associations::Builder # :nodoc:
|
4
|
-
class HasOne < SingularAssociation
|
4
|
+
class HasOne < SingularAssociation # :nodoc:
|
5
5
|
def self.macro
|
6
6
|
:has_one
|
7
7
|
end
|
@@ -11,6 +11,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
11
11
|
valid += [:as, :foreign_type] if options[:as]
|
12
12
|
valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
|
13
13
|
valid += [:through, :source, :source_type] if options[:through]
|
14
|
+
valid += [:disable_joins] if options[:disable_joins] && options[:through]
|
14
15
|
valid
|
15
16
|
end
|
16
17
|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
# This class is inherited by the has_one and belongs_to association classes
|
4
4
|
|
5
5
|
module ActiveRecord::Associations::Builder # :nodoc:
|
6
|
-
class SingularAssociation < Association
|
6
|
+
class SingularAssociation < Association # :nodoc:
|
7
7
|
def self.valid_options(options)
|
8
8
|
super + [:required, :touch]
|
9
9
|
end
|
@@ -13,7 +13,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
13
13
|
mixin = model.generated_association_methods
|
14
14
|
name = reflection.name
|
15
15
|
|
16
|
-
define_constructors(mixin, name)
|
16
|
+
define_constructors(mixin, name) unless reflection.polymorphic?
|
17
17
|
|
18
18
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
19
19
|
def reload_#{name}
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/enumerable"
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
module Associations
|
5
7
|
# = Active Record Association Collection
|
@@ -25,9 +27,11 @@ module ActiveRecord
|
|
25
27
|
#
|
26
28
|
# If you need to work on all current children, new and existing records,
|
27
29
|
# +load_target+ and the +loaded+ flag are your friends.
|
28
|
-
class CollectionAssociation < Association
|
30
|
+
class CollectionAssociation < Association # :nodoc:
|
29
31
|
# Implements the reader method, e.g. foo.items for Foo.has_many :items
|
30
32
|
def reader
|
33
|
+
ensure_klass_exists!
|
34
|
+
|
31
35
|
if stale_target?
|
32
36
|
reload
|
33
37
|
end
|
@@ -75,6 +79,7 @@ module ActiveRecord
|
|
75
79
|
def reset
|
76
80
|
super
|
77
81
|
@target = []
|
82
|
+
@replaced_or_added_targets = Set.new
|
78
83
|
@association_ids = nil
|
79
84
|
end
|
80
85
|
|
@@ -121,21 +126,6 @@ module ActiveRecord
|
|
121
126
|
end
|
122
127
|
end
|
123
128
|
|
124
|
-
# Starts a transaction in the association class's database connection.
|
125
|
-
#
|
126
|
-
# class Author < ActiveRecord::Base
|
127
|
-
# has_many :books
|
128
|
-
# end
|
129
|
-
#
|
130
|
-
# Author.first.books.transaction do
|
131
|
-
# # same effect as calling Book.transaction
|
132
|
-
# end
|
133
|
-
def transaction(*args)
|
134
|
-
reflection.klass.transaction(*args) do
|
135
|
-
yield
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
129
|
# Removes all records from the association without calling callbacks
|
140
130
|
# on the associated records. It honors the +:dependent+ option. However
|
141
131
|
# if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
|
@@ -279,20 +269,19 @@ module ActiveRecord
|
|
279
269
|
end
|
280
270
|
|
281
271
|
def add_to_target(record, skip_callbacks: false, replace: false, &block)
|
282
|
-
|
283
|
-
index = @target.index(record)
|
284
|
-
end
|
285
|
-
replace_on_target(record, index, skip_callbacks, &block)
|
272
|
+
replace_on_target(record, skip_callbacks, replace: replace || association_scope.distinct_value, &block)
|
286
273
|
end
|
287
274
|
|
288
275
|
def target=(record)
|
289
|
-
return super unless
|
276
|
+
return super unless reflection.klass.has_many_inversing
|
290
277
|
|
291
278
|
case record
|
279
|
+
when nil
|
280
|
+
# It's not possible to remove the record from the inverse association.
|
292
281
|
when Array
|
293
282
|
super
|
294
283
|
else
|
295
|
-
|
284
|
+
replace_on_target(record, true, replace: true, inversing: true)
|
296
285
|
end
|
297
286
|
end
|
298
287
|
|
@@ -315,6 +304,10 @@ module ActiveRecord
|
|
315
304
|
end
|
316
305
|
|
317
306
|
private
|
307
|
+
def transaction(&block)
|
308
|
+
reflection.klass.transaction(&block)
|
309
|
+
end
|
310
|
+
|
318
311
|
# We have some records loaded from the database (persisted) and some that are
|
319
312
|
# in-memory (memory). The same record may be represented in the persisted array
|
320
313
|
# and in the memory array.
|
@@ -342,12 +335,12 @@ module ActiveRecord
|
|
342
335
|
end
|
343
336
|
end
|
344
337
|
|
345
|
-
persisted + memory
|
338
|
+
persisted + memory.reject(&:persisted?)
|
346
339
|
end
|
347
340
|
|
348
341
|
def _create_record(attributes, raise = false, &block)
|
349
342
|
unless owner.persisted?
|
350
|
-
raise ActiveRecord::RecordNotSaved
|
343
|
+
raise ActiveRecord::RecordNotSaved.new("You cannot call create unless the parent is saved", owner)
|
351
344
|
end
|
352
345
|
|
353
346
|
if attributes.is_a?(Array)
|
@@ -425,7 +418,7 @@ module ActiveRecord
|
|
425
418
|
common_records = intersection(new_target, original_target)
|
426
419
|
common_records.each do |record|
|
427
420
|
skip_callbacks = true
|
428
|
-
replace_on_target(record,
|
421
|
+
replace_on_target(record, skip_callbacks, replace: true)
|
429
422
|
end
|
430
423
|
end
|
431
424
|
|
@@ -448,7 +441,11 @@ module ActiveRecord
|
|
448
441
|
records
|
449
442
|
end
|
450
443
|
|
451
|
-
def replace_on_target(record,
|
444
|
+
def replace_on_target(record, skip_callbacks, replace:, inversing: false)
|
445
|
+
if replace && (!record.new_record? || @replaced_or_added_targets.include?(record))
|
446
|
+
index = @target.index(record)
|
447
|
+
end
|
448
|
+
|
452
449
|
catch(:abort) do
|
453
450
|
callback(:before_add, record)
|
454
451
|
end || return unless skip_callbacks
|
@@ -459,6 +456,12 @@ module ActiveRecord
|
|
459
456
|
|
460
457
|
yield(record) if block_given?
|
461
458
|
|
459
|
+
if !index && @replaced_or_added_targets.include?(record)
|
460
|
+
index = @target.index(record)
|
461
|
+
end
|
462
|
+
|
463
|
+
@replaced_or_added_targets << record if inversing || index || record.new_record?
|
464
|
+
|
462
465
|
if index
|
463
466
|
target[index] = record
|
464
467
|
elsif @_was_loaded || !loaded?
|
@@ -481,7 +484,11 @@ module ActiveRecord
|
|
481
484
|
|
482
485
|
def callbacks_for(callback_name)
|
483
486
|
full_callback_name = "#{callback_name}_for_#{reflection.name}"
|
484
|
-
owner.class.
|
487
|
+
if owner.class.respond_to?(full_callback_name)
|
488
|
+
owner.class.send(full_callback_name)
|
489
|
+
else
|
490
|
+
[]
|
491
|
+
end
|
485
492
|
end
|
486
493
|
|
487
494
|
def include_in_memory?(record)
|
@@ -27,7 +27,7 @@ module ActiveRecord
|
|
27
27
|
# is computed directly through SQL and does not trigger by itself the
|
28
28
|
# instantiation of the actual post records.
|
29
29
|
class CollectionProxy < Relation
|
30
|
-
def initialize(klass, association, **)
|
30
|
+
def initialize(klass, association, **) # :nodoc:
|
31
31
|
@association = association
|
32
32
|
super klass
|
33
33
|
|
@@ -46,7 +46,7 @@ module ActiveRecord
|
|
46
46
|
# Returns +true+ if the association has been loaded, otherwise +false+.
|
47
47
|
#
|
48
48
|
# person.pets.loaded? # => false
|
49
|
-
# person.pets
|
49
|
+
# person.pets.records
|
50
50
|
# person.pets.loaded? # => true
|
51
51
|
def loaded?
|
52
52
|
@association.loaded?
|
@@ -813,7 +813,7 @@ module ActiveRecord
|
|
813
813
|
# to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
|
814
814
|
# it is equivalent to <tt>!collection.exists?</tt>. If the collection has
|
815
815
|
# not already been loaded and you are going to fetch the records anyway it
|
816
|
-
# is better to check <tt>collection.
|
816
|
+
# is better to check <tt>collection.load.empty?</tt>.
|
817
817
|
#
|
818
818
|
# class Person < ActiveRecord::Base
|
819
819
|
# has_many :pets
|
@@ -849,6 +849,11 @@ module ActiveRecord
|
|
849
849
|
# person.pets.count # => 1
|
850
850
|
# person.pets.any? # => true
|
851
851
|
#
|
852
|
+
# Calling it without a block when the collection is not yet
|
853
|
+
# loaded is equivalent to <tt>collection.exists?</tt>.
|
854
|
+
# If you're going to load the collection anyway, it is better
|
855
|
+
# to call <tt>collection.load.any?</tt> to avoid an extra query.
|
856
|
+
#
|
852
857
|
# You can also pass a +block+ to define criteria. The behavior
|
853
858
|
# is the same, it returns true if the collection based on the
|
854
859
|
# criteria is not empty.
|