Consolidate event roster/breakdown data sources into a population-parameterized service

Aperta
#2,116 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
35/100
Tipo di issue
Refactoring
Chiarezza
Specificata chiaramente
Stato di attività
Tranquilla
Stack tecnologico
ruby
Ambito
backend

Direzione di ricerca

Inizia da app/services/event_dashboard.rb, app/services/attendees_roster.rb e app/services/attendees_breakdowns.rb, quindi segui EventsController#roster e #attendees fino ai partial condivisi di roster e breakdown. Esegui le spec di request/view per il Roster del singolo evento e per gli Attendees tra eventi prima di modificare il comportamento. Il lavoro è completo quando i servizi duplicati sono rimossi, entrambi i contesti eseguono il rendering in modo identico e i nuovi servizi di population, roster e breakdown condividono un’unica implementazione.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

enhancement event mgmt

🤖 From Claude: filed at the maintainer's request as a follow-up to the events-IA refactor (PR #2113).

Summary

The per-event Roster page and the cross-event Attendees index render the same shared partials (app/views/events/_registrant_roster.html.erb, app/views/events/_registrant_breakdowns.html.erb) but are fed by two different data sources that each re-implement the same roster/breakdown method surface over a different population:

  • EventDashboard — one event's active registrants (registered + attended + incomplete_attendance + transferred_in), anchored to the event date.
  • AttendeesRoster / AttendeesBreakdowns — the cross-event population of people who attended a facilitator training, anchored to "now".

This is the deferred "Phase 4" of the IA work. The goal is to collapse the duplicated roster/breakdown logic behind a single population-parameterized service so there's one implementation, differing only in which people, via which registrations, as of when.

Why

  • Duplication / drift risk. AttendeesRoster/AttendeesBreakdowns were deliberately written to mirror EventDashboard's method names and return shapes so the shared partials read either one. Any change to a breakdown (a new dimension, a bug fix, a query optimization) must be made in two places or they silently diverge. The recent affiliations-N+1 fix, for example, had to be applied to EventDashboard and both Attendees* services separately.
  • EventDashboard is a god object (~1,300 lines, ~120 public methods) doing money, attendance, scholarships, roster maps, and breakdown datasets. The roster/breakdown concern is cleanly separable.
  • The "which people / as of when" difference is currently implicit and hand-rolled in each service, rather than an explicit, testable property.

Current state (files)

  • app/services/event_dashboard.rb — roster maps (registrants, organization_ids_by_registrant, primary_sector_names_by_registrant, scholarship_by_recipient, program_statuses_by_registrant, location_label_by_registrant, ce_registration_by_registrant, …) and breakdown datasets (primary_sectors/sector_counts, age_groups, state_counts, country_counts, program_status_counts/program_status_by_organization/program_status_registrant_ids, life_experiences, settings, registrant_city_breakdown, organizations, scholarship/CE pies, and the *_registrant_ids_by_* drill-in maps).
  • app/services/attendees_roster.rb — the roster-map subset over a people collection (attended-training registrations).
  • app/services/attendees_breakdowns.rb — the breakdown-dataset subset over a people collection.
  • Shared consumers: _registrant_roster.html.erb, _registrant_breakdowns.html.erb, _breakdown_card.html.erb, _registrant_city_row.html.erb, _registrant_city_breakdown.html.erb.
  • Controller: EventsController#roster (feeds EventDashboard, context: :event) and EventsController#attendees (feeds Attendees*, context: :index).

Proposed design

Introduce three POROs (names from the original plan; open to alternatives):

  1. RegistrantPopulation — the single source of "which people, via which registrations, as of when." Constructors for each context, e.g.
    • RegistrantPopulation.for_event(event) → the event's active registrations, reference_date = event start.
    • RegistrantPopulation.attended_trainings(people_scope) → attended facilitator-training registrations, reference_date = today.
      Owns the shared, date-anchored primitives currently duplicated: city_by_organization, program_status_by_organization, org/person id maps, the reference-date anchoring, and the org-affiliation preload.
  2. RegistrantRoster.new(population) — every per-registrant lookup map _registrant_roster reads. Replaces AttendeesRoster + EventDashboard's roster maps.
  3. RegistrantBreakdowns.new(population) — every chart dataset + *_registrant_ids drill-in map _registrant_breakdowns reads (including the pie denominators currently computed in ERB). Replaces AttendeesBreakdowns + EventDashboard's breakdown methods.

EventDashboard keeps money / attendance / scholarship-money / applicants / shout-outs, composes a RegistrantPopulation.for_event, and delegates the roster/breakdown method names so dashboard.html.erb / recipients.html.erb / roster.html.erb don't change.

Tasks / scope

  • Build RegistrantPopulation with both constructors and the shared date-anchored primitives; unit-spec the two anchorings explicitly (the reference-date behavior is currently only implicit).
  • Build RegistrantRoster + RegistrantBreakdowns over a population; move the pie-denominator math out of _registrant_breakdowns.html.erb into RegistrantBreakdowns.
  • Point EventsController#attendees at the new services; have EventDashboard compose + delegate.
  • Delete AttendeesRoster / AttendeesBreakdowns.
  • Carry over the org-affiliation preload (the N+1 fix from PR #2113) into RegistrantPopulation so it lives in one place.
  • Port the two deleted services' specs to the new names; add per-event-population examples (reference-date anchoring).
  • Update AGENTS.md services section.

Risks / notes

  • Behavior-preserving refactor. The per-event Roster and cross-event Attendees request/view specs are the safety net — their output must be unchanged. This is the main reason it was deferred: EventDashboard is large and the roster/breakdown extraction touches many methods.
  • Watch the subtle sourcing difference already encoded today: on the per-event Roster the scholarship/CE/org columns come from this event's registrations; on the cross-event index they come from each person's attended-training registrations. The population abstraction must make "which registrations" an explicit property so both are expressed without branching in the views.
  • The context: :event vs context: :index drill-in branching in _registrant_breakdowns.html.erb can likely be simplified once the population owns the id maps, but that's optional polish.

Out of scope

  • No DB/schema changes.
  • No UI/behavior changes — pure internal consolidation.
  • No policy changes.

Acceptance criteria

  • AttendeesRoster and AttendeesBreakdowns are gone; one roster service + one breakdown service back both the per-event Roster and the cross-event Attendees index.
  • The per-event Roster, cross-event Attendees, dashboard, and recipients pages render identically (existing request/view specs pass unchanged).
  • Adding a new breakdown dimension requires editing exactly one place.
References
  • PR #2113 (events IA refactor) — where these services were renamed and the N+1 was fixed in triplicate (the motivating pain).
Lingua principale
Ruby
Stelle
15
Fork
28
Merge medio
16h 58m
PR unite (30g)
291

Preparare l'ambiente

Apri in Codespaces

Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di rubyforgood/awbw

Tutte le issue di rubyforgood/awbw

Issue simili

Altre issue su Ruby

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.