snapshot of https://github.com/ent/ent.git (master, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
225
doc/md/generating-ent-schemas.md
Normal file
225
doc/md/generating-ent-schemas.md
Normal file
@@ -0,0 +1,225 @@
|
||||
---
|
||||
id: generating-ent-schemas
|
||||
title: Generating Schemas
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
To facilitate the creation of tooling that generates `ent.Schema`s programmatically, `ent` supports the manipulation of
|
||||
the `schema/` directory using the `entgo.io/contrib/schemast` package.
|
||||
|
||||
## API
|
||||
|
||||
### Loading
|
||||
|
||||
In order to manipulate an existing schema directory we must first load it into a `schemast.Context` object:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"entgo.io/contrib/schemast"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, err := schemast.Load("./ent/schema")
|
||||
if err != nil {
|
||||
log.Fatalf("failed: %v", err)
|
||||
}
|
||||
if ctx.HasType("user") {
|
||||
fmt.Println("schema directory contains a schema named User!")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Printing
|
||||
|
||||
To print back out our context to a target directory, use `schemast.Print`:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"entgo.io/contrib/schemast"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, err := schemast.Load("./ent/schema")
|
||||
if err != nil {
|
||||
log.Fatalf("failed: %v", err)
|
||||
}
|
||||
// A no-op since we did not manipulate the Context at all.
|
||||
if err := schemast.Print("./ent/schema"); err != nil {
|
||||
log.Fatalf("failed: %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mutators
|
||||
|
||||
To mutate the `ent/schema` directory, we can use `schemast.Mutate`, which takes a list of
|
||||
`schemast.Mutator`s to apply to the context:
|
||||
|
||||
```go
|
||||
package schemast
|
||||
|
||||
// Mutator changes a Context.
|
||||
type Mutator interface {
|
||||
Mutate(ctx *Context) error
|
||||
}
|
||||
```
|
||||
|
||||
Currently, only a single type of `schemast.Mutator` is implemented, `UpsertSchema`:
|
||||
|
||||
```go
|
||||
package schemast
|
||||
|
||||
// UpsertSchema implements Mutator. UpsertSchema will add to the Context the type named
|
||||
// Name if not present and rewrite the type's Fields, Edges, Indexes and Annotations methods.
|
||||
type UpsertSchema struct {
|
||||
Name string
|
||||
Fields []ent.Field
|
||||
Edges []ent.Edge
|
||||
Indexes []ent.Index
|
||||
Annotations []schema.Annotation
|
||||
}
|
||||
```
|
||||
|
||||
To use it:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"entgo.io/contrib/schemast"
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/schema/field"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, err := schemast.Load("./ent/schema")
|
||||
if err != nil {
|
||||
log.Fatalf("failed: %v", err)
|
||||
}
|
||||
mutations := []schemast.Mutator{
|
||||
&schemast.UpsertSchema{
|
||||
Name: "User",
|
||||
Fields: []ent.Field{
|
||||
field.String("name"),
|
||||
},
|
||||
},
|
||||
&schemast.UpsertSchema{
|
||||
Name: "Team",
|
||||
Fields: []ent.Field{
|
||||
field.String("name"),
|
||||
},
|
||||
},
|
||||
}
|
||||
err = schemast.Mutate(ctx, mutations...)
|
||||
if err := ctx.Print("./ent/schema"); err != nil {
|
||||
log.Fatalf("failed: %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After running this program, observe two new files exist in the schema directory: `user.go` and `team.go`:
|
||||
|
||||
```go
|
||||
// user.go
|
||||
package schema
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/schema"
|
||||
"entgo.io/ent/schema/field"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (User) Fields() []ent.Field {
|
||||
return []ent.Field{field.String("name")}
|
||||
}
|
||||
func (User) Edges() []ent.Edge {
|
||||
return nil
|
||||
}
|
||||
func (User) Annotations() []schema.Annotation {
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
package schema
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/schema"
|
||||
"entgo.io/ent/schema/field"
|
||||
)
|
||||
|
||||
type Team struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (Team) Fields() []ent.Field {
|
||||
return []ent.Field{field.String("name")}
|
||||
}
|
||||
func (Team) Edges() []ent.Edge {
|
||||
return nil
|
||||
}
|
||||
func (Team) Annotations() []schema.Annotation {
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Working with Edges
|
||||
|
||||
Edges are defined in `ent` this way:
|
||||
|
||||
```go
|
||||
edge.To("edge_name", OtherSchema.Type)
|
||||
```
|
||||
|
||||
This syntax relies on the fact that the `OtherSchema` struct already exists when we define the edge so we can refer to
|
||||
its `Type` method. When we are generating schemas programmatically, obviously we need somehow to describe the edge to
|
||||
the code-generator before the type definitions exist. To do this you can do something like:
|
||||
|
||||
```go
|
||||
type placeholder struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func withType(e ent.Edge, typeName string) ent.Edge {
|
||||
e.Descriptor().Type = typeName
|
||||
return e
|
||||
}
|
||||
|
||||
func newEdgeTo(edgeName, otherType string) ent.Edge {
|
||||
// we pass a placeholder type to the edge constructor:
|
||||
e := edge.To(edgeName, placeholder.Type)
|
||||
// then we override the other type's name directly on the edge descriptor:
|
||||
return withType(e, otherType)
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
The `protoc-gen-ent` ([doc](https://github.com/ent/contrib/tree/master/entproto/cmd/protoc-gen-ent)) is a protoc plugin
|
||||
that programmatically generates `ent.Schema`s from .proto files, it uses the `schemast` to manipulate the
|
||||
target `schema` directory. To see
|
||||
how, [read the source code](https://github.com/ent/contrib/blob/master/entproto/cmd/protoc-gen-ent/main.go#L34).
|
||||
|
||||
## Caveats
|
||||
|
||||
`schemast` is still experimental, APIs are subject to change in the future. In addition, a small portion of
|
||||
the `ent.Field` definition API is unsupported at this point in time, to see a full list of unsupported features see
|
||||
the [source code](https://github.com/ent/contrib/blob/aed7a43a3e54550c1dd9a1a066ce1236b4bae56c/schemast/field.go#L158).
|
||||
|
||||
Reference in New Issue
Block a user