Ir al contenido

Relaciones

Las relaciones definen cómo las colecciones están conectadas a nivel de base de datos. Permiten a Rebase:

  • Renderizar campos selectores de relación en formularios de entidad
  • Resolver entidades relacionadas al mostrar previsualizaciones
  • Generar restricciones de clave foránea en el esquema Drizzle
  • Soportar comportamientos de eliminación/actualización en cascada

Las relaciones se pueden definir directamente dentro de la propiedad, o explícitamente en el array relations de una colección:

Puedes definir la relación directamente en la propiedad. El framework las extrae automáticamente al array relations[] de la colección en el momento de la normalización, por lo que ya no necesitas una entrada relations[] separada para las propiedades.

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

Para casos de uso avanzados o cuando una relación no se mapea directamente a un campo de formulario, puedes definirla en el 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 clave foránea en esta tabla apunta a la clave primaria de otra tabla.

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

Esto crea: posts.author_id → users.id

La clave foránea está en la tabla objetivo, apuntando de vuelta a esta entidad.

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

Dos colecciones conectadas a través de una tabla de unión 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
}
}
]

Esto crea:

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

Para renderizar un campo de relación en un formulario, añade una propiedad con type: "relation":

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

Campo de relación en formulario

Al renderizar una previsualización (como en una celda de tabla o un chip de referencia), Rebase maneja la hidratación automáticamente:

Previsualización de relación en tabla

Para relaciones complejas que atraviesan múltiples tablas, 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"]
}
}
]

Controla qué sucede cuando las entidades relacionadas son actualizadas o eliminadas:

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
}
]
AcciónComportamiento
"cascade"Propagar el cambio a las filas relacionadas
"restrict"Prevenir la operación si existen filas relacionadas
"no action"Igual que restrict (posponer a la verificación de restricción)
"set null"Establecer la columna FK a NULL
"set default"Establecer la columna FK a su valor predeterminado

Al consultar datos a través del SDK del Cliente Rebase, las relaciones no se incluyen por defecto. Usa el método include() para solicitar entidades relacionadas junto con los datos primarios.

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

Cuando se incluye, la respuesta contiene tanto la clave foránea escalar como el objeto de relación hidratado:

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

Los nombres de relación pasados a include() deben coincidir con el relationName definido en el array relations de la colección.

Para la referencia completa del constructor de consultas (filtrado, ordenación, paginación, en tiempo real), consulta la documentación del SDK del Cliente.

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