rubyforgood / rubyforgood/awbw

Consolidate 'active' definitions: derive org status from affiliations, retire stored organization_status

Open
#2,068 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

database cleanup enhancement
Dominant language
Ruby
Stars
15
Forks
26
Avg merge
12h 42m
Merged PRs (30d)
242

Description

Context

Organization#organization_status is a FK into the organization_statuses lookup table with six stored values: Active, Inactive, Pending, Reinstate, Suspended, Unknown. These are legacy, partly manually-set, and only loosely consistent with reality.

The maebeale/remove-org-status-columns branch took a non-destructive step:

  • Kept all six stored values. (An earlier commit added a destructive collapse migration; it was removed in 6cc9f6e41, never applied, no data lost.)
  • Added OrganizationStatus::PROGRAM_STATUS_BUCKETS, collapsing the six stored values into three display buckets: :active (Active, Reinstate), :formerly_active (Inactive, Suspended), :never_active (Pending, Unknown, none).
  • Reworked the Organization.program_status index-filter scope to prefer facilitator affiliations, falling back to the stored-status bucket only for orgs with no facilitator affiliations.

Problem: "active" is defined three inconsistent ways

Status is read three different ways, and they disagree:

  1. program_status scope (app/models/organization.rb:107) — affiliation-first: active facilitator affiliation ⇒ active; facilitator affiliations but none active ⇒ formerly active; no facilitator affiliations ⇒ stored-status bucket.
  2. active scope (app/models/organization.rb:70) — stored name == "Active" (exact string, so a stored "Reinstate" that buckets to :active is excluded) OR any active affiliation (Affiliation.activeany affiliation, not just facilitators).
  3. published? (app/models/organization.rb:279) — stored name == "Active" OR any active affiliation.

So "Reinstate" counts as active for the program-status chip but not for the active/published? scopes; and a non-facilitator active affiliation counts for active/published? but not for program_status. Separately, the affiliation.rb sync callbacks flip the stored status between "Active" and "Inactive" based on active facilitator affiliations — a fourth path that writes the very field the others partly ignore.

Goal

One source of truth for organization status, derived from affiliations, with stored organization_status retained only as a fallback for orgs with no facilitator-affiliation history — and eventually retired.

Options

A. Ship the current branch as-is. Non-destructive, data-safe, program-status UI reads correctly. Leaves the three-way inconsistency. Fine as an incremental step; not the endgame.

B. (recommended next) Unify the read paths. Rewrite the active scope and published? to use the same affiliation-first rule the program_status scope already uses (facilitator affiliations win; stored bucket is fallback). Result: one definition of "active"; stored status only matters for affiliation-less orgs. No data migration; reversible.

C. Retire organization_status entirely (long-term). Only after B is proven against real data. Snapshot each org's stored value into a flat organizations.legacy_status string first (frozen audit record), then drop the FK, the organization_statuses table, the sync callbacks, and the bucketing constant. The snapshot is the only way to later audit "what was this org before" — do not skip it.

Recommendation

Ship A now (already done and safe) → do B as the next unit of work → hold C until B has run against real data and the mismatches are reviewed.

Decided against

Backfilling placeholder Facilitator affiliations onto every stored-"Active" org to force the derivation to agree. Rejected: it fabricates evidence, requires a mandatory placeholder Person per affiliation, and is unnecessary — the fallback-to-stored-status path already keeps those orgs reading as active without inventing data.

Reconciliation to do before C

Because the stored status was partly manual, some orgs' stored value won't match what affiliations imply (the org form already warns "Does not match affiliations status"). Before dropping the field, list and review these — especially stored Suspended/Pending orgs that would derive as active — so real signal isn't lost.

Key references

  • app/models/organization_status.rbPROGRAM_STATUS_BUCKETS, program_bucket, names_for_bucket
  • app/models/organization.rbactive scope (:70), program_status scope (:107), published? (:279), program_status method (:253)
  • app/models/affiliation.rbsync_organization_status_with_affiliations (:122+), facilitators scope / facilitator?, FACILITATOR_TITLE

🤖 Drafted by Claude.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read app/models/organization.rb alongside app/models/organization_status.rb and app/models/affiliation.rb. Compare the active scope, program_status scope, published?, and affiliation sync callback to define the shared facilitator-first rule. Done means the read paths agree while stored status remains a fallback for organizations without facilitator-affiliation history; reconciliation for a later removal is described but not part of this unit.

Written by the indexing model from the issue text.

Assessment

Tech stack
ruby
Domain
backend
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.