Aller au contenu

Relations

Les relations définissent comment les collections sont connectées au niveau de la base de données. Elles permettent à Rebase de :

  • Rendre les champs de sélection de relation dans les formulaires d’entité
  • Résoudre les entités liées lors de l’affichage des aperçus
  • Générer les contraintes de clé étrangère dans le schéma Drizzle
  • Supporter les comportements de suppression/mise à jour en cascade

Les relations peuvent être définies soit en ligne dans la propriété, soit explicitement dans le tableau relations d’une collection :

Vous pouvez définir la relation directement sur la propriété. Le framework extrait automatiquement celles-ci dans le tableau relations[] de la collection au moment de la normalisation, de sorte que vous n’avez plus besoin d’une entrée relations[] distincte pour les propriétés.

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"
}
}
};

Pour les cas d’utilisation avancés ou lorsqu’une relation ne correspond pas directement à un champ de formulaire, vous pouvez la définir dans le tableau 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"
}
]
};

Une clé étrangère sur cette table pointe vers la clé primaire d’une autre table.

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
}
]

Ceci crée : posts.author_id → users.id

La clé étrangère se trouve sur la table cible, pointant vers cette 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
}
]

Deux collections connectées via une table de jonction intermédiaire.

// 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
}
}
]

Ceci crée :

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

Pour afficher un champ de relation dans un formulaire, ajoutez une propriété avec type: "relation" :

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

Champ de relation dans le formulaire

Lors de l’affichage d’un aperçu (comme dans une cellule de tableau ou une puce de référence), Rebase gère automatiquement l’hydratation :

Aperçu de la relation dans le tableau

Pour les relations complexes qui traversent plusieurs tables, utilisez 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"]
}
}
]

Contrôlez ce qui se passe lorsque les entités liées sont mises à jour ou supprimées :

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
}
]
ActionComportement
"cascade"Propager le changement aux lignes liées
"restrict"Empêcher l’opération si des lignes liées existent
"no action"Idem restrict (reporter à la vérification de contrainte)
"set null"Définir la colonne de clé étrangère à NULL
"set default"Définir la colonne de clé étrangère à sa valeur par défaut

Lors de l’interrogation de données via le SDK client Rebase, les relations ne sont pas incluses par défaut. Utilisez la méthode include() pour demander les entités liées en même temps que les données primaires.

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"]
});

Lorsqu’elles sont incluses, la réponse contient à la fois la clé étrangère scalaire et l’objet de relation hydraté :

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"
}

Les noms de relation passés à include() doivent correspondre au relationName défini dans le tableau relations de la collection.

Pour la référence complète du constructeur de requêtes (filtrage, tri, pagination, temps réel), consultez la documentation du 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 };
}