ent icon indicating copy to clipboard operation
ent copied to clipboard

entgql: How to get Edge Schema fields in GraphQL?

Open nomikura opened this issue 3 years ago • 2 comments

I defined three schemas: User, Tweet, and Like. Like is Edge Schema.

I want to send the following query using GraphQL. However, There is no liked_at in the graphql file.

users {
  likedTweets {
    id # ok
    text #ok
    likedAt # not ok
  }
}

Please tell me how to get liked_at.

repository: https://github.com/nomikura/ent-example

Schema

type User struct {
	ent.Schema
}

func (User) Fields() []ent.Field {
	return []ent.Field{
		field.String("name").
			Default("Unknown"),
	}
}

func (User) Edges() []ent.Edge {
	return []ent.Edge{
		edge.To("liked_tweets", Tweet.Type).
			Through("likes", Like.Type),
	}
}

func (User) Annotations() []schema.Annotation {
	return []schema.Annotation{
		entgql.QueryField(),
		entgql.RelayConnection(),
	}
}
type Tweet struct {
	ent.Schema
}

func (Tweet) Fields() []ent.Field {
	return []ent.Field{
		field.Text("text"),
	}
}

func (Tweet) Edges() []ent.Edge {
	return []ent.Edge{
		edge.From("liked_users", User.Type).
			Ref("liked_tweets").
			Through("likes", Like.Type),
	}
}
type Like struct {
	ent.Schema
}

func (Like) Annotations() []schema.Annotation {
	return []schema.Annotation{
		field.ID("user_id", "tweet_id"),
	}
}

func (Like) Fields() []ent.Field {
	return []ent.Field{
		field.Time("liked_at").
			Default(time.Now),
		field.Int("user_id"),
		field.Int("tweet_id"),
	}
}

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

Generated GraphQL file

directive @goField(forceResolver: Boolean, name: String) on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @goModel(model: String, models: [String!]) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION
"""
Define a Relay Cursor type:
https://relay.dev/graphql/connections.htm#sec-Cursor
"""
scalar Cursor
"""
An object with an ID.
Follows the [Relay Global Object Identification Specification](https://relay.dev/graphql/objectidentification.htm)
"""
interface Node @goModel(model: "entdemo/ent.Noder") {
  """The id of the object."""
  id: ID!
}
"""Possible directions in which to order a list of items when provided an `orderBy` argument."""
enum OrderDirection {
  """Specifies an ascending order for a given `orderBy` argument."""
  ASC
  """Specifies a descending order for a given `orderBy` argument."""
  DESC
}
"""
Information about pagination in a connection.
https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo
"""
type PageInfo {
  """When paginating forwards, are there more items?"""
  hasNextPage: Boolean!
  """When paginating backwards, are there more items?"""
  hasPreviousPage: Boolean!
  """When paginating backwards, the cursor to continue."""
  startCursor: Cursor
  """When paginating forwards, the cursor to continue."""
  endCursor: Cursor
}
type Query {
  """Fetches an object given its ID."""
  node(
    """ID of the object."""
    id: ID!
  ): Node
  """Lookup nodes by a list of IDs."""
  nodes(
    """The list of node IDs."""
    ids: [ID!]!
  ): [Node]!
  users(
    """Returns the elements in the list that come after the specified cursor."""
    after: Cursor

    """Returns the first _n_ elements from the list."""
    first: Int

    """Returns the elements in the list that come before the specified cursor."""
    before: Cursor

    """Returns the last _n_ elements from the list."""
    last: Int
  ): UserConnection!
}
type Tweet implements Node {
  id: ID!
  text: String!
  likedUsers: [User!]
}
type User implements Node {
  id: ID!
  name: String!
  likedTweets: [Tweet!]
}
"""A connection to a list of items."""
type UserConnection {
  """A list of edges."""
  edges: [UserEdge]
  """Information to aid in pagination."""
  pageInfo: PageInfo!
  """Identifies the total count of items in the connection."""
  totalCount: Int!
}
"""An edge in a connection."""
type UserEdge {
  """The item at the end of the edge."""
  node: User
  """A cursor for use in pagination."""
  cursor: Cursor!
}
 to a list of items."""
type UserConnection {
  """A list of edges."""
  edges: [UserEdge]
  """Information to aid in pagination."""
  pageInfo: PageInfo!
  """Identifies the total count of items in the connection."""
  totalCount: Int!
}
"""An edge in a connection."""
type UserEdge {
  """The item at the end of the edge."""
  node: User
  """A cursor for use in pagination."""
  cursor: Cursor!
}

nomikura avatar Jan 09 '23 06:01 nomikura

Hey @a8m ✋

Thank you for adding support for Relay Connection in Edge Schema in the previous pull request! (https://github.com/ent/contrib/pull/403)

I've tried it in https://github.com/nomikura/ent-example, but the codegen fails with the following error message.

$ go generate -x ./...
go run -mod=mod entc.go
2023/05/16 11:56:20 running ent codegen: entgql.RelayConnection() must be set on entity "Tweet" in order to define "User"."liked_tweets" as Relay Connection
exit status 1
gen/generate.go:3: running "go": exit status 1

Annotations have been added to the User schema, Tweet schema and Like schema. Could you advise on how to proceed for the codegen to succeed?

schema files: https://github.com/nomikura/ent-example/tree/main/ent/schema User schema:

func (User) Fields() []ent.Field {
	return []ent.Field{
		field.String("name").
			Default("Unknown"),
	}
}

func (User) Edges() []ent.Edge {
	return []ent.Edge{
		edge.To("liked_tweets", Tweet.Type).
			Through("likes", Like.Type).
			Annotations(entgql.RelayConnection()), // add
	}
}

func (User) Annotations() []schema.Annotation {
	return []schema.Annotation{
		entgql.QueryField(),
		entgql.RelayConnection(),
	}
}

Tweet schema:

func (Tweet) Fields() []ent.Field {
	return []ent.Field{
		field.Text("text"),
	}
}

func (Tweet) Edges() []ent.Edge {
	return []ent.Edge{
		edge.From("liked_users", User.Type).
			Ref("liked_tweets").
			Through("likes", Like.Type),
	}
}

func (Tweet) Annotation() []schema.Annotation {
	return []schema.Annotation{
		entgql.RelayConnection(), // add
	}
}

Like schema:

func (Like) Fields() []ent.Field {
	return []ent.Field{
		field.Time("liked_at").
			Default(time.Now),
		field.Int("user_id"),
		field.Int("tweet_id"),
	}
}

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

func (Like) Annotation() []schema.Annotation {
	return []schema.Annotation{
		entgql.RelayConnection(), // add
	}
}

nomikura avatar May 16 '23 03:05 nomikura

Removing the compound ID field.ID("user_id", "tweet_id") and using the autogenerated ID allowed graphql generation to work correctly for me.

gabemeola avatar Jun 24 '25 00:06 gabemeola