activerecord 6.0.0.beta1 → 6.0.1.rc1
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 +529 -10
- data/README.rdoc +3 -1
- data/lib/active_record.rb +7 -1
- data/lib/active_record/association_relation.rb +15 -6
- data/lib/active_record/associations.rb +4 -3
- data/lib/active_record/associations/association.rb +27 -2
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +5 -2
- data/lib/active_record/associations/builder/collection_association.rb +5 -15
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +5 -6
- data/lib/active_record/associations/collection_proxy.rb +13 -42
- data/lib/active_record/associations/has_many_association.rb +1 -9
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/join_dependency.rb +14 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
- data/lib/active_record/associations/preloader.rb +12 -7
- data/lib/active_record/associations/preloader/association.rb +37 -34
- data/lib/active_record/associations/preloader/through_association.rb +48 -39
- data/lib/active_record/attribute_methods.rb +3 -53
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +47 -14
- data/lib/active_record/attribute_methods/primary_key.rb +7 -15
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +3 -9
- data/lib/active_record/attribute_methods/write.rb +6 -12
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +21 -7
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/callbacks.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +134 -23
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +105 -70
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
- data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
- data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -35
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +106 -138
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +95 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +73 -118
- data/lib/active_record/connection_handling.rb +40 -17
- data/lib/active_record/core.rb +35 -24
- data/lib/active_record/database_configurations.rb +99 -50
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +21 -16
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +15 -0
- data/lib/active_record/errors.rb +18 -13
- data/lib/active_record/fixtures.rb +11 -6
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +13 -1
- data/lib/active_record/internal_metadata.rb +5 -1
- data/lib/active_record/locking/optimistic.rb +3 -4
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +62 -44
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +72 -63
- data/lib/active_record/model_schema.rb +3 -0
- data/lib/active_record/persistence.rb +212 -19
- data/lib/active_record/querying.rb +18 -14
- data/lib/active_record/railtie.rb +9 -1
- data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
- data/lib/active_record/railties/databases.rake +124 -25
- data/lib/active_record/reflection.rb +18 -32
- data/lib/active_record/relation.rb +185 -35
- data/lib/active_record/relation/calculations.rb +40 -44
- data/lib/active_record/relation/delegation.rb +23 -31
- data/lib/active_record/relation/finder_methods.rb +23 -14
- data/lib/active_record/relation/merger.rb +11 -16
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +230 -69
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +10 -10
- data/lib/active_record/sanitization.rb +33 -4
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +10 -1
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping.rb +6 -7
- data/lib/active_record/scoping/default.rb +7 -15
- data/lib/active_record/scoping/named.rb +10 -2
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +9 -13
- data/lib/active_record/tasks/database_tasks.rb +109 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
- data/lib/active_record/test_databases.rb +1 -16
- data/lib/active_record/test_fixtures.rb +2 -2
- data/lib/active_record/timestamp.rb +35 -19
- data/lib/active_record/touch_later.rb +4 -2
- data/lib/active_record/transactions.rb +56 -46
- data/lib/active_record/type_caster/connection.rb +16 -10
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/arel.rb +18 -4
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/nodes/and.rb +1 -1
- data/lib/arel/nodes/case.rb +1 -1
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/select_core.rb +16 -12
- data/lib/arel/nodes/unary.rb +1 -0
- data/lib/arel/nodes/values_list.rb +2 -17
- data/lib/arel/select_manager.rb +10 -10
- data/lib/arel/visitors/depth_first.rb +7 -2
- data/lib/arel/visitors/dot.rb +7 -2
- data/lib/arel/visitors/ibm_db.rb +13 -0
- data/lib/arel/visitors/informix.rb +6 -0
- data/lib/arel/visitors/mssql.rb +15 -1
- data/lib/arel/visitors/oracle12.rb +4 -5
- data/lib/arel/visitors/postgresql.rb +4 -10
- data/lib/arel/visitors/to_sql.rb +107 -131
- data/lib/arel/visitors/visitor.rb +9 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +19 -12
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/arel/nodes/values.rb +0 -16
@@ -36,14 +36,6 @@ module ActiveRecord
|
|
36
36
|
super
|
37
37
|
end
|
38
38
|
|
39
|
-
def empty?
|
40
|
-
if reflection.has_cached_counter?
|
41
|
-
size.zero?
|
42
|
-
else
|
43
|
-
super
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
39
|
private
|
48
40
|
|
49
41
|
# Returns the number of records in this collection.
|
@@ -69,7 +61,7 @@ module ActiveRecord
|
|
69
61
|
# If there's nothing in the database and @target has no new records
|
70
62
|
# we are certain the current target is an empty array. This is a
|
71
63
|
# documented side-effect of the method that may avoid an extra SELECT.
|
72
|
-
|
64
|
+
loaded! if count == 0
|
73
65
|
|
74
66
|
[association_scope.limit_value, count].compact.min
|
75
67
|
end
|
@@ -57,21 +57,14 @@ module ActiveRecord
|
|
57
57
|
@through_records[record.object_id] ||= begin
|
58
58
|
ensure_mutable
|
59
59
|
|
60
|
-
|
61
|
-
|
60
|
+
attributes = through_scope_attributes
|
61
|
+
attributes[source_reflection.name] = record
|
62
|
+
attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
|
62
63
|
|
63
|
-
|
64
|
-
through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
|
65
|
-
end
|
66
|
-
|
67
|
-
through_record
|
64
|
+
through_association.build(attributes)
|
68
65
|
end
|
69
66
|
end
|
70
67
|
|
71
|
-
def options_for_through_record
|
72
|
-
[through_scope_attributes]
|
73
|
-
end
|
74
|
-
|
75
68
|
def through_scope_attributes
|
76
69
|
scope.where_values_hash(through_association.reflection.name.to_s).
|
77
70
|
except!(through_association.reflection.foreign_key,
|
@@ -64,16 +64,21 @@ module ActiveRecord
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
-
def initialize(base, table, associations)
|
67
|
+
def initialize(base, table, associations, join_type)
|
68
68
|
tree = self.class.make_tree associations
|
69
69
|
@join_root = JoinBase.new(base, table, build(tree, base))
|
70
|
+
@join_type = join_type
|
71
|
+
end
|
72
|
+
|
73
|
+
def base_klass
|
74
|
+
join_root.base_klass
|
70
75
|
end
|
71
76
|
|
72
77
|
def reflections
|
73
78
|
join_root.drop(1).map!(&:reflection)
|
74
79
|
end
|
75
80
|
|
76
|
-
def join_constraints(joins_to_add,
|
81
|
+
def join_constraints(joins_to_add, alias_tracker)
|
77
82
|
@alias_tracker = alias_tracker
|
78
83
|
|
79
84
|
construct_tables!(join_root)
|
@@ -82,9 +87,9 @@ module ActiveRecord
|
|
82
87
|
joins.concat joins_to_add.flat_map { |oj|
|
83
88
|
construct_tables!(oj.join_root)
|
84
89
|
if join_root.match? oj.join_root
|
85
|
-
walk join_root, oj.
|
90
|
+
walk(join_root, oj.join_root, oj.join_type)
|
86
91
|
else
|
87
|
-
make_join_constraints(oj.join_root, join_type)
|
92
|
+
make_join_constraints(oj.join_root, oj.join_type)
|
88
93
|
end
|
89
94
|
}
|
90
95
|
end
|
@@ -125,7 +130,7 @@ module ActiveRecord
|
|
125
130
|
end
|
126
131
|
|
127
132
|
protected
|
128
|
-
attr_reader :join_root
|
133
|
+
attr_reader :join_root, :join_type
|
129
134
|
|
130
135
|
private
|
131
136
|
attr_reader :alias_tracker
|
@@ -151,7 +156,7 @@ module ActiveRecord
|
|
151
156
|
end
|
152
157
|
end
|
153
158
|
|
154
|
-
def make_constraints(parent, child, join_type
|
159
|
+
def make_constraints(parent, child, join_type)
|
155
160
|
foreign_table = parent.table
|
156
161
|
foreign_klass = parent.base_klass
|
157
162
|
joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
|
@@ -173,13 +178,13 @@ module ActiveRecord
|
|
173
178
|
join ? "#{name}_join" : name
|
174
179
|
end
|
175
180
|
|
176
|
-
def walk(left, right)
|
181
|
+
def walk(left, right, join_type)
|
177
182
|
intersection, missing = right.children.map { |node1|
|
178
183
|
[left.children.find { |node2| node1.match? node2 }, node1]
|
179
184
|
}.partition(&:first)
|
180
185
|
|
181
|
-
joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r) }
|
182
|
-
joins.concat missing.flat_map { |_, n| make_constraints(left, n) }
|
186
|
+
joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r, join_type) }
|
187
|
+
joins.concat missing.flat_map { |_, n| make_constraints(left, n, join_type) }
|
183
188
|
end
|
184
189
|
|
185
190
|
def find_reflection(klass, name)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_record/associations/join_dependency/join_part"
|
4
|
+
require "active_support/core_ext/array/extract"
|
4
5
|
|
5
6
|
module ActiveRecord
|
6
7
|
module Associations
|
@@ -30,17 +31,20 @@ module ActiveRecord
|
|
30
31
|
table = tables[-i]
|
31
32
|
klass = reflection.klass
|
32
33
|
|
33
|
-
|
34
|
+
join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
|
34
35
|
|
35
|
-
joins << table.create_join(table, table.create_on(constraint), join_type)
|
36
|
-
|
37
|
-
join_scope = reflection.join_scope(table, foreign_klass)
|
38
36
|
arel = join_scope.arel(alias_tracker.aliases)
|
37
|
+
nodes = arel.constraints.first
|
38
|
+
|
39
|
+
others = nodes.children.extract! do |node|
|
40
|
+
!Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
|
41
|
+
end
|
42
|
+
|
43
|
+
joins << table.create_join(table, table.create_on(nodes), join_type)
|
39
44
|
|
40
|
-
|
45
|
+
unless others.empty?
|
41
46
|
joins.concat arel.join_sources
|
42
|
-
|
43
|
-
right.expr = right.expr.and(arel.constraints)
|
47
|
+
append_constraints(joins.last, others)
|
44
48
|
end
|
45
49
|
|
46
50
|
# The current table in this iteration becomes the foreign table in the next
|
@@ -60,6 +64,16 @@ module ActiveRecord
|
|
60
64
|
|
61
65
|
@readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value
|
62
66
|
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def append_constraints(join, constraints)
|
70
|
+
if join.is_a?(Arel::Nodes::StringJoin)
|
71
|
+
join_string = table.create_and(constraints.unshift(join.left))
|
72
|
+
join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
|
73
|
+
else
|
74
|
+
join.right.expr.children.concat(constraints)
|
75
|
+
end
|
76
|
+
end
|
63
77
|
end
|
64
78
|
end
|
65
79
|
end
|
@@ -112,7 +112,7 @@ module ActiveRecord
|
|
112
112
|
association.flat_map { |parent, child|
|
113
113
|
grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
|
114
114
|
loaders = preloaders_for_reflection(reflection, reflection_records, scope)
|
115
|
-
recs = loaders.flat_map(&:preloaded_records)
|
115
|
+
recs = loaders.flat_map(&:preloaded_records).uniq
|
116
116
|
child_polymorphic_parent = reflection && reflection.options[:polymorphic]
|
117
117
|
loaders.concat Array.wrap(child).flat_map { |assoc|
|
118
118
|
preloaders_on assoc, recs, scope, child_polymorphic_parent
|
@@ -143,16 +143,13 @@ module ActiveRecord
|
|
143
143
|
|
144
144
|
def preloaders_for_reflection(reflection, records, scope)
|
145
145
|
records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs|
|
146
|
-
|
147
|
-
loader.run self
|
148
|
-
loader
|
146
|
+
preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope).run
|
149
147
|
end
|
150
148
|
end
|
151
149
|
|
152
150
|
def grouped_records(association, records, polymorphic_parent)
|
153
151
|
h = {}
|
154
152
|
records.each do |record|
|
155
|
-
next unless record
|
156
153
|
reflection = record.class._reflect_on_association(association)
|
157
154
|
next if polymorphic_parent && !reflection || !record.association(association).klass
|
158
155
|
(h[reflection] ||= []) << record
|
@@ -166,10 +163,18 @@ module ActiveRecord
|
|
166
163
|
@reflection = reflection
|
167
164
|
end
|
168
165
|
|
169
|
-
def run
|
166
|
+
def run
|
167
|
+
self
|
168
|
+
end
|
170
169
|
|
171
170
|
def preloaded_records
|
172
|
-
|
171
|
+
@preloaded_records ||= records_by_owner.flat_map(&:last)
|
172
|
+
end
|
173
|
+
|
174
|
+
def records_by_owner
|
175
|
+
@records_by_owner ||= owners.each_with_object({}) do |owner, result|
|
176
|
+
result[owner] = Array(owner.association(reflection.name).target)
|
177
|
+
end
|
173
178
|
end
|
174
179
|
|
175
180
|
private
|
@@ -4,29 +4,43 @@ module ActiveRecord
|
|
4
4
|
module Associations
|
5
5
|
class Preloader
|
6
6
|
class Association #:nodoc:
|
7
|
-
attr_reader :preloaded_records
|
8
|
-
|
9
7
|
def initialize(klass, owners, reflection, preload_scope)
|
10
8
|
@klass = klass
|
11
9
|
@owners = owners
|
12
10
|
@reflection = reflection
|
13
11
|
@preload_scope = preload_scope
|
14
12
|
@model = owners.first && owners.first.class
|
15
|
-
@preloaded_records = []
|
16
13
|
end
|
17
14
|
|
18
|
-
def run
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
def run
|
16
|
+
if !preload_scope || preload_scope.empty_scope?
|
17
|
+
owners.each do |owner|
|
18
|
+
associate_records_to_owner(owner, records_by_owner[owner] || [])
|
19
|
+
end
|
20
|
+
else
|
21
|
+
# Custom preload scope is used and
|
22
|
+
# the association can not be marked as loaded
|
23
|
+
# Loading into a Hash instead
|
24
|
+
records_by_owner
|
23
25
|
end
|
26
|
+
self
|
27
|
+
end
|
24
28
|
|
25
|
-
|
26
|
-
|
29
|
+
def records_by_owner
|
30
|
+
# owners can be duplicated when a relation has a collection association join
|
31
|
+
# #compare_by_identity makes such owners different hash keys
|
32
|
+
@records_by_owner ||= preloaded_records.each_with_object({}.compare_by_identity) do |record, result|
|
33
|
+
owners_by_key[convert_key(record[association_key_name])].each do |owner|
|
34
|
+
(result[owner] ||= []) << record
|
35
|
+
end
|
27
36
|
end
|
28
37
|
end
|
29
38
|
|
39
|
+
def preloaded_records
|
40
|
+
return @preloaded_records if defined?(@preloaded_records)
|
41
|
+
@preloaded_records = owner_keys.empty? ? [] : records_for(owner_keys)
|
42
|
+
end
|
43
|
+
|
30
44
|
private
|
31
45
|
attr_reader :owners, :reflection, :preload_scope, :model, :klass
|
32
46
|
|
@@ -42,11 +56,10 @@ module ActiveRecord
|
|
42
56
|
|
43
57
|
def associate_records_to_owner(owner, records)
|
44
58
|
association = owner.association(reflection.name)
|
45
|
-
association.loaded!
|
46
59
|
if reflection.collection?
|
47
|
-
association.target
|
60
|
+
association.target = records
|
48
61
|
else
|
49
|
-
association.target = records.first
|
62
|
+
association.target = records.first
|
50
63
|
end
|
51
64
|
end
|
52
65
|
|
@@ -55,13 +68,10 @@ module ActiveRecord
|
|
55
68
|
end
|
56
69
|
|
57
70
|
def owners_by_key
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
h[key] = owner if key
|
62
|
-
end
|
71
|
+
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
72
|
+
key = convert_key(owner[owner_key_name])
|
73
|
+
(result[key] ||= []) << owner if key
|
63
74
|
end
|
64
|
-
@owners_by_key
|
65
75
|
end
|
66
76
|
|
67
77
|
def key_conversion_required?
|
@@ -88,23 +98,16 @@ module ActiveRecord
|
|
88
98
|
@model.type_for_attribute(owner_key_name).type
|
89
99
|
end
|
90
100
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
99
|
-
@preloaded_records.group_by do |record|
|
100
|
-
convert_key(record[association_key_name])
|
101
|
+
def records_for(ids)
|
102
|
+
scope.where(association_key_name => ids).load do |record|
|
103
|
+
# Processing only the first owner
|
104
|
+
# because the record is modified but not an owner
|
105
|
+
owner = owners_by_key[convert_key(record[association_key_name])].first
|
106
|
+
association = owner.association(reflection.name)
|
107
|
+
association.set_inverse_instance(record)
|
101
108
|
end
|
102
109
|
end
|
103
110
|
|
104
|
-
def records_for(ids, &block)
|
105
|
-
scope.where(association_key_name => ids).load(&block)
|
106
|
-
end
|
107
|
-
|
108
111
|
def scope
|
109
112
|
@scope ||= build_scope
|
110
113
|
end
|
@@ -116,7 +119,7 @@ module ActiveRecord
|
|
116
119
|
def build_scope
|
117
120
|
scope = klass.scope_for_association
|
118
121
|
|
119
|
-
if reflection.type
|
122
|
+
if reflection.type && !reflection.through_reflection?
|
120
123
|
scope.where!(reflection.type => model.polymorphic_name)
|
121
124
|
end
|
122
125
|
|
@@ -4,42 +4,57 @@ module ActiveRecord
|
|
4
4
|
module Associations
|
5
5
|
class Preloader
|
6
6
|
class ThroughAssociation < Association # :nodoc:
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
7
|
+
PRELOADER = ActiveRecord::Associations::Preloader.new
|
8
|
+
|
9
|
+
def initialize(*)
|
10
|
+
super
|
11
|
+
@already_loaded = owners.first.association(through_reflection.name).loaded?
|
12
|
+
end
|
13
|
+
|
14
|
+
def preloaded_records
|
15
|
+
@preloaded_records ||= source_preloaders.flat_map(&:preloaded_records)
|
16
|
+
end
|
17
|
+
|
18
|
+
def records_by_owner
|
19
|
+
return @records_by_owner if defined?(@records_by_owner)
|
20
|
+
source_records_by_owner = source_preloaders.map(&:records_by_owner).reduce(:merge)
|
21
|
+
through_records_by_owner = through_preloaders.map(&:records_by_owner).reduce(:merge)
|
22
|
+
|
23
|
+
@records_by_owner = owners.each_with_object({}) do |owner, result|
|
24
|
+
through_records = through_records_by_owner[owner] || []
|
25
|
+
|
26
|
+
if @already_loaded
|
19
27
|
if source_type = reflection.options[:source_type]
|
20
28
|
through_records = through_records.select do |record|
|
21
29
|
record[reflection.foreign_type] == source_type
|
22
30
|
end
|
23
31
|
end
|
24
|
-
else
|
25
|
-
owner.association(through_reflection.name).reset if through_scope
|
26
|
-
end
|
27
|
-
result = through_records.flat_map do |record|
|
28
|
-
association = record.association(source_reflection.name)
|
29
|
-
target = association.target
|
30
|
-
association.reset if preload_scope
|
31
|
-
target
|
32
32
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
result.uniq! if reflection_scope.distinct_value
|
33
|
+
|
34
|
+
records = through_records.flat_map do |record|
|
35
|
+
source_records_by_owner[record]
|
37
36
|
end
|
38
|
-
|
37
|
+
|
38
|
+
records.compact!
|
39
|
+
records.sort_by! { |rhs| preload_index[rhs] } if scope.order_values.any?
|
40
|
+
records.uniq! if scope.distinct_value
|
41
|
+
result[owner] = records
|
39
42
|
end
|
40
43
|
end
|
41
44
|
|
42
45
|
private
|
46
|
+
def source_preloaders
|
47
|
+
@source_preloaders ||= PRELOADER.preload(middle_records, source_reflection.name, scope)
|
48
|
+
end
|
49
|
+
|
50
|
+
def middle_records
|
51
|
+
through_preloaders.flat_map(&:preloaded_records)
|
52
|
+
end
|
53
|
+
|
54
|
+
def through_preloaders
|
55
|
+
@through_preloaders ||= PRELOADER.preload(owners, through_reflection.name, through_scope)
|
56
|
+
end
|
57
|
+
|
43
58
|
def through_reflection
|
44
59
|
reflection.through_reflection
|
45
60
|
end
|
@@ -49,8 +64,8 @@ module ActiveRecord
|
|
49
64
|
end
|
50
65
|
|
51
66
|
def preload_index
|
52
|
-
@preload_index ||=
|
53
|
-
result[
|
67
|
+
@preload_index ||= preloaded_records.each_with_object({}).with_index do |(record, result), index|
|
68
|
+
result[record] = index
|
54
69
|
end
|
55
70
|
end
|
56
71
|
|
@@ -58,11 +73,15 @@ module ActiveRecord
|
|
58
73
|
scope = through_reflection.klass.unscoped
|
59
74
|
options = reflection.options
|
60
75
|
|
76
|
+
values = reflection_scope.values
|
77
|
+
if annotations = values[:annotate]
|
78
|
+
scope.annotate!(*annotations)
|
79
|
+
end
|
80
|
+
|
61
81
|
if options[:source_type]
|
62
82
|
scope.where! reflection.foreign_type => options[:source_type]
|
63
83
|
elsif !reflection_scope.where_clause.empty?
|
64
84
|
scope.where_clause = reflection_scope.where_clause
|
65
|
-
values = reflection_scope.values
|
66
85
|
|
67
86
|
if includes = values[:includes]
|
68
87
|
scope.includes!(source_reflection.name => includes)
|
@@ -89,17 +108,7 @@ module ActiveRecord
|
|
89
108
|
end
|
90
109
|
end
|
91
110
|
|
92
|
-
scope
|
93
|
-
end
|
94
|
-
|
95
|
-
def target_reflection_scope
|
96
|
-
if preload_scope
|
97
|
-
reflection_scope.merge(preload_scope)
|
98
|
-
elsif reflection.scope
|
99
|
-
reflection_scope
|
100
|
-
else
|
101
|
-
nil
|
102
|
-
end
|
111
|
+
scope
|
103
112
|
end
|
104
113
|
end
|
105
114
|
end
|