Consolidate event roster/breakdown data sources into a population-parameterized service
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 35/100
Línea de trabajo
Comienza con app/services/event_dashboard.rb, app/services/attendees_roster.rb y app/services/attendees_breakdowns.rb; después sigue EventsController#roster y #attendees hasta los partials compartidos de roster y breakdown. Ejecuta las specs de request/view de Roster por evento y de Attendees entre eventos antes de cambiar el comportamiento. Se considera terminado cuando se hayan eliminado los servicios duplicados, ambos contextos rendericen de forma idéntica y los nuevos servicios de population, roster y breakdown compartan una sola implementación.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
🤖 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/AttendeesBreakdownswere deliberately written to mirrorEventDashboard'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 toEventDashboardand bothAttendees*services separately. EventDashboardis 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 apeoplecollection (attended-training registrations).app/services/attendees_breakdowns.rb— the breakdown-dataset subset over apeoplecollection.- 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(feedsEventDashboard,context: :event) andEventsController#attendees(feedsAttendees*,context: :index).
Proposed design
Introduce three POROs (names from the original plan; open to alternatives):
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.
RegistrantRoster.new(population)— every per-registrant lookup map_registrant_rosterreads. ReplacesAttendeesRoster+EventDashboard's roster maps.RegistrantBreakdowns.new(population)— every chart dataset +*_registrant_idsdrill-in map_registrant_breakdownsreads (including the pie denominators currently computed in ERB). ReplacesAttendeesBreakdowns+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
RegistrantPopulationwith both constructors and the shared date-anchored primitives; unit-spec the two anchorings explicitly (the reference-date behavior is currently only implicit). - Build
RegistrantRoster+RegistrantBreakdownsover a population; move the pie-denominator math out of_registrant_breakdowns.html.erbintoRegistrantBreakdowns. - Point
EventsController#attendeesat the new services; haveEventDashboardcompose + delegate. - Delete
AttendeesRoster/AttendeesBreakdowns. - Carry over the org-affiliation preload (the N+1 fix from PR #2113) into
RegistrantPopulationso 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.mdservices 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:
EventDashboardis 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: :eventvscontext: :indexdrill-in branching in_registrant_breakdowns.html.erbcan 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
AttendeesRosterandAttendeesBreakdownsare 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).
- Lenguaje dominante
- Ruby
- Estrellas
- 15
- Forks
- 28
- Merge medio
- 16 h 58 min
- PR fusionados (30 d)
- 291
Preparar el entorno
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Incluye un Dockerfile o un archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de rubyforgood/awbw
-
blocked
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
rubyforgood/awbw#2824 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
rubyforgood/awbw#2822 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
rubyforgood/awbw#2160 ·
Los mantenedores suelen responder en 1 día
-
Update callout faq defualt text when the new person subscription launchesPosiblemente ocupada @maebeale la tomó hace 84 días. Abiertoblocked
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
rubyforgood/awbw#2095 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
rubyforgood/awbw#2088 ·
Los mantenedores suelen responder en 1 día
Todos los issues de rubyforgood/awbw
Issues similares
-
intercepted.rb crashes on an optional positional argument when the method also takes keywordsAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
yegor256/intercepted#128 ·
-
documentation good first issue
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
ysb3996-blip/oops#51 ·
Los mantenedores suelen responder en 1 día
-
good first issue
Dificultad 1/5 Menos de una hora Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
Remove leftover Cloud66 custom_web process from ProcfilePosiblemente ocupada @ShahadatTest la tomó hoy. AbiertoDifficulty: Easy Good First Issue
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
rubyforgood/casa#7330 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
area:core-lib P2 size:S
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día