Skip to content

Commit 552e3ac

Browse files
committed
Refactor fetch_q_and_a: Filter answers at db level
Previously, `fetch_q_and_a` iterated over all plan questions and searched for matching answers in Ruby, performing theme filtering and Q&A formatting in memory. This caused unnecessary loops, potential N+1 lookups, and inefficient handling of multi-theme questions. Accessing questions directly via `Plan.questions` also triggered joins through sections, phases, and templates, along with ordering (due to default scope), which added query overhead. The refactored version: - Queries answers directly via a join to question themes, filtering at the DB level. - Accesses questions through the answers (`answer.question`) instead of `plan.questions`, avoiding heavy joins and unnecessary default-scoped sorting.
1 parent d15c601 commit 552e3ac

1 file changed

Lines changed: 24 additions & 12 deletions

File tree

app/presenters/api/v2/research_output_presenter.rb

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,25 +55,37 @@ def fetch_q_and_a_as_single_statement(themes:)
5555
fetch_q_and_a(themes: themes).collect { |item| item[:description] }.join('<br>')
5656
end
5757

58-
def fetch_q_and_a(themes:) # rubocop:disable Metrics/CyclomaticComplexity
58+
def fetch_q_and_a(themes:)
5959
return [] unless themes.is_a?(Array) && themes.any?
6060

61-
themes.filter_map do |theme|
62-
qs = questions_for_theme(theme)
63-
descr = qs.filter_map do |q|
64-
a = @plan.answers.find { |ans| ans.question_id == q.id }
65-
next unless a.present?
61+
answers = answers_for_themes(themes)
6662

67-
format_q_and_a(q, a)
68-
end
69-
next if descr.blank?
63+
descs_by_theme = build_descriptions_by_theme_hash(answers, themes)
7064

71-
{ title: theme, description: descr }
65+
descs_by_theme.map do |theme, descs|
66+
{ title: theme, description: descs }
7267
end
7368
end
7469

75-
def questions_for_theme(theme)
76-
@plan.questions.select { |q| q.themes.collect(&:title).include?(theme) }
70+
def answers_for_themes(themes)
71+
@plan.answers
72+
.joins(question: :themes)
73+
.where(themes: { title: themes })
74+
.includes(question: :themes)
75+
.distinct
76+
end
77+
78+
def build_descriptions_by_theme_hash(answers, themes)
79+
descs_by_theme = Hash.new { |h, k| h[k] = [] }
80+
81+
answers.each do |answer|
82+
answer.question.themes.each do |theme|
83+
next unless themes.include?(theme.title)
84+
85+
descs_by_theme[theme.title] << format_q_and_a(answer.question, answer)
86+
end
87+
end
88+
descs_by_theme
7789
end
7890

7991
def format_q_and_a(question, answer)

0 commit comments

Comments
 (0)