Skip to content

Commit e040718

Browse files
authored
feat: reduce redundant queries on cross-db belongs_to (#734)
1 parent 9d1fb09 commit e040718

File tree

2 files changed

+77
-5
lines changed

2 files changed

+77
-5
lines changed

app/services/forest_liana/base_getter.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ def compute_includes
2121
@includes = ForestLiana::QueryHelper.get_one_association_names_symbol(@resource)
2222
end
2323

24-
def optimize_record_loading(resource, records)
24+
def optimize_record_loading(resource, records, force_preload = true)
2525
polymorphic, preload_loads = analyze_associations(resource)
2626
result = records.eager_load(@includes.uniq - preload_loads - polymorphic)
2727

28-
result = result.preload(preload_loads) if Rails::VERSION::MAJOR >= 7
28+
result = result.preload(preload_loads) if Rails::VERSION::MAJOR >= 7 && force_preload
2929

3030
result
3131
end

app/services/forest_liana/resources_getter.rb

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ def perform
3737
end
3838

3939
if includes.empty? || has_smart_fields
40-
@records = optimize_record_loading(@resource, @records)
40+
@records = optimize_record_loading(@resource, @records, false)
4141
else
4242
select = compute_select_fields
43-
@records = optimize_record_loading(@resource, @records).references(includes).select(*select)
43+
@records = optimize_record_loading(@resource, @records, false).references(includes).select(*select)
4444
end
4545

4646
@records
@@ -55,7 +55,79 @@ def query_for_batch
5555
end
5656

5757
def records
58-
@records.offset(offset).limit(limit).to_a
58+
records = @records.offset(offset).limit(limit).to_a
59+
60+
polymorphic_association, preload_loads = analyze_associations(@resource)
61+
if polymorphic_association && Rails::VERSION::MAJOR >= 7
62+
# TODO
63+
end
64+
65+
preload_cross_database_associations(records, preload_loads)
66+
67+
records
68+
end
69+
70+
def preload_cross_database_associations(records, preload_loads)
71+
preload_loads.each do |association_name|
72+
association = @resource.reflect_on_association(association_name)
73+
next unless separate_database?(@resource, association)
74+
75+
columns = columns_for_cross_database_association(association_name)
76+
77+
if association.macro == :belongs_to
78+
foreign_key = association.foreign_key
79+
primary_key = association.klass.primary_key
80+
81+
ids = records.map { |r| r.public_send(foreign_key) }.compact.uniq
82+
next if ids.empty?
83+
84+
associated = association.klass.where(primary_key => ids)
85+
.select(columns)
86+
.index_by { |record| record.public_send(primary_key) }
87+
88+
records.each do |record|
89+
record.define_singleton_method(association_name) do
90+
associated[record.send(foreign_key.to_sym)] || nil
91+
end
92+
end
93+
end
94+
95+
if association.macro == :has_one
96+
foreign_key = association.foreign_key
97+
primary_key = association.active_record_primary_key
98+
99+
ids = records.map { |r| r.public_send(primary_key) }.compact.uniq
100+
next if ids.empty?
101+
102+
associated = association.klass.where(foreign_key => ids)
103+
.select(columns)
104+
.index_by { |record| record.public_send(foreign_key.to_sym) }
105+
106+
records.each do |record|
107+
record.define_singleton_method(association_name) do
108+
associated[record.send(primary_key.to_sym)] || nil
109+
end
110+
end
111+
end
112+
end
113+
end
114+
115+
def columns_for_cross_database_association(association_name)
116+
return [:id] unless @params[:fields].present?
117+
118+
fields = @params[:fields][association_name.to_s]
119+
return [:id] unless fields
120+
121+
base_fields = fields.split(',').map(&:strip).map(&:to_sym) | [:id]
122+
123+
association = @resource.reflect_on_association(association_name)
124+
extra_key = association.foreign_key
125+
126+
# Add the foreign key used for the association to ensure it's available in the preloaded records
127+
# This is necessary for has_one associations, without it calling record.public_send(foreign_key) would raise a "missing attribute" error
128+
base_fields << extra_key if association.macro == :has_one
129+
130+
base_fields.uniq
59131
end
60132

61133
def compute_includes

0 commit comments

Comments
 (0)