Denormalize the facilitator affiliation flag off the title - #2259
Denormalize the facilitator affiliation flag off the title#2259maebeale wants to merge 7 commits into
Conversation
|
@jmilljr24 as i work more w the affiliations and org's art program status and org's active/inactive status, it'd really help to have the affiliation itself identified by more than the Title string on affiliation. you ok w adding this boolean? i still want to do the same js style changes based on the title field, so this also includes drying up that logic bc it's used in more than one place rn. |
jmilljr24
left a comment
There was a problem hiding this comment.
I agree, this makes sense. Some of this will be surfaced again when membership gets fully built but I think a boolean to capture this on affiliation has been knocking on our door for a while.
67c33bc to
afbe467
Compare
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>
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>
Ahoy stamps resource_type from the saving instance's class, so splitting Affiliation into subtypes scattered a single row's history across three names: "Affiliation" for everything recorded before the split (and for freshly built rows), plus the two subtypes once a row is loaded back. Both readers assumed one name, so the person History card silently dropped affiliation edits and the affiliation edit page lost its created-by/updated-by attribution. Models now declare which names their events can carry, so the next STI split doesn't have to rediscover this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
e6f321b to
75e1fb5
Compare
The STI subtypes bought a typed model but cost a second source of truth for every name the record answers to: model_name, policy_class and an event_resource_types indirection for Ahoy all existed only to make two subclasses keep behaving like one Affiliation. The boolean says the same thing about the same rows without splitting the class. Reverts da0d5e7..75e1fb5, restoring the tree from 1537201. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CLAUDE.md requires AGENTS.md to track added rake tasks. Also drops the duplicate migrate_sectors.rake line, which made the count read right by accident. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@jmilljr24 i talked myself out of this bc i'd still want to keep it simple for staff and therefore need that callback reconciliation on title. at that point, the boolean is just row-level caching and doesn't buy us much. def open to re-opening. |
🤖 suggested review level: 5 Inspect 🔬 denormalized flag on a core model, plus a migration and a post-deploy backfill that gates org Art Program status
What is the goal of this PR and why is this important?
BINARY TRIM(title) = 'Facilitator'SQL scope.How did you approach the change?
Model
affiliations.facilitatorboolean (null: false, default: false).sync_facilitator_from_title(before_validation) keeps it in step with the title: exactly"Facilitator", trimmed, case-sensitive..facilitatorsreads the flag;#facilitator?is the column's own reader.titleviaupdate_columns/update_all, which skips the callback and lets the flag drift.JS
lib/affiliation.js(isFacilitatorTitle); the three affiliation editors share it for their live preview.rake affiliations:backfill_facilitatorimmediately after deploy.facilitator: false, so.facilitatorsmatches nothing.Affiliation#sync_organization_status_with_affiliations(anafter_save) sees no active facilitators and flips the org to Inactive viaupdate_column, loggingautochange.organization. Any affiliation save triggers it, including public event registration.UPDATEs into the migration'supcloses the window.Anything else to add?
FacilitatorAffiliation/JobAffiliation). Reverted: the subtypes neededmodel_name,policy_class, and an Ahoyevent_resource_typesindirection purely to keep behaving like oneAffiliation.