37signals open-sourced Fizzy, their Kanban tracker for issues & ideas. @dhh
I went through the code and stole 10 patterns you can reuse in your own Rails / SaaS app 👇
github.com/basecamp/fizzy
1️⃣ Account slug multi-tenancy (no namespaced controllers)
Fizzy uses a rack middleware (AccountSlug::Extractor) that:
Pulls the numeric account id from the URL
Rewrites SCRIPT_NAME / PATH_INFO
Wraps the request in Current.with_account
Result: the whole app is effectively mounted at /{account_id} and controllers stay clean & non-multi-tenant-aware.
2️⃣ Base36 UUIDv7 primary keys
They store UUIDv7 in binary columns but expose them as 25-char base36 strings:
Ordered by time
Short enough to copy/paste
Still globally unique
It’s a great pattern if you want “nice” IDs without giving up UUID semantics.
3️⃣ Account-aware background jobs
Fizzy extends Active Job so that every job:
Captures Current.account when enqueued
Serializes it into the payload
Restores it and runs inside Current.with_account on perform
No more “oops this job ran for the wrong tenant” in Sidekiq.
4️⃣ Sharded full-text search per account
They define 16 Search::Record::Trilogy shard classes (search_records_0…15).
Shard chosen by CRC32(account_id)
Data is stemmed before save
Queries use boolean MATCH ... AGAINST scoped with an account_key
That’s a cheap way to keep search fast as data grows.
5️⃣ Bundled email notifications with time windows
Notification::Bundle keeps a rolling window per user (starts_at..ends_at):
Collects unread notifications in that window
Sends a single digest when the window closes
Marks bundle as delivered
You get “less spammy” notifications without complex logic in each notifier.
6️⃣ Entropy-driven auto-postponing of stale cards
Boards + accounts have “entropy” settings.
Card::Entropic looks at last_active_at and can:
Find cards “postponing soon”
Auto-move overdue ones into a Not now column with auto_postpone_all_due
7️⃣ Activity spike & stall detection
Each card has an ActivitySpike record.
When last_active_at changes, Fizzy queues detection jobs and uses a stalled scope for cards that stayed quiet for 14+ days.
Great pattern if you want to highlight “truly stuck” work automatically.
8️⃣ Access revocation that actually scrubs data
Board::Accessible doesn’t just remove “access”.
When a user loses access, it scrubs:
Mentions
Notifications
Watches
via raw SQL joins so nothing leaks from other cards. An underrated privacy & UX pattern.
Share this Scrolly Tale with your friends.
A Scrolly Tale is a new way to read Twitter threads with a more visually immersive experience.
Discover more beautiful Scrolly Tales like this.
