lead_time_days, an integer) to products and wires it through every surface the dashboard has:
- edited on the product form, saved by the form’s own Save button
- a column in the products table
- a filter (“lead time > 7 days”) and a sort (“slowest first”) in the list toolbar
First: should this be a custom field instead?
Default to a custom field definition. It needs zero code — declare the field, and the product form renders and saves it. That covers most plugin data and everything merchants define themselves. Graduate to a real database column only when the attribute needs list operations or database guarantees:
Lead time is a column because its whole point is the query: “show me everything that ships slow, slowest first.”
1. Backend
Four small pieces — each one unlocks a specific frontend capability. In a host app these live inbackend/ (or api/); in a distributed plugin, in the Rails engine half.
The column (with an index — you’re adding it because it will be queried):
validates :lead_time_days, numericality: { greater_than_or_equal_to: 0, allow_nil: true }) — it’s the authoritative validation, and the dashboard renders its 422 message inline on the field automatically.
2. Frontend — one declaration
<form> of its own, no save button:
3. Verify it
API first — the fastest way to confirm the backend pieces:How the pieces map
Two
ColumnDef extras worth knowing: ransackAttribute points the predicate somewhere other than the column key (the built-in sku column filters through master_sku), and displayable: false makes a filter-only field that never renders as a column.
Reference
- Custom form field recipe — the form layer in isolation, and the custom-field-definition path
- Tables — the full
ColumnDefand table registry API - Backend integration — serializers, permitted params, error mapping
- Decorators — the
prependpattern used for the controller

