Pular para o conteúdo

Relações

Relações definem como as coleções são conectadas no nível do banco de dados. Elas permitem que o Rebase:

  • Renderizar campos de seleção de relação em formulários de entidade
  • Resolver entidades relacionadas ao exibir pré-visualizações
  • Gerar restrições de chave estrangeira no esquema Drizzle
  • Suportar comportamentos de exclusão/atualização em cascata

As relações podem ser definidas inline dentro da propriedade, ou explicitamente no array relations de uma coleção:

Você pode definir a relação diretamente na propriedade. O framework extrai automaticamente estas para o relations[] da coleção no momento da normalização, então você não precisa mais de uma entrada relations[] separada para as propriedades.

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

Para casos de uso avançados ou quando uma relação não mapeia diretamente para um campo de formulário, você pode defini-la no 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"
}
]
});

Uma chave estrangeira nesta tabela aponta para a chave primária de outra tabela.

relations: [
{
kind: "belongsTo", // The FK is on THIS table
relationName: "author",
target: () => usersCollection,
localKey: "author_id" // Column on the posts table
}
]

Isso cria: posts.author_id → users.id

A chave estrangeira está na tabela de destino, apontando de volta para esta entidade.

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

Duas coleções conectadas através de uma tabela de junção intermediária.

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

Isso cria:

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

Para renderizar um campo de relação em um formulário, adicione uma propriedade com type: "relation":

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

Campo de relação no formulário

Ao renderizar uma pré-visualização (como em uma célula de tabela ou um chip de referência), o Rebase lida com a hidratação automaticamente:

Pré-visualização de relação na tabela

Para relacionamentos complexos que atravessam várias tabelas, use 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"]
}
}
]

Controle o que acontece quando entidades relacionadas são atualizadas ou excluídas:

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
}
]
Ação Comportamento
"cascade" Propaga a mudança para linhas relacionadas
"restrict" Impede a operação se existirem linhas relacionadas
"no action" O mesmo que restringir (adiar para verificação de restrição)
"set null" Define a coluna FK como NULL
"set default" Define a coluna FK para seu valor padrão

Ao consultar dados através do Rebase Client SDK, as relações não são incluídas por padrão. Use o método include() para solicitar entidades relacionadas juntamente com os dados primários.

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 incluída, a resposta contém tanto a chave estrangeira escalar quanto o objeto de relação 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"
}

Os nomes das relações passados para include() devem corresponder ao relationName definido no array relations da coleção.

Para a referência completa do construtor de consultas (filtragem, classificação, paginação, tempo real), consulte a documentação do Client SDK.

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
}