diff --git a/app/queries/dashboard_query.rb b/app/queries/dashboard_query.rb index 8c9f33537..00e0b69a8 100644 --- a/app/queries/dashboard_query.rb +++ b/app/queries/dashboard_query.rb @@ -22,18 +22,7 @@ def self.upcoming_events end def self.upcoming_events_for_user(member) - chapter_workshops = Workshop.upcoming - .eager_load(:chapter, :sponsors, :organisers, :permissions) - .where(chapter: member.chapters) - .to_a - - accepted_workshops = member.workshop_invitations.accepted - .joins(:workshop) - .merge(Workshop.upcoming) - .eager_load(workshop: %i[chapter sponsors organisers permissions]) - .map(&:workshop) - - all_events(chapter_workshops + accepted_workshops) + all_events(chapter_workshops_for(member) + accepted_workshops_for(member)) .sort_by(&:date_and_time) .group_by(&:date) end @@ -63,4 +52,23 @@ def self.upcoming_workshops .to_a end private_class_method :upcoming_workshops + + def self.chapter_workshops_for(member) + Workshop.upcoming + .eager_load(:chapter, :sponsors, :organisers, :permissions) + .preload(workshop_host: :sponsor) + .where(chapter: member.chapters) + .to_a + end + private_class_method :chapter_workshops_for + + def self.accepted_workshops_for(member) + member.workshop_invitations.accepted + .joins(:workshop) + .merge(Workshop.upcoming) + .eager_load(workshop: %i[chapter sponsors organisers permissions]) + .preload(workshop: { workshop_host: :sponsor }) + .map(&:workshop) + end + private_class_method :accepted_workshops_for end diff --git a/spec/queries/dashboard_query_spec.rb b/spec/queries/dashboard_query_spec.rb index bdc24fab2..ce9d2eecf 100644 --- a/spec/queries/dashboard_query_spec.rb +++ b/spec/queries/dashboard_query_spec.rb @@ -128,6 +128,34 @@ def read_venue(event) end describe '.upcoming_events_for_user' do + def create_invited_workshops(member:, count:, host:, extra_sponsor:) + count.times do + workshop = Fabricate(:workshop_no_sponsor) + Fabricate(:workshop_sponsor, workshop:, sponsor: extra_sponsor) + Fabricate(:workshop_sponsor, workshop:, sponsor: host, host: true) + Fabricate(:attending_workshop_invitation, member:, workshop:) + end + end + + def create_chapter_workshops(chapter:, count:, host:, extra_sponsor:) + count.times do + workshop = Fabricate(:workshop_no_sponsor, chapter:) + Fabricate(:workshop_sponsor, workshop:, sponsor: extra_sponsor) + Fabricate(:workshop_sponsor, workshop:, sponsor: host, host: true) + end + end + + def load_user_events_and_count_host_queries(member:, host:) + result = nil + queries = count_queries do + result = described_class.upcoming_events_for_user(member) + workshops = result.values.flatten.select { |event| event.is_a?(Workshop) } + expect(workshops.map(&:host)).to all(eq(host)) + end + + [result, queries] + end + it 'returns events for the member chapters and accepted workshops' do chapter = Fabricate(:chapter) member = Fabricate(:member, groups: [Fabricate(:students, chapter:)]) @@ -139,6 +167,57 @@ def read_venue(event) expect(result.values.flatten).to include(workshop) end + it 'preloads workshop hosts for chapter workshops without growing query count' do + chapter = Fabricate(:chapter) + member = Fabricate(:member, groups: [Fabricate(:students, chapter:)]) + host = Fabricate(:sponsor) + extra_sponsor = Fabricate(:sponsor) + + create_chapter_workshops(chapter:, count: 1, host:, extra_sponsor:) + result_one = described_class.upcoming_events_for_user(member) + one_query_count = count_queries { described_class.upcoming_events_for_user(member) } + + create_chapter_workshops(chapter:, count: 3, host:, extra_sponsor:) + result_four = nil + four_query_count = count_queries { result_four = described_class.upcoming_events_for_user(member) } + + expect(result_four.values.flatten.count { |event| event.is_a?(Workshop) }).to eq(4) + expect(result_four.values.flatten.select { |event| event.is_a?(Workshop) }.map(&:host)).to all(eq(host)) + expect(four_query_count).to be <= one_query_count + 2 + expect(result_one.values.flatten.select { |event| event.is_a?(Workshop) }.map(&:host)).to all(eq(host)) + end + + it 'preloads workshop hosts for accepted invitations without growing query count with workshop count' do + member = Fabricate(:member) + host = Fabricate(:sponsor) + extra_sponsor = Fabricate(:sponsor) + expect(member.chapters).to be_empty + + create_invited_workshops(member:, count: 1, host:, extra_sponsor:) + result_one, one_workshop_queries = load_user_events_and_count_host_queries(member:, host:) + expect(result_one.values.flatten.count { |event| event.is_a?(Workshop) }).to eq(1) + expect(one_workshop_queries).to be_positive + + create_invited_workshops(member:, count: 3, host:, extra_sponsor:) + result_four, four_workshop_queries = load_user_events_and_count_host_queries(member:, host:) + expect(result_four.values.flatten.count { |event| event.is_a?(Workshop) }).to eq(4) + expect(four_workshop_queries).to be_positive + expect(four_workshop_queries).to be <= one_workshop_queries + 2 + end + + it 'resolves host to nil for accepted workshops with only non-host sponsors' do + member = Fabricate(:member) + workshop = Fabricate(:workshop_no_sponsor) + Fabricate(:workshop_sponsor, workshop:, sponsor: Fabricate(:sponsor), host: false) + Fabricate(:attending_workshop_invitation, member:, workshop:) + + result = described_class.upcoming_events_for_user(member) + loaded = result.values.flatten.find { |event| event == workshop } + + expect(loaded).to eq(workshop) + expect(loaded.host).to be_nil + end + it 'returns an empty hash when the member has no upcoming events' do member = Fabricate(:member)