feat(12.1-02): password and form-only fields, rules per operation and preset
- pact.FormVirtualFields lists form fields that are not model columns: never bound, filled or projected; their values reach the Form hooks through cabana.VirtualFieldsFromContext when the field's context allows the operation - type: password is a masked field that must be listed as virtual - pact.FormRules supplies the rule set per operation and replaces the model's Rules() for admin saves; a rule on a virtual field sees the submitted value - preset on a text field follows another text field on the create form - SPA: PasswordField, preset handling in FormView, empty password left out of an update - README, docs, OpenAPI document, TS types and dist updated
This commit is contained in:
@@ -221,6 +221,47 @@ A hook or scope that has to read the database during a write should use the tran
|
||||
|
||||
Scope reads and writes with `pact.ListExtendQuery` and `pact.FormExtendQuery` rather than checking in a hook: the scope then applies to every route, including relation and action routes.
|
||||
|
||||
### Form-only values and rules
|
||||
|
||||
A form may collect values that are not columns of the record (see [Form-only fields](forms.md#form-only-fields)). The Form hooks read what the administrator submitted with `cabana.VirtualFieldsFromContext(ctx)`. The map holds only the fields that were sent and that the field's `context` allows for this operation, so a missing key means "not submitted". Values are scalars as decoded from the request: a string, a bool, a `json.Number` or nil. The map is a copy, and the second result is false outside a create or update.
|
||||
|
||||
```go src=modules/cabana/example_form_seams_test.go#MembersController.FormBeforeCreate
|
||||
// FormBeforeCreate reads the submitted virtual values, which have passed the
|
||||
// rules by now, and stores what the model needs.
|
||||
func (MembersController) FormBeforeCreate(ctx context.Context, model any) error {
|
||||
values, _ := cabana.VirtualFieldsFromContext(ctx)
|
||||
member := model.(*Member)
|
||||
if plain, ok := values["password"].(string); ok && plain != "" {
|
||||
member.Password = hashPassword(plain)
|
||||
}
|
||||
if notify, _ := values["notify"].(bool); notify {
|
||||
// Queue the welcome message here.
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
A save validates the record against the model's `Rules()`. Those are often the rules of a public sign-up, which an admin form cannot meet: an update that changes only a name would have to repeat the password. A controller implementing `pact.FormRules` returns the rules of an admin save for `create` or `update`, and that set replaces the model's:
|
||||
|
||||
```go src=modules/cabana/example_form_seams_test.go#MembersController.FormRules
|
||||
// FormRules replaces the model's rules for admin saves: a create needs a
|
||||
// password, an update takes one only when the administrator types it.
|
||||
func (MembersController) FormRules(_ context.Context, op string) map[string]string {
|
||||
rules := map[string]string{"name": "required"}
|
||||
if op == "create" {
|
||||
rules["password"] = "required|between:8,255|confirmed"
|
||||
} else {
|
||||
rules["password"] = "nullable|between:8,255|confirmed"
|
||||
}
|
||||
return rules
|
||||
}
|
||||
```
|
||||
|
||||
- The form's `required` flags are still merged in, also for form-only fields.
|
||||
- A rule on a form-only field is checked against the submitted value, or against nothing when the field was not sent; the model column of the same name is never read. `confirmed` compares with the submitted `<field>_confirmation`.
|
||||
- Rule strings use the tokens `lagoon.Validate` supports. An unknown token is the opaque 500 on every save, so checks outside that set belong in a hook that returns a `cabana.ValidationError`.
|
||||
- The model must still have a `Rules` method; relation forms and settings forms keep using the model's rules.
|
||||
|
||||
## Refusing a write
|
||||
|
||||
A hook or an action stops a write by returning an error. Which error decides what the administrator sees:
|
||||
|
||||
Reference in New Issue
Block a user