Skip to content

Commit 89db677

Browse files
authored
fix: improve performance by only serializing requested fields
2 parents 3b48024 + 8766f25 commit 89db677

File tree

4 files changed

+131
-59
lines changed

4 files changed

+131
-59
lines changed

app/controllers/forest_liana/application_controller.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,12 @@ def fields_per_model(params_fields, model)
156156
params_fields_hash.inject({}) do |fields, param_field|
157157
relation_name = param_field[0]
158158
relation_fields = param_field[1]
159+
forest_collection = ForestLiana.apimap.find { |collection| collection.name.to_s == model.to_s.gsub('::', '__') }
160+
smart_relations = forest_collection.fields_smart_belongs_to
159161

160162
if relation_name == ForestLiana.name_for(model)
161163
fields[relation_name] = relation_fields
162-
else
164+
elsif model.reflect_on_association(relation_name.to_sym)
163165
model_association = model.reflect_on_association(relation_name.to_sym)
164166
if model_association
165167
model_name = ForestLiana.name_for(model_association.klass)
@@ -173,7 +175,14 @@ def fields_per_model(params_fields, model)
173175
fields[model_name] = relation_fields
174176
end
175177
end
178+
else
179+
smart_relations.each do |smart_relation|
180+
if smart_relation[:field].to_s == relation_name
181+
fields[smart_relation[:reference].split('.').first] = relation_fields
182+
end
183+
end
176184
end
185+
177186
fields
178187
end
179188
else

app/serializers/forest_liana/serializer_factory.rb

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,81 @@ def self.get_serializer_name(active_record_class)
4747
end
4848
end
4949

50+
# duplicate method from Serializer
51+
ForestAdmin::JSONAPI::Serializer.singleton_class.send(:define_method, :find_recursive_relationships) do |root_object, root_inclusion_tree, results, options|
52+
ActiveSupport::Notifications.instrument(
53+
'render.jsonapi_serializers.find_recursive_relationships',
54+
{class_name: root_object.class.name},
55+
) do
56+
root_inclusion_tree.each do |attribute_name, child_inclusion_tree|
57+
next if attribute_name == :_include
58+
59+
serializer = ForestAdmin::JSONAPI::Serializer.find_serializer(root_object, options)
60+
unformatted_attr_name = serializer.unformat_name(attribute_name).to_sym
61+
object = nil
62+
is_collection = false
63+
is_valid_attr = false
64+
if serializer.has_one_relationships.has_key?(unformatted_attr_name)
65+
# only added this condition
66+
if root_object.class.reflect_on_association(unformatted_attr_name)&.polymorphic?
67+
options[:context][:unoptimized] = true
68+
end
69+
70+
is_valid_attr = true
71+
attr_data = serializer.has_one_relationships[unformatted_attr_name]
72+
object = serializer.has_one_relationship(unformatted_attr_name, attr_data)
73+
elsif serializer.has_many_relationships.has_key?(unformatted_attr_name)
74+
is_valid_attr = true
75+
is_collection = true
76+
attr_data = serializer.has_many_relationships[unformatted_attr_name]
77+
object = serializer.has_many_relationship(unformatted_attr_name, attr_data)
78+
end
79+
80+
if !is_valid_attr
81+
raise ForestAdmin::JSONAPI::Serializer::InvalidIncludeError.new(
82+
"'#{attribute_name}' is not a valid include.")
83+
end
84+
85+
if attribute_name != serializer.format_name(attribute_name)
86+
expected_name = serializer.format_name(attribute_name)
87+
88+
raise ForestAdmin::JSONAPI::Serializer::InvalidIncludeError.new(
89+
"'#{attribute_name}' is not a valid include. Did you mean '#{expected_name}' ?"
90+
)
91+
end
92+
93+
next if object.nil?
94+
95+
objects = is_collection ? object : [object]
96+
if child_inclusion_tree[:_include] == true
97+
objects.each do |obj|
98+
obj_serializer = ForestAdmin::JSONAPI::Serializer.find_serializer(obj, options)
99+
key = [obj_serializer.type, obj_serializer.id]
100+
101+
current_child_includes = []
102+
inclusion_names = child_inclusion_tree.keys.reject { |k| k == :_include }
103+
inclusion_names.each do |inclusion_name|
104+
if child_inclusion_tree[inclusion_name][:_include]
105+
current_child_includes << inclusion_name
106+
end
107+
end
108+
109+
current_child_includes += results[key] && results[key][:include_linkages] || []
110+
current_child_includes.uniq!
111+
results[key] = {object: obj, include_linkages: current_child_includes}
112+
end
113+
end
114+
115+
if !child_inclusion_tree.empty?
116+
objects.each do |obj|
117+
find_recursive_relationships(obj, child_inclusion_tree, results, options)
118+
end
119+
end
120+
end
121+
end
122+
nil
123+
end
124+
50125
def initialize(is_smart_collection = false)
51126
@is_smart_collection = is_smart_collection
52127
end
@@ -131,6 +206,51 @@ def relationship_related_link(attribute_name)
131206
ret
132207
end
133208

209+
def has_one_relationships
210+
return {} if self.class.to_one_associations.nil?
211+
data = {}
212+
self.class.to_one_associations.each do |attribute_name, attr_data|
213+
relation = object.class.reflect_on_all_associations.find { |a| a.name == attribute_name }
214+
next if !should_include_attr?(attribute_name, attr_data)
215+
216+
if relation && relation.belongs_to? && relation.polymorphic?.nil?
217+
reflection_primary_key = relation.options[:primary_key]&.to_sym || :id
218+
klass_primary_key = relation.klass.primary_key.to_sym
219+
220+
if reflection_primary_key != klass_primary_key
221+
data[attribute_name] = attr_data.merge({
222+
attr_or_block: proc {
223+
relation.klass.find_by(reflection_primary_key => object.send(relation.foreign_key))
224+
}
225+
})
226+
next
227+
end
228+
end
229+
230+
data[attribute_name] = attr_data
231+
end
232+
233+
data
234+
end
235+
236+
def should_include_attr?(attribute_name, attr_data)
237+
collection = self.type
238+
239+
unless @options.dig(:context, :unoptimized)
240+
return false unless @options[:fields][collection]&.include?(attribute_name.to_sym)
241+
end
242+
243+
# Allow "if: :show_title?" and "unless: :hide_title?" attribute options.
244+
if_method_name = attr_data[:options][:if]
245+
unless_method_name = attr_data[:options][:unless]
246+
formatted_attribute_name = format_name(attribute_name).to_sym
247+
show_attr = true
248+
show_attr &&= send(if_method_name) if if_method_name
249+
show_attr &&= !send(unless_method_name) if unless_method_name
250+
show_attr &&= @_fields[type.to_s].include?(formatted_attribute_name) if @_fields[type.to_s]
251+
show_attr
252+
end
253+
134254
private
135255

136256
def intercom_integration?

app/services/forest_liana/resources_getter.rb

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -74,55 +74,9 @@ def records
7474
end
7575
end
7676

77-
preload_cross_database_associations(records, preload_loads)
78-
7977
records
8078
end
8179

82-
def preload_cross_database_associations(records, preload_loads)
83-
preload_loads.each do |association_name|
84-
association = @resource.reflect_on_association(association_name)
85-
next unless separate_database?(@resource, association)
86-
87-
columns = columns_for_cross_database_association(association_name)
88-
if association.macro == :belongs_to
89-
foreign_key = association.foreign_key
90-
primary_key = association.klass.primary_key
91-
92-
ids = records.map { |r| r.public_send(foreign_key) }.compact.uniq
93-
next if ids.empty?
94-
95-
associated = association.klass.where(primary_key => ids)
96-
.select(columns)
97-
.index_by { |record| record.public_send(primary_key) }
98-
99-
records.each do |record|
100-
record.define_singleton_method(association_name) do
101-
associated[record.send(foreign_key.to_sym)] || nil
102-
end
103-
end
104-
end
105-
106-
if association.macro == :has_one
107-
foreign_key = association.foreign_key
108-
primary_key = association.active_record_primary_key
109-
110-
ids = records.map { |r| r.public_send(primary_key) }.compact.uniq
111-
next if ids.empty?
112-
113-
associated = association.klass.where(foreign_key => ids)
114-
.select(columns)
115-
.index_by { |record| record.public_send(foreign_key.to_sym) }
116-
117-
records.each do |record|
118-
record.define_singleton_method(association_name) do
119-
associated[record.send(primary_key.to_sym)] || nil
120-
end
121-
end
122-
end
123-
end
124-
end
125-
12680
def columns_for_cross_database_association(association_name)
12781
association = @resource.reflect_on_association(association_name)
12882

spec/requests/resources_spec.rb

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -124,18 +124,7 @@
124124
"included" => [{
125125
"type" => "Location",
126126
"id" => "1",
127-
"attributes" => include(
128-
"id" => 1,
129-
"created_at" => nil,
130-
"updated_at" => nil,
131-
"coordinates" => nil
132-
),
133-
"links" => { "self" => "/forest/location/1" },
134-
"relationships" => {
135-
"island" => {
136-
"links" => { "related" => {} }
137-
}
138-
}
127+
"links" => { "self" => "/forest/location/1" }
139128
}]
140129
})
141130
end

0 commit comments

Comments
 (0)