Le relazioni collegano due oggetti tra loro. In Twenty, le relazioni sono sempre bidirezionali — ogni relazione ha due lati e ciascun lato è dichiarato come un campo che fa riferimento all’altro.
| Tipo di relazione | Descrizione | Ha una chiave esterna? |
|---|
MANY_TO_ONE | Molti record di questo oggetto puntano a un record della destinazione | Sì (joinColumnName) |
ONE_TO_MANY | Un record di questo oggetto ha molti record della destinazione | No (lato inverso) |
Come funzionano le relazioni
Ogni relazione richiede due campi che fanno riferimento l’uno all’altro:
- Il lato MANY_TO_ONE — risiede sull’oggetto che detiene la chiave esterna.
- Il lato ONE_TO_MANY — risiede sull’oggetto che possiede la collezione.
Entrambi i campi usano FieldType.RELATION e si riferiscono reciprocamente tramite relationTargetFieldMetadataUniversalIdentifier.
Esempio: Post Card ha molti destinatari
Un PostCard può essere inviato a molti record PostCardRecipient. Ogni destinatario appartiene esattamente a una sola cartolina.
Passaggio 1: definisci il lato ONE_TO_MANY su PostCard (il lato “uno”):
src/fields/post-card-recipients-on-post-card.field.ts
import { defineField, FieldType, RelationType } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
// Export so the other side can reference it
export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111';
// Import from the other side
import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field';
export default defineField({
universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCardRecipients',
label: 'Post Card Recipients',
icon: 'IconUsers',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
});
Passaggio 2: definisci il lato MANY_TO_ONE su PostCardRecipient (il lato “molti” — contiene la chiave esterna):
src/fields/post-card-on-post-card-recipient.field.ts
import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
// Export so the other side can reference it
export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222';
// Import from the other side
import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field';
export default defineField({
universalIdentifier: POST_CARD_FIELD_ID,
objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCard',
label: 'Post Card',
icon: 'IconMail',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.CASCADE,
joinColumnName: 'postCardId',
},
});
Importazioni circolari: entrambi i campi di relazione fanno riferimento all’universalIdentifier dell’altro. Per evitare problemi di importazioni circolari, esporta gli ID dei campi come costanti denominate da ciascun file e importale nell’altro. Il sistema di build le risolve in fase di compilazione.
Relazioni con gli oggetti standard
Per creare una relazione con un oggetto Twenty integrato (Person, Company, ecc.), usa STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS:
src/fields/person-on-self-hosting-user.field.ts
import {
defineField,
FieldType,
RelationType,
OnDeleteAction,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object';
export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333';
export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444';
export default defineField({
universalIdentifier: PERSON_FIELD_ID,
objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'person',
label: 'Person',
description: 'Person matching with the self hosting user',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.SET_NULL,
joinColumnName: 'personId',
},
});
Proprietà dei campi di relazione
| Proprietà | Obbligatorio | Descrizione |
|---|
type | Sì | Deve essere FieldType.RELATION |
relationTargetObjectMetadataUniversalIdentifier | Sì | L’universalIdentifier dell’oggetto di destinazione |
relationTargetFieldMetadataUniversalIdentifier | Sì | L’universalIdentifier del campo corrispondente sull’oggetto di destinazione |
universalSettings.relationType | Sì | RelationType.MANY_TO_ONE o RelationType.ONE_TO_MANY |
universalSettings.onDelete | Solo MANY_TO_ONE | Cosa accade quando il record referenziato viene eliminato: CASCADE, SET_NULL, RESTRICT o NO_ACTION |
universalSettings.joinColumnName | Solo MANY_TO_ONE | Nome della colonna del database per la chiave esterna (ad es., postCardId) |
Campi di relazione inline
Puoi anche dichiarare una relazione direttamente all’interno di defineObject. Quando è inline, ometti objectUniversalIdentifier — viene ereditato dall’oggetto padre:
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCardRecipient',
// ...
fields: [
{
universalIdentifier: POST_CARD_FIELD_ID,
type: FieldType.RELATION,
name: 'postCard',
label: 'Post Card',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.CASCADE,
joinColumnName: 'postCardId',
},
},
// … other fields
],
});