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:
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
:
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:
package schemast
// Mutator changes a Context.
type Mutator interface {
Mutate(ctx *Context) error
}
Currently, only a single type of schemast.Mutator
is implemented, UpsertSchema
:
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:
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
:
// 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
}
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:
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:
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) 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.
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.