Summary
Admin::WorkshopsController#index (/admin/chapters/:chapter_id/workshops) loads every workshop for the chapter and runs one extra SQL COUNT per rendered row. Slow on older chapters — chapter 1 (London) has ~13 years of workshops (~700+).
Symptom
- One page view loads 700+
Workshop records and renders 700+ <tr> rows.
- Each row's
workshop.invitations.accepted.count emits a separate SQL COUNT — ~700 queries per page view.
- Memory, DB time, and DOM size all scale with chapter age, so the page degrades as the chapter grows.
Root cause (two compounding problems)
1. Unbounded result set
app/controllers/admin/workshops_controller.rb:
@workshops = @chapter.workshops.includes(:sponsors)
No pagination. The view renders all rows in a single table.
2. N+1 COUNT queries in the view
app/views/admin/workshops/index.html.haml:
= workshop.invitations.accepted.count
One query per workshop per render. Each COUNT is cheap (index index_workshop_invitations_workshop_attending exists), but the round trips accumulate across all rows.
Proposed fix
Paginate with Pagy (already used elsewhere in this controller, e.g. #rsvp):
@pagy, @workshops = pagy(@chapter.workshops.includes(:sponsors), items: 50)
Ordering is already deterministic via default_scope { order('date_and_time DESC') } and index_workshops_on_date_and_time, so offset pagination is fine at codebar's scale.
Replace per-row COUNTs with one aggregate query, keyed by workshop id and looked up in the view:
@accepted_counts = @workshops.joins(:invitations)
.where(invitations: { attending: true })
.group('invitations.workshop_id').count
Optional follow-ups (only if still slow after the above)
- Default the listing to upcoming + recent past (e.g.
where(date_and_time: 1.year.ago..)) with a link to the full list.
- Fragment caching per row — probably unnecessary once pagination lands.
Acceptance criteria
Summary
Admin::WorkshopsController#index(/admin/chapters/:chapter_id/workshops) loads every workshop for the chapter and runs one extra SQLCOUNTper rendered row. Slow on older chapters — chapter 1 (London) has ~13 years of workshops (~700+).Symptom
Workshoprecords and renders 700+<tr>rows.workshop.invitations.accepted.countemits a separate SQL COUNT — ~700 queries per page view.Root cause (two compounding problems)
1. Unbounded result set
app/controllers/admin/workshops_controller.rb:No pagination. The view renders all rows in a single table.
2. N+1 COUNT queries in the view
app/views/admin/workshops/index.html.haml:= workshop.invitations.accepted.countOne query per workshop per render. Each COUNT is cheap (index
index_workshop_invitations_workshop_attendingexists), but the round trips accumulate across all rows.Proposed fix
Paginate with Pagy (already used elsewhere in this controller, e.g.
#rsvp):Ordering is already deterministic via
default_scope { order('date_and_time DESC') }andindex_workshops_on_date_and_time, so offset pagination is fine at codebar's scale.Replace per-row COUNTs with one aggregate query, keyed by workshop id and looked up in the view:
Optional follow-ups (only if still slow after the above)
where(date_and_time: 1.year.ago..)) with a link to the full list.Acceptance criteria