Skip to content
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

ent: initial support for edge schemas #2560

Merged
merged 1 commit into from May 25, 2022
Merged

ent: initial support for edge schemas #2560

merged 1 commit into from May 25, 2022

Conversation

a8m
Copy link
Member

@a8m a8m commented May 24, 2022

Edge Schema

The Through option allows setting intermediate entity schemas for edges of type M2M. We name such schemas "edge schemas".
Using this option, users can expose relationships in their API, store additional fields on their edges/relationships,
apply CRUD operations, and set hooks and privacy policy on them.

User Friendships Example

In the following example, we demonstrate how to model the friendship between two users using edge schema with the two
required fields of the relationship (user_id and friend_id) and an additional field named created_at that its value
is automatically set on creation.

er_edgeschema_bidi

// 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),
	}
}
// 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"),
	}
}

Notes

  • Similar to entity schemas, the ID field is automatically generated for edge schemas if not stated otherwise.
  • Edge schema cannot be used by more than one relationship (an edge and its back-reference).
  • The user_id and friend_id edge-fields are required in the edge schema as they are composing the relationship.:::

User Likes Example

In the following example, we demonstrate how to model a system where users can "like" tweets, and the timestamp of when
exactly the "like" has happened is stored in the database. i.e. store additional fields on the edge.

// 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),
	}
}
// 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),
	}
}
// 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"),
	}
}

Notes

Since the field.ID annotation is used for configuring a composite identifier (i.e. primary key) for the edge schema,
the ID field will not be generated along with any of its builder methods. e.g. Get, OnlyID, etc.

@a8m a8m force-pushed the edgeschema branch 11 times, most recently from 93eea5d to 363542a Compare May 24, 2022 13:25
schema/field/annotation.go Outdated Show resolved Hide resolved
@a8m a8m merged commit e1c5277 into master May 25, 2022
@a8m a8m deleted the edgeschema branch May 25, 2022 12:46
@fgm
Copy link
Contributor

fgm commented May 26, 2022

That looks really convenient. Just one small thing: it seems the Fields() could be implied since they are listed in the Edge(), so they could be created by default if not defined, making the definition more compact.

@a8m
Copy link
Member Author

a8m commented May 26, 2022

Thanks for the feedback @fgm. For now, I decided to go with the "explicit way" as mentioned in #1949 (comment). Let's give this feature some time, get some feedback on it, and may improve it in the near future.

Appreciate your help in #1949 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants