What this is about
DELETE is not the only thing that deletes. A PUT or PATCH on the parent can delete children too, namely when a dependent child (relation with the DELETE flag) no longer belongs to it afterwards. This happens when
- a list is sent without the child,
- a list counts as empty,
- a single reference is cleared.
This is intended: an invoice item that drops out of its invoice makes no sense without the invoice. But it is also the most common way to delete data by accident.
Before and after
A contact with a main phone mainPhone and further phones phones. Both relations have the DELETE flag.
| Field | before | request | after |
|---|---|---|---|
phones | P2, P3 | "phones": [{ "id": "P2…" }] | P2; P3 deleted |
phones | P2, P3 | "phones": [] | empty; P2 and P3 deleted |
mainPhone | P1 | "mainPhone": null | empty; P1 deleted |
phones | P2, P3 | PUT without phones | empty; P2 and P3 deleted |
phones | P2, P3 | PATCH without phones | P2, P3, unchanged |
The last two rows are the difference between PUT and PATCH: with PUT, “missing” means the same as “empty”; with PATCH, “missing” means “do not touch”.
PATCH /api/rest/crm/contact/update/c052…
{
"data": {
"id": "c052…",
"phones": [ { "id": "P2…" } ]
},
"response": ["id", { "field": "phones", "response": ["id", "number"] }]
}{ "data": { "id": "c052…",
"phones": [ { "id": "P2…", "number": "0611-17277000" } ] },
"meta": { "error": false } }Afterwards P3 has not only disappeared from the list, it is deleted. /phone/read/P3… returns 404.
When a child is deleted
| Verb | Field in the request | Child contained in it | Flag DELETE | Result for the child |
|---|---|---|---|---|
| – | list or reference | yes | – | stays connected |
| – | list without the child, [] or null | no | yes | deleted |
| – | list without the child, [] or null | no | no | unlinked, stays |
| PUT | missing | – | yes | deleted (missing = empty) |
| PUT | missing | – | no | unlinked (missing = empty) |
| PATCH | missing | – | – | unchanged |
| – | single reference null | – | yes | deleted |
“Contained” means: the child is in the list or the reference with its id. Which other fields you send along makes no difference for this. See Lists as target state.
What happens
-
1Client→CDMSsends
PATCH /contact/update/c052…withphones: [P2] -
2CDMSchecks visibility and update role of the contact
-
3CDMScompares the list with the stored state: P3 is missing
-
4CDMS
phoneshas DELETE → P3 is deletedWithout the DELETE flag, P3 would only be unlinked: its reference to the contact becomes empty, P3 stays. -
5CDMSchecks the delete role of the phone model (or a field role on
phones) -
6CDMS→Clientrole missing → 403, nothing is saved, not even the other changes
-
7HookDELETE hooks of P3 run, together with the UPDATE or PATCH hooks of the contact
-
8CDMS→Databasedeletes P3 with its own dependent children and files, saves the contact, reads back
Everything from Dependent objects (cascades) applies to the deleted child: it needs the delete role of its model, its own dependent children go along, its other relations are unlinked.
Pitfalls
What comes next
- The rules for lists: Lists as target state
- PUT and PATCH compared: PUT or PATCH? The null trap
- What is deleted along: Dependent objects (cascades)