Spec: Taxonomy Category Requests
Decided on 2026-07-12.
The decision in one sentence
We will build a new table taxonomy_category_requests that stores what an employer typed into the "Others" free-text input on the job form, together with a JSON snapshot of the form values at that moment — so the weekly taxonomy review has real evidence behind every category request.
Context
The 2026-07 taxonomy redesign (jodapp-api#1840) replaced 89 categories with 18. "Others" is deliberately the escape hatch: the growth rules say a new category is added when 3+ distinct employers ask for the same concept, or 5+ similar Others postings pile up. Before this feature we had no way to capture what the employer actually wanted — we could only guess from job titles filed under Others.
The category picker (jodapp-web#1311) shows all 18 options. This feature adds the missing capture: when the employer selects "Others", an optional input appears — "What kind of job is this?" — and what they type becomes a row in the inbox.
The key property: capture on blur, not on submit
The request row is saved when the input loses focus (fire-and-forget), not when the job is submitted. An employer who types "dog groomer", finds no fitting category, and abandons the form is the strongest possible signal that the taxonomy has a gap — waiting for a successful job submit would lose exactly those cases. This is why the snapshot column stores unsubmitted form values.
Rules: one request per form session; re-fire only if the text changed since the last successful send. Failures are silent — this must never block or delay posting a job.
Schema
Designed DBML-first in jodapp-api/docs/db/jodapp.dbml, then one migration. Full column table in the model doc: Taxonomy::CategoryRequest.
Deliberately no status column. This is an append-only inbox, and our soft-delete convention says append-only tables carry no status. There is no admin UI in v1 — the weekly Others review reads new rows via Metabase or the console, and the outcome of a request lives in the taxonomy itself (a promoted or widened category), not as per-row workflow state. No lifecycle to maintain.
API
POST /employers/taxonomy/category_requests — employers actor, existing org-membership auth. Standard chain, all per AGENTS.md in jodapp-api:
- Model
Taxonomy::CategoryRequest— no enums, no model validations, explicitclass_name/foreign_keyon thecreated_byassociation. - Request
Taxonomy::CategoryRequests::EmployersCreateRequest— permits:nameand the free-formcareers_job_snapshothash. - Validator
EmployersCreateValidator—namerequired and at most 100 characters;careers_job_snapshotmust be a JSON object when present, with its serialized size capped (~20 KB) so a client cannot store unbounded blobs. Plain-English messages. - Manager
EmployersCreateManager— validate, then a singlesave!. No Service class and no wrapping transaction — one write is already atomic (conventions: do not wrap a single write). - Serializer — minimal (
id,uuid,name,created_at), response201 :created. - Route — explicit verb + path in
config/routes/employers_routes.rb, neverresources.
Frontend (jodapp-web)
- The input renders only while "Others" is selected, directly under the picker. Stock Bootstrap only — no custom CSS.
- Optional field: no validation error when left empty; it never gates submitting the job.
- On blur with non-empty text:
POSTwith{ name: text, careers_job_snapshot: getValues() }. Fire-and-forget; errors are silent (no toast, no blocking). - Selecting a different category hides the input; the job form behavior is otherwise unchanged.
Weekly review workflow (process, not code)
The Others review owner reads the week's rows: name says what was asked for, careers_job_snapshot shows the job behind it, created_by (joined to company) counts distinct employers. When a concept hits the promote bar, the taxonomy gains a category (add-only, soft cap 20 / hard cap 25) — the inbox rows themselves are never edited.
Out of scope
- Any admin UI for the inbox (v1 is Metabase/console).
- Notifications or auto-categorization from requests.
- The category filter on the marketplace (separate roadmap item).
Refs
- Model doc: Taxonomy::CategoryRequest
- Initiative: 2026-07 Taxonomy Categories
- Anchor issue: jodapp-api#1840 · Picker: jodapp-web#1311