-
Notifications
You must be signed in to change notification settings - Fork 21.6k
/
through_association.rb
150 lines (123 loc) · 4.94 KB
/
through_association.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# frozen_string_literal: true
module ActiveRecord
module Associations
class Preloader
class ThroughAssociation < Association # :nodoc:
def preloaded_records
@preloaded_records ||= source_preloaders.flat_map(&:preloaded_records)
end
def records_by_owner
@records_by_owner ||= owners.each_with_object({}) do |owner, result|
if loaded?(owner)
result[owner] = target_for(owner)
next
end
through_records = through_records_by_owner[owner] || []
if owners.first.association(through_reflection.name).loaded?
if source_type = reflection.options[:source_type]
through_records = through_records.select do |record|
record[reflection.foreign_type] == source_type
end
end
end
records = through_records.flat_map do |record|
source_records_by_owner[record]
end
records.compact!
records.sort_by! { |rhs| preload_index[rhs] } if scope.order_values.any?
records.uniq! if scope.distinct_value
result[owner] = records
end
end
def runnable_loaders
if data_available?
[self]
elsif through_preloaders.all?(&:run?)
source_preloaders.flat_map(&:runnable_loaders)
else
through_preloaders.flat_map(&:runnable_loaders)
end
end
def future_classes
if run?
[]
elsif through_preloaders.all?(&:run?)
source_preloaders.flat_map(&:future_classes).uniq
else
through_classes = through_preloaders.flat_map(&:future_classes)
source_classes = source_reflection.
chain.
reject { |reflection| reflection.respond_to?(:polymorphic?) && reflection.polymorphic? }.
map(&:klass)
(through_classes + source_classes).uniq
end
end
private
def data_available?
owners.all? { |owner| loaded?(owner) } ||
through_preloaders.all?(&:run?) && source_preloaders.all?(&:run?)
end
def source_preloaders
@source_preloaders ||= ActiveRecord::Associations::Preloader.new(records: middle_records, associations: source_reflection.name, scope: scope, associate_by_default: false).loaders
end
def middle_records
through_records_by_owner.values.flatten
end
def through_preloaders
@through_preloaders ||= ActiveRecord::Associations::Preloader.new(records: owners, associations: through_reflection.name, scope: through_scope, associate_by_default: false).loaders
end
def through_reflection
reflection.through_reflection
end
def source_reflection
reflection.source_reflection
end
def source_records_by_owner
@source_records_by_owner ||= source_preloaders.map(&:records_by_owner).reduce(:merge)
end
def through_records_by_owner
@through_records_by_owner ||= through_preloaders.map(&:records_by_owner).reduce(:merge)
end
def preload_index
@preload_index ||= preloaded_records.each_with_object({}).with_index do |(record, result), index|
result[record] = index
end
end
def through_scope
scope = through_reflection.klass.unscoped
options = reflection.options
return scope if options[:disable_joins]
values = reflection_scope.values
if annotations = values[:annotate]
scope.annotate!(*annotations)
end
if options[:source_type]
scope.where! reflection.foreign_type => options[:source_type]
elsif !reflection_scope.where_clause.empty?
scope.where_clause = reflection_scope.where_clause
if includes = values[:includes]
scope.includes!(source_reflection.name => includes)
else
scope.includes!(source_reflection.name)
end
if values[:references] && !values[:references].empty?
scope.references_values |= values[:references]
else
scope.references!(source_reflection.table_name)
end
if joins = values[:joins]
scope.joins!(source_reflection.name => joins)
end
if left_outer_joins = values[:left_outer_joins]
scope.left_outer_joins!(source_reflection.name => left_outer_joins)
end
if scope.eager_loading? && order_values = values[:order]
scope = scope.order(order_values)
end
end
cascade_strict_loading(scope)
end
end
end
end
end