codebar / codebar/planner

Admin chapter workshops index is unbounded (no pagination + per-row COUNT queries)

Open
#2,899 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

good first issue performance
Dominant language
Ruby
Stars
104
Forks
205
Avg merge
1d 5h
Merged PRs (30d)
63

Description

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

  • The page is paginated and renders a bounded number of rows regardless of chapter age.
  • Query count drops from O(workshops) to O(1): pagination count query + one aggregate + sponsors preload.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with app/controllers/admin/workshops_controller.rb and the #rsvp pagination example, then inspect app/views/admin/workshops/index.html.haml where accepted invitation counts are rendered. Add bounded pagination and replace per-row counts with the aggregate lookup described in the issue; done means the page renders a fixed number of rows and meets the stated query-count criteria.

Written by the indexing model from the issue text.

Assessment

Tech stack
rails, ruby
Domain
backend, performance
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.