Show affiliation history as a merged timeline - #2260
Draft
maebeale wants to merge 3 commits into
Draft
Conversation
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
force-pushed
the
maebeale/affiliation-timeline
branch
from
August 19, 2026 14:15
263bb67 to
4307c83
Compare
maebeale
changed the base branch from
maebeale/facilitator-affiliation-reconciliation
to
maebeale/facilitator-boolean-affiliations
August 19, 2026 14:15
maebeale
force-pushed
the
maebeale/facilitator-boolean-affiliations
branch
from
August 21, 2026 16:43
e6f321b to
75e1fb5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 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
mainonce 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 | Bycolumns: 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_eventbails unless aCurrent.user/Current.sourceis set, so imported and seeded rows have no events at allattendedand never edited would never appearAhoy is used only for the affiliation's own columns, where nothing else records them.
STI interaction (worth a look)
ResourceHistorymatches every STI name for the row's table, not justrecord.class.name. On this base an affiliation is aFacilitatorAffiliation, so matching the current class alone would drop history filed asAffiliationbefore 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_rangeis 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::ResourceHistoryis generic — any record's Ahoy history, so the next edit page that wants this is a two-line wire-upMembership.enabled?, so nothing renders in production