Skip to main content

RecordForm

Derive Macro RecordForm 

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

Derive RecordForm for the typed value a resource’s form writes.

One field per model column the form writes, named and typed like the model’s field. A scalar (String, a TypedValue type, an Options enum, or an Option of one) binds the key its control posts; a #[form(embed)] field binds every key of an EmbeddedForm value and is written whole.

#[derive(tablo_core::RecordForm)]
#[form(model = User)]
pub struct UserForm {
    pub name: String,
    #[form(blank = "member")]
    pub role: String,
    #[form(blank = 0)]
    pub age: i64,
}

The derive also emits UserFormField, one variant per field, which Posted and Resource::validate_record’s FieldErrors key on and RecordForm::fields answers with each variant’s keys. It emits UserFormControls, one control per field chosen from the field — a bool is a toggle, #[form(options = T)] a choice over T’s options, #[form(options)] a choice over the field type’s options, #[form(relationship = R)] a choice over R’s records (a multiple choice over a Vec of their keys), #[form(file)] a file field, #[form(embed)] the embedded value’s schema, #[form(repeat)] a repeater, and any other field a text field — with controls() handing them over, typed by the form so only a Schema<UserForm> places them, and RecordForm::control answering one field’s. RecordForm::table lists a sortable column per text field, searchable over a String or Option<String>, an options field by its option’s label, and a toggle as yes or no. RecordForm::detail shows the same columns, plus a relationship’s key, a many-to-many field’s records by their titles, a file field as a link, an embedded value leaf by leaf and a repeater’s items. A resource’s ResourceDef defaults its form, table and detail page to them; ResourceDef::form, ResourceDef::table and ResourceDef::view arrange or extend them instead, and a form renders the controls it does not place after the ones it does.

§Attributes

  • #[form(model = User)] on the struct: the model the form writes.
  • #[form(blank = <expr>)] on a scalar: the value an empty submission reads as.
  • #[form(optional)] on a String: an empty submission reads as "".
  • #[form(options)] on an Options enum or an Option of one: a choice over its options. Without it, the field is a text field.
  • #[form(options = Status)]: a choice over Status::options().
  • #[form(relationship = AuthorResource)] on a foreign key: a choice over the source’s records, each labelled by its record_title. The source is any OptionSource, which every Resource is.
  • #[form(relationship = TagResource)] on a Vec of the source’s keys, named like the model’s #[has_many(via = joins.target)] field: a many-to-many field, a multiple choice whose records the write links through the join model, one row each. No record is its blank answer; an edit that does not post it keeps the record’s links.
  • #[form(file)] on a String: a file field.
  • #[form(embed)] on an EmbeddedForm value.
  • #[form(repeat)] on a #[document] list of RepeaterItem values: a repeater, a row per item, which the browser adds, removes and moves. No rows is its blank answer.

A scalar’s blank answer is its blank, "" for an optional String, None for an Option, or false for a bool. A field with none is required: the panel renders its control required and the parse refuses an empty submission. No control declares presence.

A generic struct, a tuple struct, an empty struct, a Deferred<_> field, blank or optional on an Option, an embedded value or a many-to-many field, optional on a type other than String, and an unknown key are compile errors. So are a field the model lacks, a type the model’s field does not have, a scalar that is not a FormScalar, and a many-to-many field whose keys are not its source’s primary key. Derive RecordForm for the typed value a resource’s form writes; documented on the tablo-core re-export.