Duplicate a record
Some records are written again and again with two or three values changed. A club runs the same annual meeting every year. A school opens the same course every term. Without a copy action, somebody retypes fifteen fields.
duplicate is that copy action. It opens the page’s create route with the values of the row
on screen, and the person changes what is different and saves.
Offer the copy link
Section titled “Offer the copy link”The page needs a new_route:, because the copy opens it. Add duplicate to the form’s
actions:.
page event "Event" { route: /events/{event} new_route: /events/new access: board
form of events { fields: title, starts_at, ends_at, place, capacity, description actions: save, delete, duplicate }}The form now shows a Duplicate link beside the save button. The link is a normal link to a normal page: it writes nothing, and the new record exists only after somebody saves the form it opens.
The action is opt-in. A form that writes no actions: gets save and delete, and no copy
link.
Understand what a copy carries
Section titled “Understand what a copy carries”A copy fills in the controls the form has, and nothing else. So these values never travel, and you do not have to say so:
| Not copied | Reason |
|---|---|
the address (slug) and any unguessable |
the engine mints them for the new record |
| the audit columns | the engine writes who created the record, and when |
| the state of a lifecycle | a copy of a published article starts as a draft |
a column filled by set |
the form fills it in on every write |
| a computed column or a count | it is derived from the row, not stored |
a sensitive column this role may not read |
the form has no control for it either |
a readonly: field |
the save would drop the value, so showing it would mislead |
| a picture | a browser cannot fill in a file input |
Three kinds of value are carried and are worth knowing about:
- a
multicomes back with the same boxes ticked; - a
refkeeps the row it pointed at; - a column that is unique on its own is left empty, because a copied value can only
collide. A column that is unique only in combination —
unique(course, pupil)— is carried, because changing the pupil is what a copy is for.
Clear more fields
Section titled “Clear more fields”The three dates of an event change every year. Name them in a duplicate block, and the copy
leaves them empty.
form of events { fields: title, starts_at, ends_at, place, capacity, description actions: save, delete, duplicate
duplicate { clear: starts_at, ends_at, signup_deadline label: "Copy as template" }}clear: only removes. There is no list that adds a column back, so a form can carry less than
the default and never more. Naming a column the copy would not have carried is a compile
error, with the reason in the message.
label: is the link’s text. Without it the link reads Duplicate.
Know who can copy what
Section titled “Know who can copy what”The copy reads one record, so it is an ordinary read and follows the same rules:
- a record the reader may not see answers 404, exactly as its own page does;
- a
where:on the page or on the region applies, because the copy runs the same query; - in an organization with an application-wide scope, a record outside the current scope is not found.
The save that follows is an ordinary create. It runs every rule the form already has: required
values, check, unique(...), deny when, and any before insert hook.
Where the action is refused
Section titled “Where the action is refused”The compiler refuses duplicate where the page cannot serve it, and names the repair:
- the page has no
new_route:, so there is no create form to open; - the page’s route carries scope, such as
/courses/{course}/marks/{mark}. A create route carries no scope, so the copy would leave it; - the page is about no single record;
- the form writes a different table from the one the page is about.