Skip to content

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.

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.

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 multi comes back with the same boxes ticked;
  • a ref keeps 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.

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.

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.

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.