Remove dead columns and tables left by deleted features #3015
Description
Problem
Follow-up to the members.unsubscribed removal (#3007, merged in #3013/#3014). A scan of every table and column in the schema (46 tables, 377 columns) found four orphaned tables and three dead columns. Each item's feature code was removed months or years ago; only the schema and the production rows remain.
Findings
Dead tables — no model, controller, route, or view references remain:
| Table | Columns | Production rows | Code removed in |
|---|---|---|---|
jobs |
21 | 164 | f25f10b8 (2022-07-22, "remove job board code") |
courses |
14 | 6 | 4fb0f938 (2022-04-12, "Deletes deprecated Course code") |
course_invitations |
8 | 1755 | 4fb0f938 |
course_tutors |
4 | 7 | 4fb0f938 |
Dead columns on live tables:
| Column | Production data | Last usage removed in |
|---|---|---|
meetings.invites_sent |
51 meetings, all non-null (default false) |
39bbbe52 (2020-12-02) |
workshops.sign_up_url |
3 non-null of 2603, all pre-2014 | c9391a35 (2014-09-09) |
events.coach_description |
3 non-null of 260, all pre-2016 | de44fe17 (2016-06-14) |
events.coach_description has one residual write: spec/fabricators/event_fabricator.rb sets a Faker value. It goes with the drop.
Work plan
Sub-issues, ordered. Each column drop follows the #3007 two-step (see "Why the ignore step comes first"): a PR that adds the column to ignored_columns, then a separate PR that drops it.
- Remove the stale
ignored_columnsentries fromMember— both referenced columns are already gone. - Drop
meetings.invites_sent(two PRs). - Drop
workshops.sign_up_url(two PRs). - Drop
events.coach_description(two PRs; includes the fabricator cleanup). - Drop the four dead tables (one PR; no ignore step needed because nothing references them).
Why the ignore step comes first
Same reasoning as #3007, shortened: the release phase runs migrations before new dynos boot, and under Heroku preboot old dynos keep serving against the new schema during the window. Rails 8.1 builds explicit column lists, so an old release serving after the drop would fail every query on that table with PG::UndefinedColumn. Adding the column to ignored_columns and deploying first takes it out of the new release's SELECT list; the second deploy then drops it safely.
Verification notes
- ast-grep scan over
app,lib,config,specmatched every column name as identifier, symbol, hash key, and string; word-boundary grep over HAML/ERB; raw SQL and string scan overdb/. Zero references for every item above. - Association cross-check: the columns are not implicit foreign keys. No
belongs_to(default or explicitforeign_key) resolves toinvites_sent,sign_up_url, orcoach_description, and none is part of a polymorphic pair. - Database-level: no views or triggers reference any of the three columns; no foreign keys, views, or triggers touch the four tables;
events.coach_descriptionis absent from all controllerparams.expectwhitelists. - Production counts from a snapshot of the production dump (2026-10-12).
invites_sentwas read once more before being ignored;sign_up_urlandcoach_descriptionhave not been read since their commits.
Activity
Remove stale ignored_columns entries from Member#3016Drop meetings.invites_sent#3017Drop workshops.sign_up_url#3018Drop events.coach_description#3019Drop dead tables: jobs, courses, course_tutors, course_invitations#3020