Saltar al contenido principal

Relaciones

[Traducción Beta No Oficial]

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Resumen rápido

Las relaciones (edges) son las asociaciones entre entidades. Por ejemplo: las mascotas de un usuario o los usuarios de un grupo:

En el ejemplo anterior, puedes ver 2 relaciones declaradas usando edges. Vamos a analizarlas.

1. Relaciones pets / owner; mascotas del usuario y dueño de la mascota:

ent/schema/user.go
package schema

import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
)

// User schema.
type User struct {
ent.Schema
}

// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
// ...
}
}

// Edges of the user.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type),
}
}

Como puedes ver, una entidad User puede tener muchas mascotas, pero una entidad Pet solo puede tener un dueño.
En la definición de relaciones, el edge pets es una relación O2M (uno a muchos), y el edge owner es una relación M2O (muchos a uno).

El esquema User posee la relación pets/owner porque usa edge.To, mientras que el esquema Pet solo tiene una referencia inversa declarada con edge.From usando el método Ref.

El método Ref especifica qué edge del esquema User estamos referenciando, ya que puede haber múltiples referencias desde un esquema hacia otros.

La cardinalidad de la relación se controla con el método Unique, que se explica con más detalle más adelante.

2. Relaciones users / groups; usuarios del grupo y grupos del usuario:

ent/schema/group.go
package schema

import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
)

// Group schema.
type Group struct {
ent.Schema
}

// Fields of the group.
func (Group) Fields() []ent.Field {
return []ent.Field{
// ...
}
}

// Edges of the group.
func (Group) Edges() []ent.Edge {
return []ent.Edge{
edge.To("users", User.Type),
}
}

Como puedes ver, una entidad Group puede tener muchos usuarios, y una entidad User puede tener muchos grupos.
En la definición de relaciones, el edge users es una relación M2M (muchos a muchos), y el edge groups también es una relación M2M (muchos a muchos).

To y From

edge.To y edge.From son los dos constructores para crear relaciones.

Un esquema que define un edge con edge.To posee la relación, a diferencia de edge.From que solo proporciona una referencia inversa (con nombre diferente).

Veamos varios ejemplos que muestran cómo definir diferentes tipos de relaciones usando edges.

Tipos de relación

O2O Two Types

En este ejemplo, un usuario tiene solo una tarjeta de crédito, y una tarjeta tiene solo un dueño.

El esquema User define un edge edge.To llamado card, mientras que el esquema Card define una referencia inversa con edge.From llamada owner.

ent/schema/user.go
// Edges of the user.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("card", Card.Type).
Unique(),
}
}

La API para interactuar con estas relaciones es la siguiente:

func Do(ctx context.Context, client *ent.Client) error {
a8m, err := client.User.
Create().
SetAge(30).
SetName("Mashraki").
Save(ctx)
if err != nil {
return fmt.Errorf("creating user: %w", err)
}
log.Println("user:", a8m)
card1, err := client.Card.
Create().
SetOwner(a8m).
SetNumber("1020").
SetExpired(time.Now().Add(time.Minute)).
Save(ctx)
if err != nil {
return fmt.Errorf("creating card: %w", err)
}
log.Println("card:", card1)
// Only returns the card of the user,
// and expects that there's only one.
card2, err := a8m.QueryCard().Only(ctx)
if err != nil {
return fmt.Errorf("querying card: %w", err)
}
log.Println("card:", card2)
// The Card entity is able to query its owner using
// its back-reference.
owner, err := card2.QueryOwner().Only(ctx)
if err != nil {
return fmt.Errorf("querying owner: %w", err)
}
log.Println("owner:", owner)
return nil
}

El ejemplo completo está en GitHub.

O2O Same Type

En este ejemplo de lista enlazada, tenemos una relación recursiva llamada next/prev. Cada nodo en la lista puede tener solo un nodo next. Si un nodo A apunta (usando next) al nodo B, B puede obtener su apuntador usando prev (la referencia inversa).

ent/schema/node.go
// Edges of the Node.
func (Node) Edges() []ent.Edge {
return []ent.Edge{
edge.To("next", Node.Type).
Unique().
From("prev").
Unique(),
}
}

Como puedes ver, en casos de relaciones del mismo tipo, puedes declarar la relación y su referencia en el mismo constructor.

func (Node) Edges() []ent.Edge {
return []ent.Edge{
+ edge.To("next", Node.Type).
+ Unique().
+ From("prev").
+ Unique(),

- edge.To("next", Node.Type).
- Unique(),
- edge.From("prev", Node.Type).
- Ref("next").
- Unique(),
}
}

La API para interactuar con estas relaciones es la siguiente:

func Do(ctx context.Context, client *ent.Client) error {
head, err := client.Node.
Create().
SetValue(1).
Save(ctx)
if err != nil {
return fmt.Errorf("creating the head: %w", err)
}
curr := head
// Generate the following linked-list: 1<->2<->3<->4<->5.
for i := 0; i < 4; i++ {
curr, err = client.Node.
Create().
SetValue(curr.Value + 1).
SetPrev(curr).
Save(ctx)
if err != nil {
return err
}
}

// Loop over the list and print it. `FirstX` panics if an error occur.
for curr = head; curr != nil; curr = curr.QueryNext().FirstX(ctx) {
fmt.Printf("%d ", curr.Value)
}
// Output: 1 2 3 4 5

// Make the linked-list circular:
// The tail of the list, has no "next".
tail, err := client.Node.
Query().
Where(node.Not(node.HasNext())).
Only(ctx)
if err != nil {
return fmt.Errorf("getting the tail of the list: %v", tail)
}
tail, err = tail.Update().SetNext(head).Save(ctx)
if err != nil {
return err
}
// Check that the change actually applied:
prev, err := head.QueryPrev().Only(ctx)
if err != nil {
return fmt.Errorf("getting head's prev: %w", err)
}
fmt.Printf("\n%v", prev.Value == tail.Value)
// Output: true
return nil
}

El ejemplo completo está en GitHub.

O2O Bidireccional

En este ejemplo de cónyuges entre usuarios, tenemos una relación O2O simétrica llamada spouse. Cada usuario puede tener solo un cónyuge. Si el usuario A establece su cónyuge (usando spouse) como B, B puede obtener su cónyuge usando la arista spouse.

Ten en cuenta que no hay términos de propietario/inverso en los casos de relaciones bidireccionales.

ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("spouse", User.Type).
Unique(),
}
}

La API para interactuar con esta arista es la siguiente:

func Do(ctx context.Context, client *ent.Client) error {
a8m, err := client.User.
Create().
SetAge(30).
SetName("a8m").
Save(ctx)
if err != nil {
return fmt.Errorf("creating user: %w", err)
}
nati, err := client.User.
Create().
SetAge(28).
SetName("nati").
SetSpouse(a8m).
Save(ctx)
if err != nil {
return fmt.Errorf("creating user: %w", err)
}

// Query the spouse edge.
// Unlike `Only`, `OnlyX` panics if an error occurs.
spouse := nati.QuerySpouse().OnlyX(ctx)
fmt.Println(spouse.Name)
// Output: a8m

spouse = a8m.QuerySpouse().OnlyX(ctx)
fmt.Println(spouse.Name)
// Output: nati

// Query how many users have a spouse.
// Unlike `Count`, `CountX` panics if an error occurs.
count := client.User.
Query().
Where(user.HasSpouse()).
CountX(ctx)
fmt.Println(count)
// Output: 2

// Get the user, that has a spouse with name="a8m".
spouse = client.User.
Query().
Where(user.HasSpouseWith(user.Name("a8m"))).
OnlyX(ctx)
fmt.Println(spouse.Name)
// Output: nati
return nil
}

Nota: la columna de clave foránea puede configurarse y exponerse como campo de entidad usando la opción Edge Field así:

// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("spouse_id").
Optional(),
}
}

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("spouse", User.Type).
Unique().
Field("spouse_id"),
}
}

El ejemplo completo está en GitHub.

O2M Dos Tipos

En este ejemplo de usuarios-mascotas, tenemos una relación O2M entre un usuario y sus mascotas. Cada usuario tiene muchas mascotas, y cada mascota tiene un dueño. Si el usuario A añade una mascota B usando la arista pets, B puede obtener su dueño mediante la arista owner (la referencia inversa).

Nota que esta relación también es M2O (muchos-a-uno) desde la perspectiva del esquema Pet.

ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type),
}
}

La API para interactuar con estas relaciones es la siguiente:

func Do(ctx context.Context, client *ent.Client) error {
// Create the 2 pets.
pedro, err := client.Pet.
Create().
SetName("pedro").
Save(ctx)
if err != nil {
return fmt.Errorf("creating pet: %w", err)
}
lola, err := client.Pet.
Create().
SetName("lola").
Save(ctx)
if err != nil {
return fmt.Errorf("creating pet: %w", err)
}
// Create the user, and add its pets on the creation.
a8m, err := client.User.
Create().
SetAge(30).
SetName("a8m").
AddPets(pedro, lola).
Save(ctx)
if err != nil {
return fmt.Errorf("creating user: %w", err)
}
fmt.Println("User created:", a8m)
// Output: User(id=1, age=30, name=a8m)

// Query the owner. Unlike `Only`, `OnlyX` panics if an error occurs.
owner := pedro.QueryOwner().OnlyX(ctx)
fmt.Println(owner.Name)
// Output: a8m

// Traverse the sub-graph. Unlike `Count`, `CountX` panics if an error occurs.
count := pedro.
QueryOwner(). // a8m
QueryPets(). // pedro, lola
CountX(ctx) // count
fmt.Println(count)
// Output: 2
return nil
}

Nota: la columna de clave foránea puede configurarse y exponerse como campo de entidad usando la opción Edge Field así:

ent/schema/pet.go
// Fields of the Pet.
func (Pet) Fields() []ent.Field {
return []ent.Field{
field.Int("owner_id").
Optional(),
}
}

// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("pets").
Unique().
Field("owner_id"),
}
}

El ejemplo completo está en GitHub.

O2M Mismo Tipo

En este ejemplo, tenemos una relación O2M recursiva entre nodos de un árbol y sus hijos (o su padre).
Cada nodo en el árbol tiene muchos hijos, y tiene un padre. Si el nodo A añade B a sus hijos, B puede obtener su propietario usando la arista owner.

ent/schema/node.go
// Edges of the Node.
func (Node) Edges() []ent.Edge {
return []ent.Edge{
edge.To("children", Node.Type).
From("parent").
Unique(),
}
}

Como puedes ver, en casos de relaciones del mismo tipo, puedes declarar la relación y su referencia en el mismo constructor.

func (Node) Edges() []ent.Edge {
return []ent.Edge{
+ edge.To("children", Node.Type).
+ From("parent").
+ Unique(),

- edge.To("children", Node.Type),
- edge.From("parent", Node.Type).
- Ref("children").
- Unique(),
}
}

La API para interactuar con estas relaciones es la siguiente:

func Do(ctx context.Context, client *ent.Client) error {
root, err := client.Node.
Create().
SetValue(2).
Save(ctx)
if err != nil {
return fmt.Errorf("creating the root: %w", err)
}
// Add additional nodes to the tree:
//
// 2
// / \
// 1 4
// / \
// 3 5
//
// Unlike `Save`, `SaveX` panics if an error occurs.
n1 := client.Node.
Create().
SetValue(1).
SetParent(root).
SaveX(ctx)
n4 := client.Node.
Create().
SetValue(4).
SetParent(root).
SaveX(ctx)
n3 := client.Node.
Create().
SetValue(3).
SetParent(n4).
SaveX(ctx)
n5 := client.Node.
Create().
SetValue(5).
SetParent(n4).
SaveX(ctx)

fmt.Println("Tree leafs", []int{n1.Value, n3.Value, n5.Value})
// Output: Tree leafs [1 3 5]

// Get all leafs (nodes without children).
// Unlike `Int`, `IntX` panics if an error occurs.
ints := client.Node.
Query(). // All nodes.
Where(node.Not(node.HasChildren())). // Only leafs.
Order(ent.Asc(node.FieldValue)). // Order by their `value` field.
GroupBy(node.FieldValue). // Extract only the `value` field.
IntsX(ctx)
fmt.Println(ints)
// Output: [1 3 5]

// Get orphan nodes (nodes without parent).
// Unlike `Only`, `OnlyX` panics if an error occurs.
orphan := client.Node.
Query().
Where(node.Not(node.HasParent())).
OnlyX(ctx)
fmt.Println(orphan)
// Output: Node(id=1, value=2)

return nil
}

Nota: la columna de clave foránea puede configurarse y exponerse como campo de entidad usando la opción Edge Field así:

// Fields of the Node.
func (Node) Fields() []ent.Field {
return []ent.Field{
field.Int("parent_id").
Optional(),
}
}

// Edges of the Node.
func (Node) Edges() []ent.Edge {
return []ent.Edge{
edge.To("children", Node.Type).
From("parent").
Unique().
Field("parent_id"),
}
}

El ejemplo completo está en GitHub.

M2M Dos Tipos

En este ejemplo de grupos-usuarios, tenemos una relación M2M entre grupos y sus usuarios. Cada grupo tiene muchos usuarios, y cada usuario puede unirse a muchos grupos.

ent/schema/group.go
// Edges of the Group.
func (Group) Edges() []ent.Edge {
return []ent.Edge{
edge.To("users", User.Type),
}
}
ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.From("groups", Group.Type).
Ref("users"),
}
}

La API para interactuar con estas relaciones es la siguiente:

func Do(ctx context.Context, client *ent.Client) error {
// Unlike `Save`, `SaveX` panics if an error occurs.
hub := client.Group.
Create().
SetName("GitHub").
SaveX(ctx)
lab := client.Group.
Create().
SetName("GitLab").
SaveX(ctx)
a8m := client.User.
Create().
SetAge(30).
SetName("a8m").
AddGroups(hub, lab).
SaveX(ctx)
nati := client.User.
Create().
SetAge(28).
SetName("nati").
AddGroups(hub).
SaveX(ctx)

// Query the edges.
groups, err := a8m.
QueryGroups().
All(ctx)
if err != nil {
return fmt.Errorf("querying a8m groups: %w", err)
}
fmt.Println(groups)
// Output: [Group(id=1, name=GitHub) Group(id=2, name=GitLab)]

groups, err = nati.
QueryGroups().
All(ctx)
if err != nil {
return fmt.Errorf("querying nati groups: %w", err)
}
fmt.Println(groups)
// Output: [Group(id=1, name=GitHub)]

// Traverse the graph.
users, err := a8m.
QueryGroups(). // [hub, lab]
Where(group.Not(group.HasUsersWith(user.Name("nati")))). // [lab]
QueryUsers(). // [a8m]
QueryGroups(). // [hub, lab]
QueryUsers(). // [a8m, nati]
All(ctx)
if err != nil {
return fmt.Errorf("traversing the graph: %w", err)
}
fmt.Println(users)
// Output: [User(id=1, age=30, name=a8m) User(id=2, age=28, name=nati)]
return nil
}
nota

Llamar a AddGroups (una arista M2M) resultará en una operación nula si la arista ya existe y no es un EdgeSchema:

a8m := client.User.
Create().
SetName("a8m").
AddGroups(
hub,
hub, // no-op.
).
SaveX(ctx)

El ejemplo completo está en GitHub.

M2M del mismo tipo

En este ejemplo de seguidores, tenemos una relación M2M entre usuarios y sus seguidores. Cada usuario puede seguir a muchos usuarios y puede tener muchos seguidores.

ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("following", User.Type).
From("followers"),
}
}

Como puedes ver, en casos de relaciones del mismo tipo, puedes declarar la relación y su referencia en el mismo constructor.

func (User) Edges() []ent.Edge {
return []ent.Edge{
+ edge.To("following", User.Type).
+ From("followers"),

- edge.To("following", User.Type),
- edge.From("followers", User.Type).
- Ref("following"),
}
}

La API para interactuar con estas relaciones es la siguiente:

func Do(ctx context.Context, client *ent.Client) error {
// Unlike `Save`, `SaveX` panics if an error occurs.
a8m := client.User.
Create().
SetAge(30).
SetName("a8m").
SaveX(ctx)
nati := client.User.
Create().
SetAge(28).
SetName("nati").
AddFollowers(a8m).
SaveX(ctx)

// Query following/followers:

flw := a8m.QueryFollowing().AllX(ctx)
fmt.Println(flw)
// Output: [User(id=2, age=28, name=nati)]

flr := a8m.QueryFollowers().AllX(ctx)
fmt.Println(flr)
// Output: []

flw = nati.QueryFollowing().AllX(ctx)
fmt.Println(flw)
// Output: []

flr = nati.QueryFollowers().AllX(ctx)
fmt.Println(flr)
// Output: [User(id=1, age=30, name=a8m)]

// Traverse the graph:

ages := nati.
QueryFollowers(). // [a8m]
QueryFollowing(). // [nati]
GroupBy(user.FieldAge). // [28]
IntsX(ctx)
fmt.Println(ages)
// Output: [28]

names := client.User.
Query().
Where(user.Not(user.HasFollowers())).
GroupBy(user.FieldName).
StringsX(ctx)
fmt.Println(names)
// Output: [a8m]
return nil
}
nota

Llamar a AddFollowers (una relación M2M) resultará en una operación nula (no-op) si la relación ya existe y no es un Esquema de Relación:

a8m := client.User.
Create().
SetName("a8m").
AddFollowers(
nati,
nati, // no-op.
).
SaveX(ctx)

El ejemplo completo está en GitHub.

M2M Bidireccional

En este ejemplo de amistades entre usuarios, tenemos una relación M2M simétrica llamada friends. Cada usuario puede tener muchos amigos. Si el usuario A se hace amigo de B, entonces B también es amigo de A.

Ten en cuenta que no hay términos de propietario/inverso en los casos de relaciones bidireccionales.

ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("friends", User.Type),
}
}

La API para interactuar con estas relaciones es la siguiente:

func Do(ctx context.Context, client *ent.Client) error {
// Unlike `Save`, `SaveX` panics if an error occurs.
a8m := client.User.
Create().
SetAge(30).
SetName("a8m").
SaveX(ctx)
nati := client.User.
Create().
SetAge(28).
SetName("nati").
AddFriends(a8m).
SaveX(ctx)

// Query friends. Unlike `All`, `AllX` panics if an error occurs.
friends := nati.
QueryFriends().
AllX(ctx)
fmt.Println(friends)
// Output: [User(id=1, age=30, name=a8m)]

friends = a8m.
QueryFriends().
AllX(ctx)
fmt.Println(friends)
// Output: [User(id=2, age=28, name=nati)]

// Query the graph:
friends = client.User.
Query().
Where(user.HasFriends()).
AllX(ctx)
fmt.Println(friends)
// Output: [User(id=1, age=30, name=a8m) User(id=2, age=28, name=nati)]
return nil
}
nota

Llamar a AddFriends (una relación M2M bidireccional) resultará en una operación nula (no-op) si la relación ya existe y no es un Esquema de Relación:

a8m := client.User.
Create().
SetName("a8m").
AddFriends(
nati,
nati, // no-op.
).
SaveX(ctx)

El ejemplo completo está en GitHub.

Campo de Relación

La opción Field para relaciones permite exponer claves foráneas como campos regulares en el esquema. Ten en cuenta que solo las relaciones que contienen claves foráneas (edge-ids) pueden usar esta opción.

ent/schema/post.go
// Fields of the Post.
func (Post) Fields() []ent.Field {
return []ent.Field{
field.Int("author_id").
Optional(),
}
}

// Edges of the Post.
func (Post) Edges() []ent.Edge {
return []ent.Edge{
edge.To("author", User.Type).
// Bind the "author_id" field to this edge.
Field("author_id").
Unique(),
}
}

La API para interactuar con campos de relación es la siguiente:

func Do(ctx context.Context, client *ent.Client) error {
p, err := c.Post.Query().
Where(post.AuthorID(id)).
OnlyX(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println(p.AuthorID) // Access the "author" foreign-key.
}

Hay múltiples ejemplos en GitHub.

Migración a Campos de Relación

Como se menciona en la sección StorageKey, Ent configura las claves de almacenamiento de relaciones (ej. claves foráneas) mediante edge.To. Por lo tanto, si quieres añadir un campo a una relación existente (que ya existe en la base de datos como columna), debes configurarlo con la opción StorageKey de esta forma:

// Fields of the Post.
func (Post) Fields() []ent.Field {
return []ent.Field{
+ field.Int("author_id").
+ Optional(),
}
}

// Edges of the Post.
func (Post) Edges() []ent.Edge {
return []ent.Edge{
edge.From("author", User.Type).
+ Field("author_id").
+ StorageKey(edge.Column("post_author")).
Unique(),
}
}

Alternativamente, esta opción puede configurarse directamente en el campo de relación:

// Fields of the Post.
func (Post) Fields() []ent.Field {
return []ent.Field{
+ field.Int("author_id").
+ StorageKey("post_author").
+ Optional(),
}
}

Si no estás seguro de cómo se llamaba la clave foránea antes de usar la opción de campo de relación, revisa la descripción del esquema generado en tu proyecto: <project>/ent/migrate/schema.go.

Esquema de Relación

Los esquemas de relación son esquemas de entidades intermedias para relaciones M2M. Usando la opción Through, los usuarios pueden definir esquemas de relación para asociaciones. Esto permite exponer relaciones en APIs públicas, almacenar campos adicionales, aplicar operaciones CRUD y configurar hooks y políticas de privacidad en relaciones.

Ejemplo de Amistades entre Usuarios

En el siguiente ejemplo, demostramos cómo modelar la amistad entre dos usuarios usando un esquema de relación con los dos campos requeridos de la asociación (user_id y friend_id), y un campo adicional llamado created_at cuyo valor se establece automáticamente al crear.

ent/schema/user.go
// User holds the schema definition for the User entity.
type User struct {
ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Default("Unknown"),
}
}

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("friends", User.Type).
Through("friendships", Friendship.Type),
}
}
información
  • Al igual que en los esquemas de entidades, el campo ID se genera automáticamente para los esquemas de relaciones si no se especifica lo contrario.
  • Los esquemas de relaciones no pueden ser utilizados por más de una relación.
  • Los campos de relación user_id y friend_id son obligatorios en el esquema de relación ya que componen la asociación.

Ejemplo de "Me gusta" de usuarios

En el siguiente ejemplo, modelamos un sistema donde los usuarios pueden dar "me gusta" a tweets, almacenando además una marca temporal del momento en que se dio el "me gusta". Esta es una forma de guardar campos adicionales en la relación.

ent/schema/user.go
// User holds the schema definition for the User entity.
type User struct {
ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Default("Unknown"),
}
}

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("liked_tweets", Tweet.Type).
Through("likes", Like.Type),
}
}
información

En el ejemplo anterior, la anotación field.ID indica a Ent que el identificador del esquema de relación es una clave primaria compuesta por los dos campos de relación: user_id y tweet_id. Por tanto, el campo ID no se generará para la estructura Like ni sus métodos asociados (ej. Get, OnlyID, etc.).

Uso de esquemas de relación en otros tipos de asociaciones

En algunos casos, conviene almacenar relaciones O2M/M2O u O2O en una tabla separada (tabla de unión) para simplificar futuras migraciones si el tipo de relación cambia. Por ejemplo, transformar una relación O2M/M2O en M2M eliminando una restricción única en lugar de migrar valores de claves externas a una nueva tabla.

En este ejemplo, presentamos un modelo donde usuarios pueden "autor" tweets con la restricción de que un tweet solo puede ser escrito por un usuario. A diferencia de las relaciones O2M/M2O convencionales, al usar un esquema de relación, reforzamos esta restricción en la tabla de unión mediante un índice único en la columna tweet_id. Esta restricción podría eliminarse posteriormente para permitir múltiples autores por tweet, transformando así la relación en M2M sin migrar datos a una nueva tabla.

ent/schema/user.go
// User holds the schema definition for the User entity.
type User struct {
ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Default("Unknown"),
}
}

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("tweets", Tweet.Type).
Through("user_tweets", UserTweet.Type),
}
}

Obligatoriedad

Las relaciones pueden definirse como obligatorias durante la creación de entidades usando el método Required en el constructor.

// Edges of the Card.
func (Card) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("card").
Unique().
Required(),
}
}

En el ejemplo anterior, no se puede crear una entidad de tarjeta sin su propietario.

información

Nota: desde la v0.10, las columnas de clave externa se crean como NOT NULL en la base de datos para relaciones obligatorias que no sean de auto-referencia. Para migrar columnas existentes, usa la opción de Migración Atlas.

Inmutables

Las relaciones inmutables solo pueden establecerse durante la creación de la entidad. Es decir, no se generarán métodos modificadores en los constructores de actualización.

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("tenant", Tenant.Type).
Field("tenant_id").
Unique().
Required().
Immutable(),
}
}

Clave de almacenamiento

Por defecto, Ent configura las claves de almacenamiento de relaciones según el propietario de la relación (esquema que contiene edge.To), no según la referencia inversa (edge.From). Esto ocurre porque las referencias inversas son opcionales y pueden eliminarse.

Para usar configuraciones personalizadas de almacenamiento, utiliza el método StorageKey como se muestra:

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type).
// Set the column name in the "pets" table for O2M relationship.
StorageKey(edge.Column("owner_id")),
edge.To("cars", Car.Type).
// Set the symbol of the foreign-key constraint for O2M relationship.
StorageKey(edge.Symbol("cars_owner_id")),
edge.To("friends", User.Type).
// Set the join-table, and the column names for a M2M relationship.
StorageKey(edge.Table("friends"), edge.Columns("user_id", "friend_id")),
edge.To("groups", Group.Type).
// Set the join-table, its column names and the symbols
// of the foreign-key constraints for M2M relationship.
StorageKey(
edge.Table("groups"),
edge.Columns("user_id", "group_id"),
edge.Symbols("groups_id1", "groups_id2")
),
}
}

Etiquetas de Estructura

Se pueden agregar etiquetas personalizadas a las estructuras generadas usando el método StructTag. Ten en cuenta que si no se proporciona esta opción, o se proporciona pero no contiene la etiqueta json, se agregará automáticamente la etiqueta json con el nombre del campo.

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type).
// Override the default json tag "pets" with "owner" for O2M relationship.
StructTag(`json:"owner"`),
}
}

Índices

Se pueden definir índices en múltiples campos y también en algunos tipos de relaciones. Sin embargo, debes tener en cuenta que actualmente esta es una característica exclusiva de SQL.

Lee más sobre esto en la sección Índices.

Comentarios

Puede añadirse un comentario a la relación usando el método .Comment(). Este comentario aparecerá antes de la relación en el código de entidad generado. Se admiten saltos de línea usando la secuencia de escape \n.

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type).
Comment("Pets that this user is responsible for taking care of.\n" +
"May be zero to many, depending on the user.")
}
}

Anotaciones

Annotations se utiliza para adjuntar metadatos arbitrarios al objeto de borde durante la generación de código. Las extensiones de plantilla pueden recuperar estos metadatos y usarlos dentro de sus propias plantillas.

Ten en cuenta que el objeto de metadatos debe ser serializable a un valor JSON en bruto (por ejemplo, struct, map o slice).

// Pet schema.
type Pet struct {
ent.Schema
}

// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.To("owner", User.Type).
Ref("pets").
Unique().
Annotations(entgql.RelayConnection()),
}
}

Lee más sobre anotaciones y su uso en plantillas en la documentación de plantillas.

Convención de nombres

Por convención, los nombres de bordes deben usar snake_case. Los campos de estructura correspondientes generados por ent seguirán la convención de Go de usar PascalCase. En casos donde se desee PascalCase, puedes lograrlo con los métodos StorageKey o StructTag.