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à.

import { defineCollection } from "@rebasepro/admin-types";
const postsCollection = defineCollection({
slug: "posts",
name: "Posts",
table: "posts",
properties: {
title: { type: "string", name: "Title" },
content: { type: "string", name: "Content", admin: { multiline: true } },
author: {
type: "relation",
name: "Author",
relation: {
kind: "belongsTo",
target: () => usersCollection,
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:

import { defineCollection } from "@rebasepro/admin-types";
const postsCollection = defineCollection({
slug: "posts",
name: "Posts",
table: "posts",
properties: {
title: { type: "string", name: "Title" },
content: { type: "string", name: "Content", admin: { multiline: true } },
author: { type: "relation", name: "Author", relationName: "author" }
},
relations: [
{
kind: "belongsTo",
relationName: "author",
target: () => usersCollection,
localKey: "author_id"
}
]
});

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

relations: [
{
kind: "belongsTo", // The FK is on THIS table
relationName: "author",
target: () => usersCollection,
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: [
{
kind: "hasMany", // The FK is on the TARGET table
relationName: "posts",
target: () => postsCollection,
foreignKeyOnTarget: "author_id" // Column on the posts table
}
]

Due collezioni connesse tramite una tabella di giunzione intermedia.

// On the Users collection:
relations: [
{
kind: "manyToMany",
relationName: "roles",
target: () => rolesCollection,
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: [
{
kind: "via",
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: [
{
kind: "belongsTo",
relationName: "author",
target: () => usersCollection,
localKey: "author_id",
onDelete: "cascade", // Delete posts when user is deleted
onUpdate: "cascade" // Update FK when user ID changes
}
]
Azione Comportamento
"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.

type Relation =
| BelongsToRelation
| HasOneRelation
| HasManyRelation
| ManyToManyRelation
| ViaRelation;
// Every kind carries these:
interface RelationBase {
relationName?: string;
target: () => CollectionConfig;
inverseRelationName?: string;
onUpdate?: "cascade" | "restrict" | "no action" | "set null" | "set default";
onDelete?: "cascade" | "restrict" | "no action" | "set null" | "set default";
overrides?: Partial<CollectionConfig>;
validation?: { required?: boolean };
}
// ...and only the fields its own kind uses:
interface BelongsToRelation extends RelationBase {
kind: "belongsTo";
localKey?: string; // column on THIS table
}
interface HasOneRelation extends RelationBase {
kind: "hasOne";
foreignKeyOnTarget?: string; // column on the TARGET table
}
interface HasManyRelation extends RelationBase {
kind: "hasMany";
foreignKeyOnTarget?: string; // column on the TARGET table
}
interface ManyToManyRelation extends RelationBase {
kind: "manyToMany";
through?: {
table?: string;
sourceColumn?: string; // FK naming THIS collection
targetColumn?: string;
};
}
interface ViaRelation extends RelationBase {
kind: "via";
cardinality: "one" | "many"; // a join chain cannot imply it
joinPath: JoinStep[]; // read-only
}