CodamAIDocs
Topicdone

Relation types and recursive flags

The four relation types and the permissions CREATE, UPDATE and DELETE, which define what CDMS may do across a relation.

Variants
ONETOONEONETOMANYMANYTOONEMANYTOMANYFlag CREATEFlag UPDATEFlag DELETEReading: role instead of flagRoles for children

What this is about

A relation connects two models: an employee belongs to a company, a company has many employees. In CDMS, a relation is always a pair of fields: one field on each side, and they point at each other. In the hub, you create one side. The other side is created automatically.

On each side you define two things:

  1. the relation type: How many objects are on each side?
  2. the recursive flags: What may CDMS do with the connected objects across this relation?

The four relation types

flowchart LR
    subgraph one ["1:1 ONETOONE"]
      P[Person] --- T[Main phone]
    end
    subgraph many ["1:n ONETOMANY / n:1 MANYTOONE"]
      C[Company] -->|employees| E1[Employee]
      C --> E2[Employee]
      E1 -.->|company| C
    end
    subgraph mm ["n:m MANYTOMANY"]
      G1[Group A] --- R1[Role 1]
      G1 --- R2[Role 2]
      G2[Group B] --- R2
    end
TypeHubmeaningother sidein JSON
ONETOONE1:1exactly one object on each sideONETOONEone object: "mainPhone": { "id": … }
ONETOMANY1:nthis object has manyMANYTOONEa list: "employees": [ … ]
MANYTOONEn:1many of these belong to oneONETOMANYone object: "company": { "id": … }
MANYTOMANYn:mmany on both sidesMANYTOMANYa list: "roles": [ … ]

In the database, for 1:n and n:1 there is a foreign key in the table of the n side. For 1:1 it is in one of the two tables. For n:m it is in a separate join table. The generator decides which table that is. For the client it does not matter: it writes and reads the relation the same way from both sides. See Both sides of a relation and Many-to-many through a join table.

What this looks like in the model

# Company – the "one" side
- name: "employees"
  relationship: "ONETOMANY"
  reference:
    id: "m-employee"      # target model
    name: "company"       # field on the other side
  recursive:
    create: true
    update: true
    delete: true

# Employee – the "many" side
- name: "company"
  relationship: "MANYTOONE"
  reference:
    id: "m-company"
    name: "employees"

The flags belong to one side. Here the company may create, update and delete its employees. The other way round, an employee may only link its company, because company has no flags. In the hub, you set the flags in the Recursion tab of the relation field. If you set nothing, all three are off.

The three flags

What the flags allow
CREATEUPDATEDELETE
applies toa child without id in the requesta child with id and other fieldsa child that drops out of the relation, and all children when the parent object is deleted
without the flag400 recursive-create-not-allowed|<feld>the child is only linked, its fields stay untouchedthe child is only detached and keeps existing
role that is checkedcreate role of the child modelupdate role of the child modeldelete role of the child model
The flags in action

When: You create a company with a new employee.

  1. 1
    Client→CDMS
    sends "employees": [{ "firstname": "Anna", "lastname": "Schmidt" }], so a child without id
  2. 2
    CDMS
    Flag CREATE on employees?
  3. 3
    CDMS→Client
    no → 400 recursive-create-not-allowed|employees
  4. 4
    CDMS→Database
    yes → creates the employee and connects it to the company

Result: The employee gets its own id, its own default values and its own _createdOn.

When: You change the name of an existing employee through the company.

  1. 1
    Client→CDMS
    sends "employees": [{ "id": "k1…", "firstname": "Anna", "lastname": "Meyer" }]
  2. 2
    CDMS
    Flag UPDATE on employees?
  3. 3
    CDMS
    no → only links k1…, lastname stays as it was
  4. 4
    CDMS→Database
    yes → updates the employee too, following the rules of the verb (PUT replaces, PATCH changes)

Result: See The four cases in nested writing.

When: An employee drops out of the list, or the company is deleted.

  1. 1
    Client→CDMS
    sends the list employees without Ben, or DELETE /company/delete/{id}
  2. 2
    CDMS
    Flag DELETE on employees?
  3. 3
    CDMS→Database
    no → Ben keeps existing, his field company becomes empty
  4. 4
    CDMS→Database
    yes → Ben is deleted, with his DELETE hooks and his own dependent children

Result: A child with the DELETE flag is called a dependent child: it only lives together with its parent object. See Lists as target state and Dependent objects (cascades).

And reading?

There is no flag for reading. Whether you may read a relation depends on the read role of the target model, or on a field role on the relation field. Which connected objects appear in the response is defined by your response. See Expanding references and lists.

Which roles apply to a child

Every child that CDMS creates, updates or deletes across a relation is checked with the roles of its own model. So if you create employees through the company, you also need the right to create employees.

Instead of the role of the child model, a field role on the relation field is also enough, for example company-employees-create. It allows the action only on this path, through the company. See Permissions on relations (field roles).

May the child be written?
Action on the childRole of the child modelField role on the relation fieldResult
only link (POST, PUT) or detach––allowed, no role of the child needed
create, update, deleteyes–allowed
create, update, deletenoyesallowed, only on this path
create, update, deletenono403 missing-permission|<rolle>, the whole request fails

What the flags change in the payload

The flags also decide which fields the client can send for a child at all. The generator builds the payload classes accordingly:

Flag on the relationChild in POST /createChild in PUT /update
noneonly id and @typeonly id and @type
CREATEall fields of the childonly id and @type
UPDATEonly id and @typeall fields of the child
CREATE and UPDATEall fieldsall fields

If you send more fields on a relation without the matching flag, they are dropped when the request is read. PATCH reads data without a fixed class. There the rules from the four cases apply. So the OpenAPI description shows you exactly what is possible on which relation.

Pitfalls

What comes next

Sources in the code and the knowledge base
  • CDMS/cdms-generator – CdmsYamlLoader (relationship, reference, recursive, normalizeRelType, applyRecursionType), EntityProcessor (JPA annotations, cascade, JoinTable), RestPayloadProcessor (CreatePayload/UpdatePayload vs. IdWrapperPayload)
  • CDMS/cdms-system-layer – AbstractLayer (setModel, recursiveCreate/Update/Patch, detachOrDeleteMember, recursiveDelete, enterField)
  • commons – MetaFieldInfo (isRecursiveCreate/Update/Delete)
  • CDMS/frontend – app/components/cdms/FieldDialog.vue (Rekursion tab)
  • hub-backend – ModelDesignService (counterpart field)
  • CDMS/cdms-integrationtest – structure/models.yaml (Company, Employee, Person, Group), AbstractRecursiveTest, AbstractRecursivePatch, AbstractUpdateTest, AbstractRoleDenialTest, AbstractFieldRoleTest
Search