A membership system moved off Perl, without a big bang
- Sector
- Membership federation
- Period
- 2024–2025
- Role
- Sole developer
- Stack
- Ruby on Rails 8.1 · PostgreSQL · Devise · Hotwire · Tailwind CSS
Situation
A federation organised in three tiers — provinces, then federations, then local departments — ran its entire membership administration on Perl under Apache mod_perl with HTML::Mason templates.
The software did real work. Volunteers used it to keep member records and memberships current as people joined, moved between departments and lapsed; to record who sat on which board at which level and for how long; to manage groups that cut across the hierarchy; and to run a shared calendar of recurring events, categorised and owned by the unit that organised them. Every level saw only what it was entitled to see, and every change was recorded.
It worked, and it had worked for years. The problem was that the number of people who could safely change it was falling every year. Replacing software that works is harder than replacing software that doesn't: there is no crisis to justify downtime, no tolerance for regression, and a decade of volunteer muscle memory you are obliged to respect.
What I built
The same system, rebuilt, with the hierarchy modelled explicitly instead of implied by queries — provinces containing federations containing departments, with memberships, cross-cutting groups, board terms, and a calendar of events and their occurrences hanging off that tree, all under per-level permissions and an immutable audit trail.
The migration mattered more than the rewrite. Rather than a one-off conversion, the import was built as repeatable tasks: lookup tables first, then core records, pulled from the live legacy database on demand. Because it could be re-run at will, the new system was validated against real production data over and over, and the cutover was a rehearsed operation rather than an event.
The detail I'd point to is passwords. The old system stored Unix crypt()
hashes, which cannot be reversed. The usual options are to force every member
to reset — a support burden, and a poor first impression of the new system —
or to carry the weak hashes forward. Instead, a custom authentication
strategy verifies the legacy hash at login and immediately rehashes the
password with bcrypt, discarding the old value. No member was asked to reset
anything, and the weak hashes drained away as people signed in.
The original Dutch URLs were kept, so bookmarks and habits survived the move.
Outcome
The federation runs on a stack its next developer will recognise, and volunteers got a faster, mobile-friendly interface without being asked to relearn their jobs or prove who they were.
Incremental beat big-bang for an unglamorous reason: because the import could be run repeatedly, every assumption about the old data was tested against reality long before cutover. Most of the risk in a legacy replacement lives in the data, not the code.