Family Data Export#
The Family Data Export system lets family admins generate and download a ZIP archive of all their financial data for backup or migration. It is exposed through the web UI at /family_exports and a REST API at GET /api/v1/family_exports. Export generation is asynchronous — a background job builds the archive and attaches it via ActiveStorage; the UI then offers a download link once it is ready.
Scope: This covers export (generating ZIPs for download). For import (uploading NDJSON files), see the NDJSON Import System. For manual account entry, see Manual Account Entry and Import.
Key Files#
| File | Role |
|---|---|
app/models/family_export.rb | ActiveRecord model; status state machine, downloadable?, force_fail! |
app/models/family/data_exporter.rb | Core logic; generates the ZIP in memory |
app/jobs/family_data_export_job.rb | Background job that drives the export |
app/controllers/family_exports_controller.rb | Web controller (admin-only) |
Lifecycle#
- Trigger — An admin hits
POST /family_exports. The controller creates aFamilyExportrecord and enqueuesFamilyDataExportJob. - Processing — The job transitions the record to
processing, callsFamily::DataExporter#generate_export, attaches the resulting ZIP via ActiveStorage, then marks itcompleted. On any error it setsfailed. - Download — The controller's
downloadaction redirects to the ActiveStorage URL ifdownloadable?is true.downloadable?requires bothcompleted?status and an attached file. - Dead-worker recovery — If the job dies mid-flight, the record stays in
pending/processing. After 1 hour (PRESUMED_LOST_AFTER), admins can hitPOST /family_exports/:id/cancel, which callsforce_fail!. Because the ZIP is built entirely in memory, there is no partial artifact to clean up.
Status enum: pending → processing → completed | failed
Filename format: sure_export_YYYYMMDD_HHMMSS.zip
ZIP Contents#
Family::DataExporter#generate_export builds the archive in memory using Zip::OutputStream. The current export version is 2 .
| File in ZIP | Format | Contents |
|---|---|---|
version.txt | plain text | export_version: 2 |
accounts.csv | CSV | id, name, type, subtype, balance, currency, created_at |
transactions.csv | CSV | date, account_name, amount, name, category, tags, notes, currency — split parents excluded |
trades.csv | CSV | date, account_name, ticker, quantity, price, amount, currency |
categories.csv | CSV | name, color, parent_category, lucide_icon |
rules.csv | CSV | name, resource_type, active, effective_date, conditions (JSON), actions (JSON) |
attachments.json | JSON | Manifest of transaction and document attachments — binary files not included |
all.ndjson | NDJSON | Full-fidelity dump of all record types (see below) |
all.ndjson record types#
Each line is {"type": "<RecordType>", "data": {...}}. The NDJSON export includes richer data than the CSVs and covers types absent from them :
Account, Balance, Category, Tag, Merchant, RecurringTransaction, Transaction (with inline split_lines), Transfer, RejectedTransfer, Trade, Holding, Valuation, Budget, BudgetCategory, Rule
Split transactions: In CSV, parent split entries are excluded and only children appear . In NDJSON, the parent Transaction record carries an embedded split_lines array for each child entry.
Rule portability: Rule conditions and actions that reference categories, merchants, or tags resolve UUIDs to human-readable names and include a value_ref object {type, id, name} so importers can remap by name if IDs differ.
Access Control#
The web controller applies before_action :require_admin, which redirects non-admins to the root path . The UI renders inside the settings layout . API routes for family_exports are listed under /api/v1/ .