activerecord 6.0.6 → 6.1.0
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 +783 -910
- data/MIT-LICENSE +1 -1
- data/README.rdoc +3 -3
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/association_relation.rb +22 -14
- data/lib/active_record/associations/alias_tracker.rb +19 -15
- data/lib/active_record/associations/association.rb +43 -26
- data/lib/active_record/associations/association_scope.rb +11 -15
- data/lib/active_record/associations/belongs_to_association.rb +15 -5
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +9 -3
- data/lib/active_record/associations/builder/belongs_to.rb +10 -7
- data/lib/active_record/associations/builder/collection_association.rb +5 -4
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
- data/lib/active_record/associations/builder/has_many.rb +6 -2
- data/lib/active_record/associations/builder/has_one.rb +11 -14
- data/lib/active_record/associations/builder/singular_association.rb +1 -1
- data/lib/active_record/associations/collection_association.rb +19 -13
- data/lib/active_record/associations/collection_proxy.rb +12 -5
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +24 -2
- data/lib/active_record/associations/has_many_through_association.rb +10 -4
- data/lib/active_record/associations/has_one_association.rb +15 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +29 -14
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +63 -49
- data/lib/active_record/associations/preloader/association.rb +13 -5
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +5 -3
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations.rb +114 -11
- data/lib/active_record/attribute_assignment.rb +10 -8
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
- data/lib/active_record/attribute_methods/dirty.rb +1 -11
- data/lib/active_record/attribute_methods/primary_key.rb +6 -2
- data/lib/active_record/attribute_methods/query.rb +3 -6
- data/lib/active_record/attribute_methods/read.rb +8 -11
- data/lib/active_record/attribute_methods/serialization.rb +11 -5
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
- data/lib/active_record/attribute_methods/write.rb +12 -20
- data/lib/active_record/attribute_methods.rb +64 -54
- data/lib/active_record/attributes.rb +32 -7
- data/lib/active_record/autosave_association.rb +47 -30
- data/lib/active_record/base.rb +2 -14
- data/lib/active_record/callbacks.rb +152 -22
- data/lib/active_record/coders/yaml_column.rb +2 -24
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +185 -134
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -22
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -7
- data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +110 -30
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +224 -85
- data/lib/active_record/connection_adapters/abstract/transaction.rb +80 -32
- data/lib/active_record/connection_adapters/abstract_adapter.rb +49 -72
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +123 -87
- data/lib/active_record/connection_adapters/column.rb +15 -1
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -24
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +3 -3
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
- data/lib/active_record/connection_adapters/pool_config.rb +63 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +12 -53
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +72 -55
- data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +30 -5
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +36 -3
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +48 -50
- data/lib/active_record/connection_adapters.rb +50 -0
- data/lib/active_record/connection_handling.rb +210 -71
- data/lib/active_record/core.rb +223 -66
- data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
- data/lib/active_record/database_configurations/database_config.rb +52 -9
- data/lib/active_record/database_configurations/hash_config.rb +54 -8
- data/lib/active_record/database_configurations/url_config.rb +15 -40
- data/lib/active_record/database_configurations.rb +124 -85
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/enum.rb +27 -10
- data/lib/active_record/errors.rb +47 -12
- data/lib/active_record/explain.rb +9 -4
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +10 -17
- data/lib/active_record/fixture_set/model_metadata.rb +1 -2
- data/lib/active_record/fixture_set/render_context.rb +1 -1
- data/lib/active_record/fixture_set/table_row.rb +2 -2
- data/lib/active_record/fixtures.rb +54 -8
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +40 -18
- data/lib/active_record/insert_all.rb +34 -5
- data/lib/active_record/integration.rb +3 -5
- data/lib/active_record/internal_metadata.rb +16 -7
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +13 -16
- data/lib/active_record/locking/pessimistic.rb +6 -2
- data/lib/active_record/log_subscriber.rb +26 -8
- data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
- data/lib/active_record/middleware/database_selector.rb +4 -1
- data/lib/active_record/migration/command_recorder.rb +47 -27
- data/lib/active_record/migration/compatibility.rb +67 -17
- data/lib/active_record/migration.rb +113 -83
- data/lib/active_record/model_schema.rb +88 -13
- data/lib/active_record/nested_attributes.rb +2 -3
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +50 -45
- data/lib/active_record/query_cache.rb +15 -5
- data/lib/active_record/querying.rb +11 -6
- data/lib/active_record/railtie.rb +64 -44
- data/lib/active_record/railties/databases.rake +266 -95
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +60 -44
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/batches.rb +38 -31
- data/lib/active_record/relation/calculations.rb +100 -43
- data/lib/active_record/relation/finder_methods.rb +44 -14
- data/lib/active_record/relation/from_clause.rb +1 -1
- data/lib/active_record/relation/merger.rb +20 -23
- data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +3 -3
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +57 -33
- data/lib/active_record/relation/query_methods.rb +318 -195
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +8 -7
- data/lib/active_record/relation/where_clause.rb +104 -57
- data/lib/active_record/relation.rb +90 -64
- data/lib/active_record/result.rb +41 -33
- data/lib/active_record/runtime_registry.rb +2 -2
- data/lib/active_record/sanitization.rb +6 -17
- data/lib/active_record/schema_dumper.rb +34 -4
- data/lib/active_record/schema_migration.rb +2 -8
- data/lib/active_record/scoping/named.rb +1 -17
- data/lib/active_record/secure_token.rb +16 -8
- data/lib/active_record/serialization.rb +5 -3
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +20 -4
- data/lib/active_record/store.rb +2 -2
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +39 -51
- data/lib/active_record/tasks/database_tasks.rb +139 -113
- data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
- data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
- data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
- data/lib/active_record/test_databases.rb +5 -4
- data/lib/active_record/test_fixtures.rb +36 -33
- data/lib/active_record/timestamp.rb +4 -6
- data/lib/active_record/touch_later.rb +21 -21
- data/lib/active_record/transactions.rb +15 -64
- data/lib/active_record/type/serialized.rb +6 -2
- data/lib/active_record/type.rb +8 -1
- data/lib/active_record/type_caster/connection.rb +0 -1
- data/lib/active_record/type_caster/map.rb +8 -5
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +24 -4
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record.rb +7 -14
- data/lib/arel/attributes/attribute.rb +4 -0
- data/lib/arel/collectors/bind.rb +5 -0
- data/lib/arel/collectors/composite.rb +8 -0
- data/lib/arel/collectors/sql_string.rb +7 -0
- data/lib/arel/collectors/substitute_binds.rb +7 -0
- data/lib/arel/nodes/binary.rb +82 -8
- data/lib/arel/nodes/bind_param.rb +8 -0
- data/lib/arel/nodes/casted.rb +21 -9
- data/lib/arel/nodes/equality.rb +6 -9
- data/lib/arel/nodes/grouping.rb +3 -0
- data/lib/arel/nodes/homogeneous_in.rb +72 -0
- data/lib/arel/nodes/in.rb +8 -1
- data/lib/arel/nodes/infix_operation.rb +13 -1
- data/lib/arel/nodes/join_source.rb +1 -1
- data/lib/arel/nodes/node.rb +7 -6
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/sql_literal.rb +3 -0
- data/lib/arel/nodes/table_alias.rb +7 -3
- data/lib/arel/nodes/unary.rb +0 -1
- data/lib/arel/nodes.rb +3 -1
- data/lib/arel/predications.rb +12 -18
- data/lib/arel/select_manager.rb +1 -2
- data/lib/arel/table.rb +13 -5
- data/lib/arel/visitors/dot.rb +14 -2
- data/lib/arel/visitors/mysql.rb +11 -1
- data/lib/arel/visitors/postgresql.rb +15 -4
- data/lib/arel/visitors/to_sql.rb +89 -78
- data/lib/arel/visitors.rb +0 -7
- data/lib/arel.rb +5 -13
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
- data/lib/rails/generators/active_record/migration.rb +6 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- metadata +28 -30
- data/lib/active_record/advisory_lock_base.rb +0 -18
- data/lib/active_record/attribute_decorators.rb +0 -88
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
- data/lib/active_record/relation/where_clause_factory.rb +0 -33
- data/lib/arel/attributes.rb +0 -22
- data/lib/arel/visitors/depth_first.rb +0 -203
- data/lib/arel/visitors/ibm_db.rb +0 -34
- data/lib/arel/visitors/informix.rb +0 -62
- data/lib/arel/visitors/mssql.rb +0 -156
- data/lib/arel/visitors/oracle.rb +0 -158
- data/lib/arel/visitors/oracle12.rb +0 -65
- data/lib/arel/visitors/where_sql.rb +0 -22
@@ -56,7 +56,7 @@ module ActiveRecord
|
|
56
56
|
def ids_writer(ids)
|
57
57
|
primary_key = reflection.association_primary_key
|
58
58
|
pk_type = klass.type_for_attribute(primary_key)
|
59
|
-
ids = Array(ids).
|
59
|
+
ids = Array(ids).compact_blank
|
60
60
|
ids.map! { |i| pk_type.cast(i) }
|
61
61
|
|
62
62
|
records = klass.where(primary_key => ids).index_by do |r|
|
@@ -101,11 +101,11 @@ module ActiveRecord
|
|
101
101
|
end
|
102
102
|
end
|
103
103
|
|
104
|
-
def build(attributes =
|
104
|
+
def build(attributes = nil, &block)
|
105
105
|
if attributes.is_a?(Array)
|
106
106
|
attributes.collect { |attr| build(attr, &block) }
|
107
107
|
else
|
108
|
-
add_to_target(build_record(attributes, &block))
|
108
|
+
add_to_target(build_record(attributes, &block), replace: true)
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
@@ -228,7 +228,7 @@ module ActiveRecord
|
|
228
228
|
# If the collection has been loaded
|
229
229
|
# it is equivalent to <tt>collection.size.zero?</tt>. If the
|
230
230
|
# collection has not been loaded, it is equivalent to
|
231
|
-
# <tt
|
231
|
+
# <tt>!collection.exists?</tt>. If the collection has not already been
|
232
232
|
# loaded and you are going to fetch the records anyway it is better to
|
233
233
|
# check <tt>collection.length.zero?</tt>.
|
234
234
|
def empty?
|
@@ -278,13 +278,24 @@ module ActiveRecord
|
|
278
278
|
target
|
279
279
|
end
|
280
280
|
|
281
|
-
def add_to_target(record, skip_callbacks
|
282
|
-
if association_scope.distinct_value
|
281
|
+
def add_to_target(record, skip_callbacks: false, replace: false, &block)
|
282
|
+
if replace || association_scope.distinct_value
|
283
283
|
index = @target.index(record)
|
284
284
|
end
|
285
285
|
replace_on_target(record, index, skip_callbacks, &block)
|
286
286
|
end
|
287
287
|
|
288
|
+
def target=(record)
|
289
|
+
return super unless ActiveRecord::Base.has_many_inversing
|
290
|
+
|
291
|
+
case record
|
292
|
+
when Array
|
293
|
+
super
|
294
|
+
else
|
295
|
+
add_to_target(record, skip_callbacks: true, replace: true)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
288
299
|
def scope
|
289
300
|
scope = super
|
290
301
|
scope.none! if null_scope?
|
@@ -297,6 +308,8 @@ module ActiveRecord
|
|
297
308
|
|
298
309
|
def find_from_target?
|
299
310
|
loaded? ||
|
311
|
+
owner.strict_loading? ||
|
312
|
+
reflection.strict_loading? ||
|
300
313
|
owner.new_record? ||
|
301
314
|
target.any? { |record| record.new_record? || record.changed? }
|
302
315
|
end
|
@@ -332,13 +345,6 @@ module ActiveRecord
|
|
332
345
|
persisted + memory
|
333
346
|
end
|
334
347
|
|
335
|
-
def build_record(attributes)
|
336
|
-
previous = klass.current_scope(true) if block_given?
|
337
|
-
super
|
338
|
-
ensure
|
339
|
-
klass.current_scope = previous if previous
|
340
|
-
end
|
341
|
-
|
342
348
|
def _create_record(attributes, raise = false, &block)
|
343
349
|
unless owner.persisted?
|
344
350
|
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
|
@@ -101,7 +101,7 @@ module ActiveRecord
|
|
101
101
|
# converting them into an array and iterating through them using
|
102
102
|
# Array#select.
|
103
103
|
#
|
104
|
-
# person.pets.select { |pet| pet.name
|
104
|
+
# person.pets.select { |pet| /oo/.match?(pet.name) }
|
105
105
|
# # => [
|
106
106
|
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
107
107
|
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
@@ -374,7 +374,7 @@ module ActiveRecord
|
|
374
374
|
# person.pets
|
375
375
|
# # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
|
376
376
|
#
|
377
|
-
# other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
|
377
|
+
# other_pets = [Pet.new(name: 'Puff', group: 'celebrities')]
|
378
378
|
#
|
379
379
|
# person.pets.replace(other_pets)
|
380
380
|
#
|
@@ -921,7 +921,7 @@ module ActiveRecord
|
|
921
921
|
!!@association.include?(record)
|
922
922
|
end
|
923
923
|
|
924
|
-
def proxy_association
|
924
|
+
def proxy_association # :nodoc:
|
925
925
|
@association
|
926
926
|
end
|
927
927
|
|
@@ -1087,17 +1087,24 @@ module ActiveRecord
|
|
1087
1087
|
end
|
1088
1088
|
|
1089
1089
|
def reset_scope # :nodoc:
|
1090
|
-
@offsets =
|
1090
|
+
@offsets = @take = nil
|
1091
1091
|
@scope = nil
|
1092
1092
|
self
|
1093
1093
|
end
|
1094
1094
|
|
1095
|
+
def inspect # :nodoc:
|
1096
|
+
load_target if find_from_target?
|
1097
|
+
super
|
1098
|
+
end
|
1099
|
+
|
1095
1100
|
delegate_methods = [
|
1096
1101
|
QueryMethods,
|
1097
1102
|
SpawnMethods,
|
1098
1103
|
].flat_map { |klass|
|
1099
1104
|
klass.public_instance_methods(false)
|
1100
|
-
} - self.public_instance_methods(false) - [:select] + [
|
1105
|
+
} - self.public_instance_methods(false) - [:select] + [
|
1106
|
+
:scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all
|
1107
|
+
]
|
1101
1108
|
|
1102
1109
|
delegate(*delegate_methods, to: :scope)
|
1103
1110
|
|
@@ -16,5 +16,18 @@ module ActiveRecord::Associations
|
|
16
16
|
attrs[reflection.type] = nil if reflection.type.present?
|
17
17
|
end
|
18
18
|
end
|
19
|
+
|
20
|
+
private
|
21
|
+
# Sets the owner attributes on the given record
|
22
|
+
def set_owner_attributes(record)
|
23
|
+
return if options[:through]
|
24
|
+
|
25
|
+
key = owner._read_attribute(reflection.join_foreign_key)
|
26
|
+
record._write_attribute(reflection.join_primary_key, key)
|
27
|
+
|
28
|
+
if reflection.type
|
29
|
+
record._write_attribute(reflection.type, owner.class.polymorphic_name)
|
30
|
+
end
|
31
|
+
end
|
19
32
|
end
|
20
33
|
end
|
@@ -26,6 +26,28 @@ module ActiveRecord
|
|
26
26
|
# No point in executing the counter update since we're going to destroy the parent anyway
|
27
27
|
load_target.each { |t| t.destroyed_by_association = reflection }
|
28
28
|
destroy_all
|
29
|
+
when :destroy_async
|
30
|
+
load_target.each do |t|
|
31
|
+
t.destroyed_by_association = reflection
|
32
|
+
end
|
33
|
+
|
34
|
+
unless target.empty?
|
35
|
+
association_class = target.first.class
|
36
|
+
primary_key_column = association_class.primary_key.to_sym
|
37
|
+
|
38
|
+
ids = target.collect do |assoc|
|
39
|
+
assoc.public_send(primary_key_column)
|
40
|
+
end
|
41
|
+
|
42
|
+
enqueue_destroy_association(
|
43
|
+
owner_model_name: owner.class.to_s,
|
44
|
+
owner_id: owner.id,
|
45
|
+
association_class: association_class.to_s,
|
46
|
+
association_ids: ids,
|
47
|
+
association_primary_key_column: primary_key_column,
|
48
|
+
ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
|
49
|
+
)
|
50
|
+
end
|
29
51
|
else
|
30
52
|
delete_all
|
31
53
|
end
|
@@ -52,7 +74,7 @@ module ActiveRecord
|
|
52
74
|
# the loaded flag is set to true as well.
|
53
75
|
def count_records
|
54
76
|
count = if reflection.has_cached_counter?
|
55
|
-
owner.
|
77
|
+
owner.read_attribute(reflection.counter_cache_column).to_i
|
56
78
|
else
|
57
79
|
scope.count(:all)
|
58
80
|
end
|
@@ -75,7 +97,7 @@ module ActiveRecord
|
|
75
97
|
if reflection.counter_must_be_updated_by_has_many?
|
76
98
|
counter = reflection.counter_cache_column
|
77
99
|
owner.increment(counter, difference)
|
78
|
-
owner.send(:
|
100
|
+
owner.send(:"clear_#{counter}_change")
|
79
101
|
end
|
80
102
|
end
|
81
103
|
|
@@ -8,7 +8,7 @@ module ActiveRecord
|
|
8
8
|
|
9
9
|
def initialize(owner, reflection)
|
10
10
|
super
|
11
|
-
@through_records = {}
|
11
|
+
@through_records = {}.compare_by_identity
|
12
12
|
end
|
13
13
|
|
14
14
|
def concat(*records)
|
@@ -54,7 +54,7 @@ module ActiveRecord
|
|
54
54
|
# However, after insert_record has been called, the cache is cleared in
|
55
55
|
# order to allow multiple instances of the same record in an association.
|
56
56
|
def build_through_record(record)
|
57
|
-
@through_records[record
|
57
|
+
@through_records[record] ||= begin
|
58
58
|
ensure_mutable
|
59
59
|
|
60
60
|
attributes = through_scope_attributes
|
@@ -65,7 +65,10 @@ module ActiveRecord
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
+
attr_reader :through_scope
|
69
|
+
|
68
70
|
def through_scope_attributes
|
71
|
+
scope = through_scope || self.scope
|
69
72
|
scope.where_values_hash(through_association.reflection.name.to_s).
|
70
73
|
except!(through_association.reflection.foreign_key,
|
71
74
|
through_association.reflection.klass.inheritance_column)
|
@@ -77,12 +80,13 @@ module ActiveRecord
|
|
77
80
|
association.save!
|
78
81
|
end
|
79
82
|
ensure
|
80
|
-
@through_records.delete(record
|
83
|
+
@through_records.delete(record)
|
81
84
|
end
|
82
85
|
|
83
86
|
def build_record(attributes)
|
84
87
|
ensure_not_nested
|
85
88
|
|
89
|
+
@through_scope = scope
|
86
90
|
record = super
|
87
91
|
|
88
92
|
inverse = source_reflection.inverse_of
|
@@ -95,6 +99,8 @@ module ActiveRecord
|
|
95
99
|
end
|
96
100
|
|
97
101
|
record
|
102
|
+
ensure
|
103
|
+
@through_scope = nil
|
98
104
|
end
|
99
105
|
|
100
106
|
def remove_records(existing_records, records, method)
|
@@ -202,7 +208,7 @@ module ActiveRecord
|
|
202
208
|
end
|
203
209
|
end
|
204
210
|
|
205
|
-
@through_records.delete(record
|
211
|
+
@through_records.delete(record)
|
206
212
|
end
|
207
213
|
end
|
208
214
|
|
@@ -32,6 +32,18 @@ module ActiveRecord
|
|
32
32
|
target.destroyed_by_association = reflection
|
33
33
|
target.destroy
|
34
34
|
throw(:abort) unless target.destroyed?
|
35
|
+
when :destroy_async
|
36
|
+
primary_key_column = target.class.primary_key.to_sym
|
37
|
+
id = target.public_send(primary_key_column)
|
38
|
+
|
39
|
+
enqueue_destroy_association(
|
40
|
+
owner_model_name: owner.class.to_s,
|
41
|
+
owner_id: owner.id,
|
42
|
+
association_class: reflection.klass.to_s,
|
43
|
+
association_ids: [id],
|
44
|
+
association_primary_key_column: primary_key_column,
|
45
|
+
ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
|
46
|
+
)
|
35
47
|
when :nullify
|
36
48
|
target.update_columns(nullified_owner_attributes) if target.persisted?
|
37
49
|
end
|
@@ -81,7 +93,9 @@ module ActiveRecord
|
|
81
93
|
target.delete
|
82
94
|
when :destroy
|
83
95
|
target.destroyed_by_association = reflection
|
84
|
-
target.
|
96
|
+
if target.persisted?
|
97
|
+
target.destroy
|
98
|
+
end
|
85
99
|
else
|
86
100
|
nullify_owner_attributes(target)
|
87
101
|
remove_inverse_instance(target)
|
@@ -14,7 +14,6 @@ module ActiveRecord
|
|
14
14
|
super(reflection.klass, children)
|
15
15
|
|
16
16
|
@reflection = reflection
|
17
|
-
@tables = nil
|
18
17
|
end
|
19
18
|
|
20
19
|
def match?(other)
|
@@ -24,11 +23,23 @@ module ActiveRecord
|
|
24
23
|
|
25
24
|
def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
|
26
25
|
joins = []
|
26
|
+
chain = []
|
27
|
+
|
28
|
+
reflection.chain.each do |reflection|
|
29
|
+
table, terminated = yield reflection
|
30
|
+
@table ||= table
|
31
|
+
|
32
|
+
if terminated
|
33
|
+
foreign_table, foreign_klass = table, reflection.klass
|
34
|
+
break
|
35
|
+
end
|
36
|
+
|
37
|
+
chain << [reflection, table]
|
38
|
+
end
|
27
39
|
|
28
40
|
# The chain starts with the target table, but we want to end with it here (makes
|
29
41
|
# more sense in this context), so we reverse
|
30
|
-
|
31
|
-
table = tables[-i]
|
42
|
+
chain.reverse_each do |reflection, table|
|
32
43
|
klass = reflection.klass
|
33
44
|
|
34
45
|
join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
|
@@ -43,13 +54,15 @@ module ActiveRecord
|
|
43
54
|
arel = join_scope.arel(alias_tracker.aliases)
|
44
55
|
nodes = arel.constraints.first
|
45
56
|
|
46
|
-
|
47
|
-
|
57
|
+
if nodes.is_a?(Arel::Nodes::And)
|
58
|
+
others = nodes.children.extract! do |node|
|
59
|
+
!Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
|
60
|
+
end
|
48
61
|
end
|
49
62
|
|
50
|
-
joins <<
|
63
|
+
joins << join_type.new(table, Arel::Nodes::On.new(nodes))
|
51
64
|
|
52
|
-
|
65
|
+
if others && !others.empty?
|
53
66
|
joins.concat arel.join_sources
|
54
67
|
append_constraints(joins.last, others)
|
55
68
|
end
|
@@ -61,24 +74,26 @@ module ActiveRecord
|
|
61
74
|
joins
|
62
75
|
end
|
63
76
|
|
64
|
-
def tables=(tables)
|
65
|
-
@tables = tables
|
66
|
-
@table = tables.first
|
67
|
-
end
|
68
|
-
|
69
77
|
def readonly?
|
70
78
|
return @readonly if defined?(@readonly)
|
71
79
|
|
72
80
|
@readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value
|
73
81
|
end
|
74
82
|
|
83
|
+
def strict_loading?
|
84
|
+
return @strict_loading if defined?(@strict_loading)
|
85
|
+
|
86
|
+
@strict_loading = reflection.scope && reflection.scope_for(base_klass.unscoped).strict_loading_value
|
87
|
+
end
|
88
|
+
|
75
89
|
private
|
76
90
|
def append_constraints(join, constraints)
|
77
91
|
if join.is_a?(Arel::Nodes::StringJoin)
|
78
|
-
join_string =
|
92
|
+
join_string = Arel::Nodes::And.new(constraints.unshift join.left)
|
79
93
|
join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
|
80
94
|
else
|
81
|
-
join.right
|
95
|
+
right = join.right
|
96
|
+
right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
|
82
97
|
end
|
83
98
|
end
|
84
99
|
end
|
@@ -17,7 +17,7 @@ module ActiveRecord
|
|
17
17
|
# association.
|
18
18
|
attr_reader :base_klass, :children
|
19
19
|
|
20
|
-
delegate :table_name, :column_names, :primary_key, to: :base_klass
|
20
|
+
delegate :table_name, :column_names, :primary_key, :attribute_types, to: :base_klass
|
21
21
|
|
22
22
|
def initialize(base_klass, children)
|
23
23
|
@base_klass = base_klass
|
@@ -34,7 +34,7 @@ module ActiveRecord
|
|
34
34
|
Table = Struct.new(:node, :columns) do # :nodoc:
|
35
35
|
def column_aliases
|
36
36
|
t = node.table
|
37
|
-
columns.map { |column| t[column.name].as
|
37
|
+
columns.map { |column| t[column.name].as(column.alias) }
|
38
38
|
end
|
39
39
|
end
|
40
40
|
Column = Struct.new(:name, :alias)
|
@@ -78,14 +78,18 @@ module ActiveRecord
|
|
78
78
|
join_root.drop(1).map!(&:reflection)
|
79
79
|
end
|
80
80
|
|
81
|
-
def join_constraints(joins_to_add, alias_tracker)
|
81
|
+
def join_constraints(joins_to_add, alias_tracker, references)
|
82
82
|
@alias_tracker = alias_tracker
|
83
|
+
@joined_tables = {}
|
84
|
+
@references = {}
|
85
|
+
|
86
|
+
references.each do |table_name|
|
87
|
+
@references[table_name.to_sym] = table_name if table_name.is_a?(Arel::Nodes::SqlLiteral)
|
88
|
+
end unless references.empty?
|
83
89
|
|
84
|
-
construct_tables!(join_root)
|
85
90
|
joins = make_join_constraints(join_root, join_type)
|
86
91
|
|
87
92
|
joins.concat joins_to_add.flat_map { |oj|
|
88
|
-
construct_tables!(oj.join_root)
|
89
93
|
if join_root.match? oj.join_root
|
90
94
|
walk(join_root, oj.join_root, oj.join_type)
|
91
95
|
else
|
@@ -94,26 +98,33 @@ module ActiveRecord
|
|
94
98
|
}
|
95
99
|
end
|
96
100
|
|
97
|
-
def instantiate(result_set, &block)
|
101
|
+
def instantiate(result_set, strict_loading_value, &block)
|
98
102
|
primary_key = aliases.column_alias(join_root, join_root.primary_key)
|
99
103
|
|
100
|
-
seen = Hash.new { |i,
|
101
|
-
i[
|
104
|
+
seen = Hash.new { |i, parent|
|
105
|
+
i[parent] = Hash.new { |j, child_class|
|
102
106
|
j[child_class] = {}
|
103
107
|
}
|
104
|
-
}
|
108
|
+
}.compare_by_identity
|
105
109
|
|
106
110
|
model_cache = Hash.new { |h, klass| h[klass] = {} }
|
107
111
|
parents = model_cache[join_root]
|
108
112
|
|
109
113
|
column_aliases = aliases.column_aliases(join_root)
|
110
|
-
column_names =
|
114
|
+
column_names = []
|
115
|
+
|
116
|
+
result_set.columns.each do |name|
|
117
|
+
column_names << name unless /\At\d+_r\d+\z/.match?(name)
|
118
|
+
end
|
111
119
|
|
112
120
|
if column_names.empty?
|
113
121
|
column_types = {}
|
114
122
|
else
|
115
123
|
column_types = result_set.column_types
|
116
|
-
|
124
|
+
unless column_types.empty?
|
125
|
+
attribute_types = join_root.attribute_types
|
126
|
+
column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
|
127
|
+
end
|
117
128
|
column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
|
118
129
|
end
|
119
130
|
|
@@ -128,7 +139,7 @@ module ActiveRecord
|
|
128
139
|
result_set.each { |row_hash|
|
129
140
|
parent_key = primary_key ? row_hash[primary_key] : row_hash
|
130
141
|
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
|
131
|
-
construct(parent, join_root, row_hash, seen, model_cache)
|
142
|
+
construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
|
132
143
|
}
|
133
144
|
end
|
134
145
|
|
@@ -136,37 +147,36 @@ module ActiveRecord
|
|
136
147
|
end
|
137
148
|
|
138
149
|
def apply_column_aliases(relation)
|
150
|
+
@join_root_alias = relation.select_values.empty?
|
139
151
|
relation._select!(-> { aliases.columns })
|
140
152
|
end
|
141
153
|
|
154
|
+
def each(&block)
|
155
|
+
join_root.each(&block)
|
156
|
+
end
|
157
|
+
|
142
158
|
protected
|
143
159
|
attr_reader :join_root, :join_type
|
144
160
|
|
145
161
|
private
|
146
|
-
attr_reader :alias_tracker
|
147
|
-
|
148
|
-
def explicit_selections(root_column_aliases, result_set)
|
149
|
-
root_names = root_column_aliases.map(&:name).to_set
|
150
|
-
result_set.columns.each_with_object([]) do |name, result|
|
151
|
-
result << name unless /\At\d+_r\d+\z/.match?(name) || root_names.include?(name)
|
152
|
-
end
|
153
|
-
end
|
162
|
+
attr_reader :alias_tracker, :join_root_alias
|
154
163
|
|
155
164
|
def aliases
|
156
165
|
@aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
|
157
|
-
|
166
|
+
column_names = if join_part == join_root && !join_root_alias
|
167
|
+
primary_key = join_root.primary_key
|
168
|
+
primary_key ? [primary_key] : []
|
169
|
+
else
|
170
|
+
join_part.column_names
|
171
|
+
end
|
172
|
+
|
173
|
+
columns = column_names.each_with_index.map { |column_name, j|
|
158
174
|
Aliases::Column.new column_name, "t#{i}_r#{j}"
|
159
175
|
}
|
160
176
|
Aliases::Table.new(join_part, columns)
|
161
177
|
}
|
162
178
|
end
|
163
179
|
|
164
|
-
def construct_tables!(join_root)
|
165
|
-
join_root.each_children do |parent, child|
|
166
|
-
child.tables = table_aliases_for(parent, child)
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
180
|
def make_join_constraints(join_root, join_type)
|
171
181
|
join_root.children.flat_map do |child|
|
172
182
|
make_constraints(join_root, child, join_type)
|
@@ -176,23 +186,25 @@ module ActiveRecord
|
|
176
186
|
def make_constraints(parent, child, join_type)
|
177
187
|
foreign_table = parent.table
|
178
188
|
foreign_klass = parent.base_klass
|
179
|
-
|
180
|
-
|
181
|
-
|
189
|
+
child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
|
190
|
+
table, terminated = @joined_tables[reflection]
|
191
|
+
root = reflection == child.reflection
|
182
192
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
193
|
+
if table && (!root || !terminated)
|
194
|
+
@joined_tables[reflection] = [table, root] if root
|
195
|
+
next table, true
|
196
|
+
end
|
197
|
+
|
198
|
+
table_name = @references[reflection.name.to_sym]
|
199
|
+
|
200
|
+
table = alias_tracker.aliased_table_for(reflection.klass.arel_table, table_name) do
|
201
|
+
name = reflection.alias_candidate(parent.table_name)
|
202
|
+
root ? name : "#{name}_join"
|
203
|
+
end
|
192
204
|
|
193
|
-
|
194
|
-
|
195
|
-
|
205
|
+
@joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
|
206
|
+
table
|
207
|
+
end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
|
196
208
|
end
|
197
209
|
|
198
210
|
def walk(left, right, join_type)
|
@@ -223,7 +235,7 @@ module ActiveRecord
|
|
223
235
|
end
|
224
236
|
end
|
225
237
|
|
226
|
-
def construct(ar_parent, parent, row, seen, model_cache)
|
238
|
+
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
|
227
239
|
return if ar_parent.nil?
|
228
240
|
|
229
241
|
parent.children.each do |node|
|
@@ -232,7 +244,7 @@ module ActiveRecord
|
|
232
244
|
other.loaded!
|
233
245
|
elsif ar_parent.association_cached?(node.reflection.name)
|
234
246
|
model = ar_parent.association(node.reflection.name).target
|
235
|
-
construct(model, node, row, seen, model_cache)
|
247
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
236
248
|
next
|
237
249
|
end
|
238
250
|
|
@@ -244,24 +256,25 @@ module ActiveRecord
|
|
244
256
|
next
|
245
257
|
end
|
246
258
|
|
247
|
-
model = seen[ar_parent
|
259
|
+
model = seen[ar_parent][node][id]
|
248
260
|
|
249
261
|
if model
|
250
|
-
construct(model, node, row, seen, model_cache)
|
262
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
251
263
|
else
|
252
|
-
model = construct_model(ar_parent, node, row, model_cache, id)
|
264
|
+
model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
|
253
265
|
|
254
|
-
seen[ar_parent
|
255
|
-
construct(model, node, row, seen, model_cache)
|
266
|
+
seen[ar_parent][node][id] = model
|
267
|
+
construct(model, node, row, seen, model_cache, strict_loading_value)
|
256
268
|
end
|
257
269
|
end
|
258
270
|
end
|
259
271
|
|
260
|
-
def construct_model(record, node, row, model_cache, id)
|
272
|
+
def construct_model(record, node, row, model_cache, id, strict_loading_value)
|
261
273
|
other = record.association(node.reflection.name)
|
262
274
|
|
263
275
|
model = model_cache[node][id] ||=
|
264
276
|
node.instantiate(row, aliases.column_aliases(node)) do |m|
|
277
|
+
m.strict_loading! if strict_loading_value
|
265
278
|
other.set_inverse_instance(m)
|
266
279
|
end
|
267
280
|
|
@@ -272,6 +285,7 @@ module ActiveRecord
|
|
272
285
|
end
|
273
286
|
|
274
287
|
model.readonly! if node.readonly?
|
288
|
+
model.strict_loading! if node.strict_loading?
|
275
289
|
model
|
276
290
|
end
|
277
291
|
end
|
@@ -45,18 +45,18 @@ module ActiveRecord
|
|
45
45
|
raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
|
46
46
|
|
47
47
|
@preloaded_records = raw_records.select do |record|
|
48
|
-
assignments =
|
48
|
+
assignments = false
|
49
49
|
|
50
50
|
owners_by_key[convert_key(record[association_key_name])].each do |owner|
|
51
51
|
entries = (@records_by_owner[owner] ||= [])
|
52
52
|
|
53
53
|
if reflection.collection? || entries.empty?
|
54
54
|
entries << record
|
55
|
-
assignments
|
55
|
+
assignments = true
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
|
59
|
+
assignments
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
@@ -142,8 +142,16 @@ module ActiveRecord
|
|
142
142
|
end
|
143
143
|
|
144
144
|
scope.merge!(reflection_scope) unless reflection_scope.empty_scope?
|
145
|
-
|
146
|
-
|
145
|
+
|
146
|
+
if preload_scope && !preload_scope.empty_scope?
|
147
|
+
scope.merge!(preload_scope)
|
148
|
+
end
|
149
|
+
|
150
|
+
if preload_scope && preload_scope.strict_loading_value
|
151
|
+
scope.strict_loading
|
152
|
+
else
|
153
|
+
scope
|
154
|
+
end
|
147
155
|
end
|
148
156
|
end
|
149
157
|
end
|