-
Notifications
You must be signed in to change notification settings - Fork 974
entc/hooks: initial work for mutations and hooks #371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
140dd95
to
5063f87
Compare
The usage of hooks in the entity schema looks as follows: type User struct {
ent.Schema
}
func (User) Hooks() []ent.Hook {
return []ent.Hook{
func(next ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
// m's concrete type is: *ent.UserMutation.
return next.Mutate(ctx, m)
})
},
// More hooks.
// ...
}
} In runtime, you can add global hooks as follows: client.Use(func(next ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
fmt.Println("mutation start")
defer fmt.Println("mutation end")
return next.Mutate(ctx, m)
})
}) Or, to a specific client: client.User.Use(func(next ent.Mutator) ent.Mutator {
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
fmt.Println("user mutation start")
defer fmt.Println("user mutation end")
return next.Mutate(ctx, m)
})
}) The type Value interface{}
type Mutation interface {
// Op returns the operation name generated by entc.
Op() string
// Type returns the schema type for this mutation.
Type() string
// Fields returns all fields that were changed during
// this mutation. Note that, in order to get all numeric
// fields that were in/decremented, call AddedFields().
Fields() []string
// Field returns the value of a field with the given name.
// The second boolean value indicates that this field was
// not set, or was not define in the schema.
Field(name string) (Value, bool)
// SetField sets the value for the given name. It returns an
// error if the field is not defined in the schema, or if the
// type mismatch the field type.
SetField(name string, value Value) error
// AddedFields returns all numeric fields that were incremented
// or decremented during this mutation.
AddedFields() []string
// AddedField returns the numeric value that was in/decremented
// from a field with the given name. The second value indicates
// that this field was not set, or was not define in the schema.
AddedField(name string) (Value, bool)
// AddField adds the value for the given name. It returns an
// error if the field is not defined in the schema, or if the
// type mismatch the field type.
AddField(name string, value Value) error
// ClearedFields returns all nullable fields that were cleared
// during this mutation.
ClearedFields() []string
// FieldCleared returns a boolean indicates if this field was
// cleared in this mutation.
FieldCleared(name string) bool
// ClearField clears the value for the given name. It returns an
// error if the field is not defined in the schema.
ClearField(name string) error
// ResetField resets all changes in the mutation regarding the
// given field name. It returns an error if the field is not
// defined in the schema.
ResetField(name string) error
// AddedEdges returns all edge names that were set/added in this
// mutation.
AddedEdges() []string
// AddedIDs returns all ids (to other nodes) that were added for
// the given edge name.
AddedIDs(name string) []Value
// RemovedEdges returns all edge names that were removed in this
// mutation.
RemovedEdges() []string
// RemovedIDs returns all ids (to other nodes) that were removed for
// the given edge name.
RemovedIDs(name string) []Value
// ClearedEdges returns all edge names that were cleared in this
// mutation.
ClearedEdges() []string
// EdgeCleared returns a boolean indicates if this edge was
// cleared in this mutation.
EdgeCleared(name string) bool
// ClearEdge clears the value for the given name. It returns an
// error if the edge name is not defined in the schema.
ClearEdge(name string) error
// ResetEdge resets all changes in the mutation regarding the
// given edge name. It returns an error if the edge is not
// defined in the schema.
ResetEdge(name string) error
} The concrete mutation type gives a type safe API for each schema. For example:
When dealing with generic mutations, there are 2 options to read and mutate fields/edges func M(ctx context.Context, m ent.Mutation) (ent.Value, error) {
v, ok := m.Field(user.FieldName) // or, m.Field("name")
if ok {
// ...
}
// An error is returned when field does not exist in the
// schema, or the value type mismatch the field type.
if err := m.SetField(user.FieldName, "boring"); err != nil {
return nil, err
}
// ...
} Use, type assertion: func M(ctx context.Context, m ent.Mutation) (ent.Value, error) {
type NameSetter interface {
Name() (string, bool)
SetName(string)
}
ns, ok := m.(NameSetter)
if !ok {
// ignore.
}
name, ok := ns.Name()
if ok {
// ...
}
ns.SetName("boring")
// ...
} WIP: An client.Card.Use(func(next ent.Mutator) ent.Mutator {
return hook.UserFunc(func(ctx context.Context, m *ent.UserMutation) (ent.Value, error) {
// Do something.
return next.Mutate(ctx, m)
})
}) TODO for next PRs: Add an API for getting the |
This is a WIP and it should be ignored for now.
There are a few things that need to be added before merging it, and additional work that can be added in separate PRs:
generated
ent
package, we can have a circular dependency situation (becauseent.Client
needs to register itsschema.Hooks
, but can't import them). So, we need to decide on one solution here.generated.go
file in the user's schema package, that will register its hooks in the generatedent
package. Then, users will need to empty import it as follows:entclient
package, and request users to use it in order to create a newent.Client
.ent
package. We keep backwards compatibility, and generate those packages only if the user uses hooks in the schema. No changes are needed, if a user registers hooks on runtime (on client creation).Next steps:
hook
(also WIP) for auto type-assertion, or operation filtering. For example: