Skip to content

WAIT: Form-field identifiers + persist registration answers as structured data#1878

Closed
maebeale wants to merge 7 commits into
mainfrom
maebeale/additional-age-groups-prod-fix
Closed

WAIT: Form-field identifiers + persist registration answers as structured data#1878
maebeale wants to merge 7 commits into
mainfrom
maebeale/additional-age-groups-prod-fix

Conversation

@maebeale

@maebeale maebeale commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

🤖 PR, suggested 👤 review level: 🔬 Inspect — substantive logic across the registration service, a new column, and admin-gated profile UI

What is the goal of this PR and why is this important?

  • Accept additional_age_groups: production forms carrying the pluralized identifier rendered no options, rejected submissions, and never tagged age groups — all silently. The code only knew the singular additional_age_group.
  • Persist answers as structured data: several registration fields were saved only as form_answers, never mapped onto their existing model columns (mailing/agency country, org website, org type), and racial/ethnic identity had no structured home at all.
  • Align dev seeds with the real public AWBW forms (screenshots).

How did you approach the change?

  • Lifted age-group identifiers into PRIMARY/ADDITIONAL_AGE_GROUP_FIELD_IDENTIFIERS constants (mirroring the sector legacy-coverage pattern); additional_age_groups is now accepted everywhere age fields resolve.
  • Added people.racial_ethnic_identity, shown on the person profile and editable on the edit form, both gated to admins (allowed_to?(:manage?, Person); edit is already admin-only).
  • In PublicRegistration, mapped racial_ethnic_identity, mailing_country/agency_country, agency_website, and agency_type onto Person/Organization/Address. A non-blank answer overwrites the value on file (latest registration wins); the prior value is preserved by AhoyTrackable's after_update audit event. A blank answer never clobbers.
  • Updated the seeds to match the screenshots (CE wording, license disclaimer, "Primary Email", dropped nickname/pronouns, marketing order, payment note, "Partial Scholarship Application"), idempotently.

Anything else to add?

  • No data migration for the age-group fix — purely additive identifier recognition.
  • payment_method is intentionally left as a form answer only; at registration it drives the Stripe-checkout decision but persists no dedicated column.

Some production registration forms carry the pluralized identifier
"additional_age_groups" on the additional age-group field. The code only
ever recognized the canonical singular "additional_age_group", so on those
forms the field rendered with no options, rejected submissions, and never
tagged age groups — failing silently end to end.

Lift the hardcoded age-group identifiers into PRIMARY/ADDITIONAL identifier-
list constants (mirroring the sector legacy-coverage pattern) and accept the
plural alias everywhere they resolve: dynamic options, submission validation,
tagging, profile display, and the form-submission show page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread app/models/form_field.rb
"additional_age_group" => "AgeRange"
}.freeze
# Every "primary" and "additional" age group field (canonical or legacy) is
# backed by the published AgeRange categories. Unlike the sector fields, age

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Building DYNAMIC_FIELD_CATEGORY_TYPES from the identifier constants means dynamic option rendering, submission validation, and the option-source badge all pick up additional_age_groups for free — these consumers key off this hash, so they needed no further change.

primary_sector_ids = collect_ids(FormField::PRIMARY_SECTOR_FIELD_IDENTIFIERS)
additional_sector_ids = collect_ids(FormField::ADDITIONAL_SECTOR_FIELD_IDENTIFIERS)
primary_age_ids = collect_ids(FormField::PRIMARY_AGE_GROUP_FIELD_IDENTIFIERS)
additional_age_ids = collect_ids(FormField::ADDITIONAL_AGE_GROUP_FIELD_IDENTIFIERS)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Collecting across the identifier list (like sectors already did) means a form with either the canonical or the legacy plural name tags correctly; renamed the helper from collect_sector_ids to collect_ids since it now serves both.

@maebeale maebeale marked this pull request as ready for review June 22, 2026 19:24
@maebeale maebeale changed the title Accept "additional_age_groups" as a legacy age-group field identifier WAIT: Accept "additional_age_groups" as a legacy age-group field identifier Jun 22, 2026
maebeale and others added 2 commits June 22, 2026 16:33
Several registration fields were saved only as form_answers, never mapped onto
their existing model columns: mailing/agency country, organization website and
type. And there was no structured home for racial/ethnic identity at all.

- Add people.racial_ethnic_identity, shown on the person profile and editable
  on the edit form, both gated to admins (allowed_to?(:manage?, Person) for
  display; the edit page is already admin-only).
- In PublicRegistration, map racial_ethnic_identity, mailing_country/
  agency_country, agency_website, and agency_type onto Person/Organization/
  Address. A non-blank answer overwrites the value on file (latest registration
  wins); the prior value is preserved by AhoyTrackable's after_update audit
  event. A blank answer never clobbers existing data.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the dev registration/scholarship forms to the real form screenshots:
reword the CE question ("Do you seek CE hours for this training?"), add the
license-number disclaimer, label the primary email "Primary Email", drop the
optional nickname/pronouns questions, order "What motivated you" before "How
did you hear", note payment timing under Payment Information, and head the
scholarship section "Partial Scholarship Application".

Every clause is idempotent and only matches the prior default, so re-seeds and
admin edits are left alone — including updates that bring an already-seeded CE
block up to the new wording.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@maebeale maebeale changed the title WAIT: Accept "additional_age_groups" as a legacy age-group field identifier WAIT: Form-field identifiers + persist registration answers as structured data Jun 22, 2026
apply_value(person, :racial_ethnic_identity, field_value("racial_ethnic_identity"))
end

def sync_organization_profile(organization)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Overwrite (not fill-when-blank) is deliberate: the latest registration is the freshest source of truth, and because every model includes AhoyTrackable, this update! logs an Ahoy::Event capturing the prior→new value — so no history is lost. A blank answer is a no-op, and an unchanged value records no event.

</div>
<% end %>
<!-- Racial/ethnic identity (admin only) -->
<% if allowed_to?(:manage?, Person) && @person.racial_ethnic_identity.present? %>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 From Claude: Display is gated with allowed_to?(:manage?, Person) so an owner viewing their own profile (show? = admin? || owner?) does not see this; editing is already admin-only via PersonPolicy#edit?.

maebeale and others added 4 commits June 22, 2026 20:11
The scholarship edit view read the registration form's submission and filtered
for "scholarship" section fields, so it only worked when the questions were
embedded in the registration form. The app actually links a separate
role: "scholarship" form whose answers are saved as their own submission, so the
edit view showed "No scholarship questions were answered" even when they were.

load_scholarship_submission now prefers the event's dedicated scholarship form
(and its role: "scholarship" submission), falling back to the embedded-section
registration form for older events.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two more registration answers gain structured homes:

- expected_payment_method (string) on event_registrations records the
  registrant's stated payment intent (Credit card now/later, Check) — the one
  payment signal not otherwise visible for the non-Stripe paths. Set on
  registration and overwritten on re-registration (latest intent wins); shown
  read-only in the admin registration form. Bulk payment keeps it as a form
  answer only, since it creates no EventRegistration until allocation.

- mailing_list_consent_at / mailing_list_consent_source on people record email
  consent. Opt-in only: an affirmative answer stamps the time and source when
  none is on file; we never re-stamp or clear it from registration (withdrawal
  is a separate action). Shown admin-gated on the profile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Include the event start date in the consent source ("Registration: <title>
  (<date>)") so consent is traceable when many trainings share a title.
- Add a "Consented to mailing list" checkbox to the admin person form via a
  virtual mailing_list_consented attribute: unchecking withdraws (clears the
  timestamp and source), checking grants when none is on file (stamped "Admin
  update"), and re-checking leaves an existing consent untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lead with the ISO date so the registration source reads
"2026-06-23 Facilitator Training registration".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@maebeale

Copy link
Copy Markdown
Collaborator Author

🤖 From Claude: Closing — this branch's work has been split into focused PRs, and every concern here is now covered:

Nothing in this branch is left unrepresented, so it's superseded.

@maebeale maebeale closed this Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant