Salta ai contenuti

Relazioni

Le relazioni definiscono come le collezioni sono connesse a livello di database. Consentono a Rebase di:

  • Renderizzare campi di selezione relazione nei form delle entità
  • Risolvere entità correlate quando si mostrano le anteprime
  • Generare vincoli di chiave esterna nello schema Drizzle
  • Supportare comportamenti di eliminazione/aggiornamento a cascata

Le relazioni possono essere definite in linea all’interno della proprietà, o esplicitamente nell’array relations di una collezione:

Puoi definire la relazione direttamente sulla proprietà. Il framework le estrae automaticamente nell’array relations[] della collezione al momento della normalizzazione, quindi non hai più bisogno di una voce relations[] separata per le proprietà.

const postsCollection: EntityCollection = {
slug: "posts",
name: "Posts",
table: "posts",
properties: {
title: { type: "string", name: "Title" },
content: { type: "string", name: "Content", multiline: true },
author: {
type: "relation",
name: "Author",
target: () => usersCollection,
cardinality: "one",
direction: "owning",
localKey: "author_id"
}
}
};

Per casi d’uso avanzati o quando una relazione non mappa direttamente a un campo del form, puoi definirla nell’array relations:

const postsCollection: EntityCollection = {
slug: "posts",
name: "Posts",
table: "posts",
properties: {
title: { type: "string", name: "Title" },
content: { type: "string", name: "Content", multiline: true },
author: { type: "relation", name: "Author", relationName: "author" }
},
relations: [
{
relationName: "author",
target: () => usersCollection,
cardinality: "one",
localKey: "author_id"
}
]
};

Una chiave esterna su questa tabella punta alla chiave primaria di un’altra tabella.

relations: [
{
relationName: "author",
target: () => usersCollection,
cardinality: "one", // This entity has ONE author
direction: "owning", // The FK is on THIS table
localKey: "author_id" // Column on the posts table
}
]

Questo crea: posts.author_id → users.id

La chiave esterna si trova sulla tabella target, puntando a questa entità.

// On the Users collection:
relations: [
{
relationName: "posts",
target: () => postsCollection,
cardinality: "many", // This user has MANY posts
direction: "inverse", // The FK is on the TARGET table
foreignKeyOnTarget: "author_id" // Column on the posts table
}
]

Due collezioni connesse tramite una tabella di giunzione intermedia.

// On the Users collection:
relations: [
{
relationName: "roles",
target: () => rolesCollection,
cardinality: "many",
direction: "owning",
through: {
table: "user_roles", // Junction table name
sourceColumn: "user_id", // FK to this collection
targetColumn: "role_id" // FK to target collection
}
}
]

Questo crea:

CREATE TABLE user_roles (
user_id INTEGER REFERENCES users(id),
role_id INTEGER REFERENCES roles(id),
PRIMARY KEY (user_id, role_id)
);

Per renderizzare un campo di relazione in un form, aggiungi una proprietà con type: "relation":

properties: {
author: {
type: "relation",
name: "Author",
target: () => usersCollection, // Target collection
widget: "select" // "select" (dropdown) or "dialog" (full picker)
}
}

Campo relazione nel modulo

Quando si renderizza un’anteprima (come in una cella di tabella o un chip di riferimento), Rebase gestisce automaticamente l’idratazione:

Anteprima relazione nella tabella

Per relazioni complesse che attraversano più tabelle, usa joinPath:

// Users → Permissions through Roles
relations: [
{
relationName: "permissions",
target: () => permissionsCollection,
cardinality: "many",
joinPath: [
{
table: "user_roles",
on: { from: "id", to: "user_id" }
},
{
table: "roles",
on: { from: "role_id", to: "id" }
},
{
table: "role_permissions",
on: { from: "id", to: "role_id" }
},
{
table: "permissions",
on: { from: "permission_id", to: "id" }
}
]
}
]
joinPath: [
{
table: "customers",
on: {
from: ["company_code", "region_id"], // Multiple columns
to: ["code", "region_id"]
}
}
]

Controlla cosa succede quando le entità correlate vengono aggiornate o eliminate:

relations: [
{
relationName: "author",
target: () => usersCollection,
cardinality: "one",
localKey: "author_id",
onDelete: "cascade", // Delete posts when user is deleted
onUpdate: "cascade" // Update FK when user ID changes
}
]
AzioneComportamento
"cascade"Propaga la modifica alle righe correlate
"restrict"Impedisce l’operazione se esistono righe correlate
"no action"Uguale a restrict (rinvia al controllo del vincolo)
"set null"Imposta la colonna FK a NULL
"set default"Imposta la colonna FK al suo valore predefinito

Quando si interrogano i dati tramite l’SDK Rebase Client, le relazioni non sono incluse di default. Usa il metodo include() per richiedere le entità correlate insieme ai dati primari.

const { data } = await client.data.articles
.include("author", "categories")
.find();
const { data } = await client.data.articles
.include("*")
.find();
const { data } = await client.data.articles.find({
include: ["author", "categories"]
});

Quando incluse, la risposta contiene sia la chiave esterna scalare che l’oggetto relazione idratato:

const { data } = await client.data.articles
.include("author")
.find();
for (const article of data) {
// Scalar FK — always present
article.values.author_id; // "uuid-1234"
// Hydrated relation — only present when included
article.values.author?.name; // "Jane Doe"
}

I nomi delle relazioni passati a include() devono corrispondere al relationName definito nell’array relations della collezione.

Per la documentazione completa del query builder (filtro, ordinamento, paginazione, real-time), consulta la documentazione dell’SDK Client.

interface Relation {
relationName?: string;
target: () => EntityCollection;
cardinality: "one" | "many";
direction?: "owning" | "inverse";
inverseRelationName?: string;
localKey?: string;
foreignKeyOnTarget?: string;
through?: {
table: string;
sourceColumn: string;
targetColumn: string;
};
joinPath?: JoinStep[];
onUpdate?: "cascade" | "restrict" | "no action" | "set null" | "set default";
onDelete?: "cascade" | "restrict" | "no action" | "set null" | "set default";
overrides?: Partial<EntityCollection>;
validation?: { required?: boolean };
}