Skip to content

Show affiliation history as a merged timeline - #2260

Draft
maebeale wants to merge 3 commits into
maebeale/facilitator-boolean-affiliationsfrom
maebeale/affiliation-timeline
Draft

Show affiliation history as a merged timeline#2260
maebeale wants to merge 3 commits into
maebeale/facilitator-boolean-affiliationsfrom
maebeale/affiliation-timeline

Conversation

@maebeale

@maebeale maebeale commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

🤖 suggested review level: 3 Read 📖 two read-only views over existing data; no writes, no migrations

Split out of #2195. Stacked on #2259 (STI subtypes) — retarget to main once that merges.

Why

An affiliation's dates say what is true now, not how it got there. The Ahoy lifecycle events already record every edit, and the trainings and membership periods that explain those edits live in their own tables. This puts all three in one time-ordered view.

What

Affiliation edit → History section

Merged newest-first in Change | What | When | By columns: recorded edits (Field: before → after), the person's facilitator trainings, membership periods, and which registration minted the row.

Person → Affiliation history page

Person-level counterpart, reached from the gear on the affiliations section. Admin-only.

Why trainings and memberships aren't read from Ahoy

  • track_lifecycle_event bails unless a Current.user/Current.source is set, so imported and seeded rows have no events at all
  • Ahoy records changes, not state — a registration created already attended and never edited would never appear

Ahoy is used only for the affiliation's own columns, where nothing else records them.

STI interaction (worth a look)

ResourceHistory matches every STI name for the row's table, not just record.class.name. On this base an affiliation is a FacilitatorAffiliation, so matching the current class alone would drop history filed as Affiliation before the subtypes existed, and history filed under the other subtype from before a retitle re-typed the row. Covered by two specs.

Notes

  • AffiliationDecorator#date_range is added here because the timeline needs it; it also exists on Reconcile facilitator affiliations against attendance (admin bulk action) #2195, so whichever lands second will have a trivial conflict on that method.
  • Analytics::ResourceHistory is generic — any record's Ahoy history, so the next edit page that wants this is a two-line wire-up
  • Membership entries are gated on Membership.enabled?, so nothing renders in production
  • Read-only throughout: no writes, no migrations, no schema changes

maebeale and others added 3 commits August 19, 2026 09:59
The "is this a facilitator affiliation?" fact was derived from a raw,
collation-sensitive BINARY TRIM(title) = 'Facilitator' scope re-encoded in
Ruby and three JS controllers. Denormalize it to a boolean column kept in
sync from the title, so the SQL scope reads a plain flag instead of raw SQL.
Title stays the input; the two-row (job + Facilitator) model is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the facilitator boolean with STI: FacilitatorAffiliation and
JobAffiliation (default). The subtype is derived from the title in a
before_validation, so title stays the single source of truth and a retitle
re-types the row. Server-authoritative — no form-submitted type needed.

Key STI accommodations: the type column has no default (a default subclass
name makes Affiliation.new build that subclass and break reload after the
callback re-types); #facilitator?/.facilitators read the type column;
subtypes share Affiliation's routes/param-key/dom_id via self.model_name and
authorize through AffiliationPolicy via self.policy_class.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reading an affiliation's dates tells you what is true now but not how it got
there. The Ahoy lifecycle events already record every edit, and the trainings and
membership periods that explain those edits live in their own tables — this puts
all three in one time-ordered view so an admin can see why a row looks the way it
does without leaving the page.

Trainings and memberships are read from their own tables rather than from Ahoy:
Ahoy records *changes*, and only those made while a Current.user or Current.source
was set, so imported and seeded rows have no events at all. Ahoy is used only for
the affiliation's own columns, where nothing else records them.

Ahoy events are matched on every STI name for the row's table, not just the
record's current class. A row filed as Affiliation before the subtypes existed —
or under the other subtype before a retitle re-typed it — would otherwise lose
that history.

Split out of #2195; stacked on #2259 for the STI subtypes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@maebeale
maebeale force-pushed the maebeale/affiliation-timeline branch from 263bb67 to 4307c83 Compare August 19, 2026 14:15
@maebeale
maebeale changed the base branch from maebeale/facilitator-affiliation-reconciliation to maebeale/facilitator-boolean-affiliations August 19, 2026 14:15
@maebeale
maebeale force-pushed the maebeale/facilitator-boolean-affiliations branch from e6f321b to 75e1fb5 Compare August 21, 2026 16:43
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