Skip to content

Forms

New and edit forms are built from declared fields. Each field maps by :as to an input type:

:as Input
string text input
text textarea
select dropdown (from :collection)
boolean checkbox
date date picker
time time picker
datetime datetime-local picker
number number input
password password input
hidden hidden input
file file input
1
2
3
4
5
field('title',     :as<string>, :placeholder<Headline>, :hint('Keep it short.'));
field('body',      :as<text>);
field('published', :as<boolean>);
field('author-id', :as<select>, :collection({ Author.all.all.map(-> $a { $a.id => $a.name }) }));
field('tag-ids',   :as<select>, :multiple, :collection({ Tag.all.all.map(-> $t { $t.id => $t.name }) }));

Field labels resolve through I18n (human-attribute-name), or pass :label to set one explicitly for a single field. A :hint and :placeholder render alongside the input. A select renders a dropdown from its explicit collection, and with :multiple it renders a multi-select for a collection association.

Persistence

Create and update assign only the attributes named in the resource's permit allowlist, so an unlisted parameter is never mass-assigned. On success the action redirects to the show page with a flash; on a validation failure it re-renders the form with the submitted values.

1
permit(<title body published author-id>);

Because an HTML form cannot issue a PATCH, the engine also accepts a plain POST to the member path for the update action, so editing works without JavaScript.

Validation errors

A failed save re-renders the form. ORM validation errors show inline beneath each field and in a summary at the top, using the ORM's full-messages.

Nested attributes

Declare a nested association with nested, giving its own sub-fields. The model must back it with accepts-nested-attributes-for.

1
2
3
nested('posts', :multiple, {
  field('title', :as<string>);
});

A singular association renders one nested fieldset; a :multiple (has-many) association renders one fieldset per existing child plus a blank row for adding one. Submitting posts posts-attributes to the model, which creates new children, updates those with an id, and (with allow-destroy) removes those marked for deletion. Use the model's reject-if to skip a blank add-row.

File uploads

A file field stores its upload through Active Storage. The form becomes multipart/form-data, and on save the upload is attached to the record by its type, id, and the field name (no storage role on the model required):

1
field('cover', :as<file>);

Uploading a new file on update replaces the field's previous attachment, deleting the old blob record and its stored bytes. Saving without choosing a file leaves the existing attachment in place. Deleting a record purges its file attachments, so no blob record or stored file is left orphaned.

Configure a storage service and repository in your application's setup, the same as any Active Storage integration.