Skip to main content

ModelColumns

Derive Macro ModelColumns 

Source
#[derive(ModelColumns)]
{
    // Attributes available to this derive:
    #[columns]
}
Expand description

Derive a model’s ModelColumns: the table a resource lists it in and the detail page it shows it on, when the resource declares neither.

#[derive(Debug, Clone, toasty::Model, tablo_core::ModelColumns)]
pub struct User {
    #[key]
    #[auto]
    pub id: uuid::Uuid,
    pub name: String,
    pub role: Role,
    pub active: bool,
    #[columns(skip)]
    pub password_hash: String,
    #[default(jiff::Timestamp::now())]
    pub created_at: jiff::Timestamp,
}

// Lists `name`, `role`, `active` and `created_at`, and shows them on the detail page.
let table = <User as tablo_core::ModelColumns>::table();

Each field shows whoever writes it, the person through a form or Toasty through a #[default] or an #[update], in a column its type picks:

No column shows a key Toasty generates, a #[version] field, a relation or its foreign key, which only a declared RelationColumn shows, by the related record’s title, or a TenantId or Option<TenantId> field, which belongs to the resource’s Tenancy. An #[auto] created_at or updated_at shows like any timestamp. A #[version] field is what ModelColumns::version returns, so the edit form refuses a save another save overtook.

Every other field shows, a secret such as a password hash or an API token included, to anyone the policy lets view the list: mark it #[columns(skip)], which leaves it out of the table, its search and export, and the detail page.

§Per-field attributes

The other #[columns(..)] keys shape the column the field’s type picks, in the table and on the detail page, through the column’s own builder:

  • #[columns(label = "Amount due")] — the column’s label, and its filter’s (TextColumn::label);
  • #[columns(badge)] — a text column’s value as a badge (TextColumn::badge);
  • #[columns(money = EUR)] — a number as an amount of a Currency: a bare three-letter uppercase code names one of its constants, and any other expression is the currency itself, such as money = SHOP_CURRENCY for the app’s own constant or money = Currency::new("CHF") (TextColumn::money);
  • #[columns(hidden_by_default)] — a table column each reader may show, hidden at first (TextColumn::hidden_by_default);
  • #[columns(filter)] — a filter on the table: a TernaryFilter for a bool, a DateFilter over a range of days for a date or a timestamp, optional or not, a SelectFilter for an Options enum. An Option<bool> or an optional enum takes no built-in filter, so filter on one fails to compile: declare its filter on the resource’s table.
#[derive(Debug, Clone, toasty::Model, tablo_core::ModelColumns)]
pub struct Invoice {
    #[key]
    #[auto]
    pub id: uuid::Uuid,
    #[columns(badge, filter)]
    pub status: Status,
    #[columns(label = "Amount due", money = EUR)]
    pub total_cents: i64,
    #[columns(hidden_by_default, filter)]
    pub issued_at: jiff::Timestamp,
}

A key the field’s column does not fit fails to compile at the key, such as badge on a bool or money on a String, as does filter on a type no built-in filter matches, label on a field no column shows, and hidden_by_default on one the table leaves out. So do an unknown or repeated key, skip beside another key, any key on a field the derive already leaves out, and a generic model.

The derive reads the model, not a form: a file path shows as text and a String an #[form(options = ..)] key labels shows its stored value. Declare a FileColumn or a formatted TextColumn for them.

A resource replaces either default with ResourceDef::table or ResourceDef::detail, and extends one by starting from it: User::table().filters(..), with ModelColumns in scope, which adds its filters after the derived ones; mounting refuses two filters on one field. A declared table is also the detail page’s default: a resource that declares a table and no detail page shows the table’s columns there. Derive a model’s default table and detail page; documented on the tablo-core re-export.