Files
ent/doc/md/schema-edges.mdx
Noam Cattan d143432580 doc: replace diagram examples links (#3435)
* cmd/internal: fix help message for "new" command

* doc: replace example links
2023-04-03 16:38:57 +03:00

1688 lines
40 KiB
Plaintext

---
id: schema-edges
title: Edges
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Quick Summary
Edges are the relations (or associations) of entities. For example, user's pets, or group's users:
<Tabs>
<TabItem value="graph" label="Graph" default>
![er-group-users](https://entgo.io/images/assets/er_user_pets_groups.png)
</TabItem>
<TabItem value="erd" label="ERD and SQL">
[![erd-group-users](https://entgo.io/images/assets/erd/edges-quick-summary.png)](https://gh.atlasgo.cloud/explore/saved/60129542144)
<div style={{textAlign: 'center'}}>
<a href={"https://gh.atlasgo.cloud/explore/saved/60129542144"} style={{fontSize: 12}}>ERD was generated by Atlas</a>
</div>
</TabItem>
</Tabs>
In the example above, you can see 2 relations declared using edges. Let's go over them.
1\. `pets` / `owner` edges; user's pets and pet's owner:
<Tabs>
<TabItem value="user" label="User" default>
```go title="ent/schema/user.go" {23}
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),
}
}
```
</TabItem>
<TabItem value="pet" label="Pet">
```go title="ent/schema/pet.go" {23-25}
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
)
// Pet holds the schema definition for the Pet entity.
type Pet struct {
ent.Schema
}
// Fields of the Pet.
func (Pet) Fields() []ent.Field {
return []ent.Field{
// ...
}
}
// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("pets").
Unique(),
}
}
```
</TabItem>
</Tabs>
As you can see, a `User` entity can **have many** pets, but a `Pet` entity can **have only one** owner.
In relationship definition, the `pets` edge is a *O2M* (one-to-many) relationship, and the `owner` edge
is a *M2O* (many-to-one) relationship.
The `User` schema **owns** the `pets/owner` relationship because it uses `edge.To`, and the `Pet` schema
just has a back-reference to it, declared using `edge.From` with the `Ref` method.
The `Ref` method describes which edge of the `User` schema we're referencing because there can be multiple
references from one schema to other.
The cardinality of the edge/relationship can be controlled using the `Unique` method, and it's explained
more widely below.
2\. `users` / `groups` edges; group's users and user's groups:
<Tabs>
<TabItem value="group" label="Group" default>
```go title="ent/schema/group.go" {23}
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),
}
}
```
</TabItem>
<TabItem value="user" label="User">
```go title="ent/schema/user.go" {23-24}
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.From("groups", Group.Type).
Ref("users"),
// "pets" declared in the example above.
edge.To("pets", Pet.Type),
}
}
```
</TabItem>
</Tabs>
As you can see, a Group entity can **have many** users, and a User entity can have **have many** groups.
In relationship definition, the `users` edge is a *M2M* (many-to-many) relationship, and the `groups`
edge is also a *M2M* (many-to-many) relationship.
## To and From
`edge.To` and `edge.From` are the 2 builders for creating edges/relations.
A schema that defines an edge using the `edge.To` builder owns the relation,
unlike using the `edge.From` builder that gives only a back-reference for the relation (with a different name).
Let's go over a few examples that show how to define different relation types using edges.
## Relationship
- [O2O Two Types](#o2o-two-types)
- [O2O Same Type](#o2o-same-type)
- [O2O Bidirectional](#o2o-bidirectional)
- [O2M Two Types](#o2m-two-types)
- [O2M Same Type](#o2m-same-type)
- [M2M Two Types](#m2m-two-types)
- [M2M Same Type](#m2m-same-type)
- [M2M Bidirectional](#m2m-bidirectional)
## O2O Two Types
<Tabs>
<TabItem value="graph" label="Graph" default>
![er-user-card](https://entgo.io/images/assets/er_user_card.png)
</TabItem>
<TabItem value="erd" label="ERD and SQL">
[![edges-o2o-two-types](https://entgo.io/images/assets/erd/edges-o2o-two-types.png)](https://gh.atlasgo.cloud/explore/saved/60129542145)
<div style={{textAlign: 'center'}}>
<a href={"https://gh.atlasgo.cloud/explore/saved/60129542145"} style={{fontSize: 12}}>ERD was generated by Atlas</a>
</div>
</TabItem>
</Tabs>
In this example, a user **has only one** credit-card, and a card **has only one** owner.
The `User` schema defines an `edge.To` card named `card`, and the `Card` schema
defines a back-reference to this edge using `edge.From` named `owner`.
<Tabs>
<TabItem value="user" label="User" default>
```go title="ent/schema/user.go"
// Edges of the user.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("card", Card.Type).
Unique(),
}
}
```
</TabItem>
<TabItem value="card" label="Card">
```go title="ent/schema/card.go"
// Edges of the Card.
func (Card) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("card").
Unique().
// We add the "Required" method to the builder
// to make this edge required on entity creation.
// i.e. Card cannot be created without its owner.
Required(),
}
}
```
</TabItem>
</Tabs>
The API for interacting with these edges is as follows:
```go
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
}
```
The full example exists in [GitHub](https://github.com/ent/ent/tree/master/examples/o2o2types).
## O2O Same Type
<Tabs>
<TabItem value="graph" label="Graph" default>
![er-linked-list](https://entgo.io/images/assets/er_linked_list.png)
</TabItem>
<TabItem value="erd" label="ERD and SQL">
[![edges-linked-list](https://entgo.io/images/assets/erd/edges-o2o-same-type.png)](https://gh.atlasgo.cloud/explore/saved/60129542146)
<div style={{textAlign: 'center'}}>
<a href={"https://gh.atlasgo.cloud/explore/saved/60129542146"} style={{fontSize: 12}}>ERD was generated by Atlas</a>
</div>
</TabItem>
</Tabs>
In this linked-list example, we have a **recursive relation** named `next`/`prev`. Each node in the list can
**have only one** `next` node. If a node A points (using `next`) to node B, B can get its pointer using `prev` (the back-reference edge).
```go title="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(),
}
}
```
As you can see, in cases of relations of the same type, you can declare the edge and its
reference in the same builder.
```diff
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(),
}
}
```
The API for interacting with these edges is as follows:
```go
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
}
```
The full example exists in [GitHub](https://github.com/ent/ent/tree/master/examples/o2orecur).
## O2O Bidirectional
<Tabs>
<TabItem value="graph" label="Graph" default>
![er-user-spouse](https://entgo.io/images/assets/er_user_spouse.png)
</TabItem>
<TabItem value="erd" label="ERD and SQL">
[![edges-o2o-bidirectional](https://entgo.io/images/assets/erd/edges-o2o-bidirectional.png)](https://gh.atlasgo.cloud/explore/saved/60129542147)
<div style={{textAlign: 'center'}}>
<a href={"https://gh.atlasgo.cloud/explore/saved/60129542147"} style={{fontSize: 12}}>ERD was generated by Atlas</a>
</div>
</TabItem>
</Tabs>
In this user-spouse example, we have a **symmetric O2O relation** named `spouse`. Each user can **have only one** spouse.
If user A sets its spouse (using `spouse`) to B, B can get its spouse using the `spouse` edge.
Note that there are no owner/inverse terms in cases of bidirectional edges.
```go title="ent/schema/user.go"
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("spouse", User.Type).
Unique(),
}
}
```
The API for interacting with this edge is as follows:
```go
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
}
```
Note that, the foreign-key column can be configured and exposed as an entity field using the
[Edge Field](#edge-field) option as follows:
```go {4,14}
// 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"),
}
}
```
The full example exists in [GitHub](https://github.com/ent/ent/tree/master/examples/o2obidi).
## O2M Two Types
<Tabs>
<TabItem value="graph" label="Graph" default>
![er-user-pets](https://entgo.io/images/assets/er_user_pets.png)
</TabItem>
<TabItem value="erd" label="ERD and SQL">
[![edges-o2m-two-types](https://entgo.io/images/assets/erd/edges-o2m-two-types.png)](https://gh.atlasgo.cloud/explore/saved/60129542148)
<div style={{textAlign: 'center'}}>
<a href={"https://gh.atlasgo.cloud/explore/saved/60129542148"} style={{fontSize: 12}}>ERD was generated by Atlas</a>
</div>
</TabItem>
</Tabs>
In this user-pets example, we have a O2M relation between user and its pets.
Each user **has many** pets, and a pet **has one** owner.
If user A adds a pet B using the `pets` edge, B can get its owner using the `owner` edge (the back-reference edge).
Note that this relation is also a M2O (many-to-one) from the point of view of the `Pet` schema.
<Tabs>
<TabItem value="user" label="User" default>
```go title="ent/schema/user.go" {4}
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type),
}
}
```
</TabItem>
<TabItem value="pet" label="Pet">
```go title="ent/schema/pet.go" {4-6}
// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("pets").
Unique(),
}
}
```
</TabItem>
</Tabs>
The API for interacting with these edges is as follows:
```go
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
}
```
Note that, the foreign-key column can be configured and exposed as an entity field using the
[Edge Field](#edge-field) option as follows:
```go title="ent/schema/pet.go" {4,15}
// 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"),
}
}
```
The full example exists in [GitHub](https://github.com/ent/ent/tree/master/examples/o2m2types).
## O2M Same Type
<Tabs>
<TabItem value="graph" label="Graph" default>
![er-tree](https://entgo.io/images/assets/er_tree.png)
</TabItem>
<TabItem value="erd" label="ERD and SQL">
[![edges-o2m-same-type](https://entgo.io/images/assets/erd/edges-o2m-same-type.png)](https://gh.atlasgo.cloud/explore/saved/60129542149)
<div style={{textAlign: 'center'}}>
<a href={"https://gh.atlasgo.cloud/explore/saved/60129542149"} style={{fontSize: 12}}>ERD was generated by Atlas</a>
</div>
</TabItem>
</Tabs>
In this example, we have a recursive O2M relation between tree's nodes and their children (or their parent).
Each node in the tree **has many** children, and **has one** parent. If node A adds B to its children,
B can get its owner using the `owner` edge.
```go title="ent/schema/node.go"
// Edges of the Node.
func (Node) Edges() []ent.Edge {
return []ent.Edge{
edge.To("children", Node.Type).
From("parent").
Unique(),
}
}
```
As you can see, in cases of relations of the same type, you can declare the edge and its
reference in the same builder.
```diff
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(),
}
}
```
The API for interacting with these edges is as follows:
```go
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
}
```
Note that, the foreign-key column can be configured and exposed as an entity field using the
[Edge Field](#edge-field) option as follows:
```go {4,15}
// 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"),
}
}
```
The full example exists in [GitHub](https://github.com/ent/ent/tree/master/examples/o2mrecur).
## M2M Two Types
<Tabs>
<TabItem value="graph" label="Graph" default>
![er-user-groups](https://entgo.io/images/assets/er_user_groups.png)
</TabItem>
<TabItem value="erd" label="ERD and SQL">
[![edges-m2m-two-types](https://entgo.io/images/assets/erd/edges-m2m-two-types.png)](https://gh.atlasgo.cloud/explore/saved/60129542150)
<div style={{textAlign: 'center'}}>
<a href={"https://gh.atlasgo.cloud/explore/saved/60129542150"} style={{fontSize: 12}}>ERD was generated by Atlas</a>
</div>
</TabItem>
</Tabs>
In this groups-users example, we have a M2M relation between groups and their users.
Each group **has many** users, and each user can be joined to **many** groups.
```go title="ent/schema/group.go"
// Edges of the Group.
func (Group) Edges() []ent.Edge {
return []ent.Edge{
edge.To("users", User.Type),
}
}
```
```go title="ent/schema/user.go"
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.From("groups", Group.Type).
Ref("users"),
}
}
```
The API for interacting with these edges is as follows:
```go
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
}
```
:::note
Calling `AddGroups` (a M2M edge) will result in a no-op in case the edge already exists and is
not an [EdgeSchema](#edge-schema):
```go {6}
a8m := client.User.
Create().
SetName("a8m").
AddGroups(
hub,
hub, // no-op.
).
SaveX(ctx)
```
:::
The full example exists in [GitHub](https://github.com/ent/ent/tree/master/examples/m2m2types).
## M2M Same Type
<Tabs>
<TabItem value="graph" label="Graph" default>
![er-following-followers](https://entgo.io/images/assets/er_following_followers.png)
</TabItem>
<TabItem value="erd" label="ERD and SQL">
[![edges-m2m-same-type](https://entgo.io/images/assets/erd/edges-m2m-same-type.png)](https://gh.atlasgo.cloud/explore/saved/60129542151)
<div style={{textAlign: 'center'}}>
<a href={"https://gh.atlasgo.cloud/explore/saved/60129542151"} style={{fontSize: 12}}>ERD was generated by Atlas</a>
</div>
</TabItem>
</Tabs>
In this following-followers example, we have a M2M relation between users to their followers. Each user
can follow **many** users, and can have **many** followers.
```go title="ent/schema/user.go"
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("following", User.Type).
From("followers"),
}
}
```
As you can see, in cases of relations of the same type, you can declare the edge and its
reference in the same builder.
```diff
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"),
}
}
```
The API for interacting with these edges is as follows:
```go
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
}
```
:::note
Calling `AddFollowers` (a M2M edge) will result in a no-op in case the edge already exists and is
not an [EdgeSchema](#edge-schema):
```go {6}
a8m := client.User.
Create().
SetName("a8m").
AddFollowers(
nati,
nati, // no-op.
).
SaveX(ctx)
```
:::
The full example exists in [GitHub](https://github.com/ent/ent/tree/master/examples/m2mrecur).
## M2M Bidirectional
<Tabs>
<TabItem value="graph" label="Graph" default>
![er-user-friends](https://entgo.io/images/assets/er_user_friends.png)
</TabItem>
<TabItem value="erd" label="ERD and SQL">
[![edges-m2m-bidirectional](https://entgo.io/images/assets/erd/edges-m2m-bidirectional.png)](https://gh.atlasgo.cloud/explore/saved/60129542152)
<div style={{textAlign: 'center'}}>
<a href={"https://gh.atlasgo.cloud/explore/saved/60129542152"} style={{fontSize: 12}}>ERD was generated by Atlas</a>
</div>
</TabItem>
</Tabs>
In this user-friends example, we have a **symmetric M2M relation** named `friends`.
Each user can **have many** friends. If user A becomes a friend of B, B is also a friend of A.
Note that there are no owner/inverse terms in cases of bidirectional edges.
```go title="ent/schema/user.go"
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("friends", User.Type),
}
}
```
The API for interacting with these edges is as follows:
```go
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
}
```
:::note
Calling `AddFriends` (a M2M bidirectional edge) will result in a no-op in case the edge already exists and is
not an [EdgeSchema](#edge-schema):
```go {6}
a8m := client.User.
Create().
SetName("a8m").
AddFriends(
nati,
nati, // no-op.
).
SaveX(ctx)
```
:::
The full example exists in [GitHub](https://github.com/ent/ent/tree/master/examples/m2mbidi).
## Edge Field
The `Field` option for edges allows users to expose foreign-keys as regular fields on the schema.
Note that only relations that hold foreign-keys (edge-ids) are allowed to use this option.
```go title="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(),
}
}
```
The API for interacting with edge-fields is as follows:
```go
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.
}
```
Multiple examples exists in [GitHub](https://github.com/ent/ent/tree/master/entc/integration/edgefield).
#### Migration To Edge Fields
As mentioned in the [StorageKey](#storagekey) section, Ent configures edge storage-keys (e.g. foreign-keys) by the
`edge.To`. Therefore, if you want to add a field to an existing edge (already exists in the database as a column),
you need to set it up with the `StorageKey` option as follows:
```diff
// 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(),
}
}
```
Alternatively, this option can be configured on the edge-field instead:
```diff
// Fields of the Post.
func (Post) Fields() []ent.Field {
return []ent.Field{
+ field.Int("author_id").
+ StorageKey("post_author").
+ Optional(),
}
}
```
If you're not sure how the foreign-key was named before using the edge-field option,
check out the generated schema description in your project: `<project>/ent/migrate/schema.go`.
## Edge Schema
Edge schemas are intermediate entity schemas for M2M edges. By using the `Through` option, users can define edge schemas
for relationships. This allows users to expose relationships in their public APIs, store additional fields, apply CRUD
operations, and set hooks and privacy policies on edges.
#### User Friendships Example
In the following example, we demonstrate how to model the friendship between two users using an edge schema with the two
required fields of the relationship (`user_id` and `friend_id`), and an additional field named `created_at` whose value
is automatically set on creation.
<Tabs>
<TabItem value="graph" label="Graph" default>
![er_edgeschema_bidi](https://entgo.io/images/assets/er_edgeschema_bidi.png)
</TabItem>
<TabItem value="erd" label="ERD and SQL">
[![edges-schema](https://entgo.io/images/assets/erd/edges-schema.png)](https://gh.atlasgo.cloud/explore/saved/60129542153)
<div style={{textAlign: 'center'}}>
<a href={"https://gh.atlasgo.cloud/explore/saved/60129542153"} style={{fontSize: 12}}>ERD was generated by Atlas</a>
</div>
</TabItem>
</Tabs>
<Tabs
defaultValue="user"
values={[
{label: 'User', value: 'user'},
{label: 'Friendship', value: 'friendship'},
]}>
<TabItem value="user">
```go title="ent/schema/user.go" {18}
// 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),
}
}
```
</TabItem>
<TabItem value="friendship">
```go title="ent/schema/friendship.go" {11-12}
// Friendship holds the edge schema definition of the Friendship relationship.
type Friendship struct {
ent.Schema
}
// Fields of the Friendship.
func (Friendship) Fields() []ent.Field {
return []ent.Field{
field.Time("created_at").
Default(time.Now),
field.Int("user_id"),
field.Int("friend_id"),
}
}
// Edges of the Friendship.
func (Friendship) Edges() []ent.Edge {
return []ent.Edge{
edge.To("user", User.Type).
Required().
Unique().
Field("user_id"),
edge.To("friend", User.Type).
Required().
Unique().
Field("friend_id"),
}
}
```
</TabItem>
</Tabs>
:::info
- Similar to entity schemas, the `ID` field is automatically generated for edge schemas if not stated otherwise.
- Edge schemas cannot be used by more than one relationship.
- The `user_id` and `friend_id` edge-fields are **required** in the edge schema as they compose the relationship.
:::
#### User Likes Example
In the following example, we demonstrate how to model a system where users can "like" tweets, and a timestamp of when
the tweet was "liked" is stored in the database. This is a way to store additional fields on the edge.
<Tabs
defaultValue="user"
values={[
{label: 'User', value: 'user'},
{label: 'Tweet', value: 'tweet'},
{label: 'Like', value: 'like'},
]}>
<TabItem value="user">
```go title="ent/schema/user.go" {18}
// 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),
}
}
```
</TabItem>
<TabItem value="tweet">
```go title="ent/schema/tweet.go" {18}
// Tweet holds the schema definition for the Tweet entity.
type Tweet struct {
ent.Schema
}
// Fields of the Tweet.
func (Tweet) Fields() []ent.Field {
return []ent.Field{
field.Text("text"),
}
}
// Edges of the Tweet.
func (Tweet) Edges() []ent.Edge {
return []ent.Edge{
edge.From("liked_users", User.Type).
Ref("liked_tweets").
Through("likes", Like.Type),
}
}
```
</TabItem>
<TabItem value="like">
```go title="ent/schema/like.go" {8,17-18}
// Like holds the edge schema definition for the Like edge.
type Like struct {
ent.Schema
}
func (Like) Annotations() []schema.Annotation {
return []schema.Annotation{
field.ID("user_id", "tweet_id"),
}
}
// Fields of the Like.
func (Like) Fields() []ent.Field {
return []ent.Field{
field.Time("liked_at").
Default(time.Now),
field.Int("user_id"),
field.Int("tweet_id"),
}
}
// Edges of the Like.
func (Like) Edges() []ent.Edge {
return []ent.Edge{
edge.To("user", User.Type).
Unique().
Required().
Field("user_id"),
edge.To("tweet", Tweet.Type).
Unique().
Required().
Field("tweet_id"),
}
}
```
</TabItem>
</Tabs>
:::info
In the example above, the `field.ID` annotation is used to tell Ent that the edge schema identifier is a
composite primary-key of the two edge-fields, `user_id` and `tweet_id`. Therefore, the `ID` field will
not be generated for the `Like` struct along with any of its builder methods. e.g. `Get`, `OnlyID`, etc.
:::
#### Usage Of Edge Schema In Other Edge Types
In some cases, users want to store O2M/M2O or O2O relationships in a separate table (i.e. join table) in order to
simplify future migrations in case the edge type was changed. For example, wanting to change a O2M/M2O edge to M2M by
dropping a unique constraint instead of migrating foreign-key values to a new table.
In the following example, we present a model where users can "author" tweets with the constraint that a tweet can be
written by only one user. Unlike regular O2M/M2O edges, by using an edge schema, we enforce this constraint on the join
table using a unique index on the `tweet_id` column. This constraint may be dropped in the future to allow multiple
users to participate in the "authoring" of a tweet. Hence, changing the edge type to M2M without migrating the data to
a new table.
<Tabs
defaultValue="user"
values={[
{label: 'User', value: 'user'},
{label: 'Tweet', value: 'tweet'},
{label: 'UserTweet', value: 'usertweet'},
]}>
<TabItem value="user">
```go title="ent/schema/user.go" {18}
// 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),
}
}
```
</TabItem>
<TabItem value="tweet">
```go title="ent/schema/tweet.go" {18}
// Tweet holds the schema definition for the Tweet entity.
type Tweet struct {
ent.Schema
}
// Fields of the Tweet.
func (Tweet) Fields() []ent.Field {
return []ent.Field{
field.Text("text"),
}
}
// Edges of the Tweet.
func (Tweet) Edges() []ent.Edge {
return []ent.Edge{
edge.From("user", User.Type).
Ref("tweets").
Through("tweet_user", UserTweet.Type).
Comment("The uniqueness of the author is enforced on the edge schema"),
}
}
```
</TabItem>
<TabItem value="usertweet">
```go title="ent/schema/usertweet.go" {33-34}
// UserTweet holds the schema definition for the UserTweet entity.
type UserTweet struct {
ent.Schema
}
// Fields of the UserTweet.
func (UserTweet) Fields() []ent.Field {
return []ent.Field{
field.Time("created_at").
Default(time.Now),
field.Int("user_id"),
field.Int("tweet_id"),
}
}
// Edges of the UserTweet.
func (UserTweet) Edges() []ent.Edge {
return []ent.Edge{
edge.To("user", User.Type).
Unique().
Required().
Field("user_id"),
edge.To("tweet", Tweet.Type).
Unique().
Required().
Field("tweet_id"),
}
}
// Indexes of the UserTweet.
func (UserTweet) Indexes() []ent.Index {
return []ent.Index{
index.Fields("tweet_id").
Unique(),
}
}
```
</TabItem>
</Tabs>
## Required
Edges can be defined as required in the entity creation using the `Required` method on the builder.
```go {7}
// Edges of the Card.
func (Card) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("card").
Unique().
Required(),
}
}
```
If the example above, a card entity cannot be created without its owner.
:::info
Note that, starting with [v0.10](https://github.com/ent/ent/releases/tag/v0.10.0), foreign key columns are created
as `NOT NULL` in the database for required edges that are not [self-reference](#o2m-same-type). In order to migrate
existing foreign key columns, use the [Atlas Migration](migrate.md#atlas-integration) option.
:::
## Immutable
Immutable edges are edges that can be set or added only in the creation of the entity.
i.e., no setters will be generated for the update builders of the entity.
```go {8}
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("tenant", Tenant.Type).
Field("tenant_id").
Unique().
Required().
Immutable(),
}
}
```
## StorageKey
By default, Ent configures edge storage-keys by the edge-owner (the schema that holds the `edge.To`), and not the by
back-reference (`edge.From`). This is because back-references are optional and can be removed.
In order to use custom storage configuration for edges, use the `StorageKey` method as follows:
```go
// 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")
),
}
}
```
## Struct Tags
Custom struct tags can be added to the generated entities using the `StructTag`
method. Note that if this option was not provided, or provided and did not
contain the `json` tag, the default `json` tag will be added with the field name.
```go
// 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"`),
}
}
```
## Indexes
Indexes can be defined on multi fields and some types of edges as well.
However, you should note, that this is currently an SQL-only feature.
Read more about this in the [Indexes](schema-indexes.md) section.
## Comments
A comment can be added to the edge using the `.Comment()` method. This comment
appears before the edge in the generated entity code. Newlines are supported
using the `\n` escape sequence.
```go
// 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.")
}
}
```
## Annotations
`Annotations` is used to attach arbitrary metadata to the edge object in code generation.
Template extensions can retrieve this metadata and use it inside their templates.
Note that the metadata object must be serializable to a JSON raw value (e.g. struct, map or slice).
```go
// 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()),
}
}
```
Read more about annotations and their usage in templates in the [template doc](templates.md#annotations).
## Naming Convention
By convention edge names should use `snake_case`. The corresponding struct fields generated by `ent` will follow the Go convention
of using `PascalCase`. In cases where `PascalCase` is desired, you can do so with the `StorageKey` or `StructTag` methods.