メインコンテンツへスキップする

エッジ

簡単な概要#

エッジとは、エンティティの関係 (または関連) です。 例えば、ユーザーのペットやグループのユーザーです。

er-group-users

上記の例では、エッジを使用して宣言された2つのリレーションを見ることができます。 それでは見ていきましょう。

1. エッジ pets / owner; user の pets と pet の owner -

ent/schema/user.go

package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
)
// User schema.
type User struct {
ent.Schema
}
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
// ...
}
}
// Edges of the user.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type),
}
}

ent/schema/pet.go

package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
)
// Pet holds the schema definition for the Pet entity.
type Pet struct {
ent.Schema
}
// Fields of the Pet.
func (Pet) Fields() []ent.Field {
return []ent.Field{
// ...
}
}
// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("pets").
Unique(),
}
}

ご覧の様に User エンティティは pets を 複数持つ 。しかし Pet エンティティは owner を 1つだけ持つ
。 リレーションの定義では、pets エッジは O2M (one-to-many) mの関係であり、owner エッジは M2O (many-to-one) の関係です。

Userスキーマはedge.Toを使用しているため、pets/owner関係を所有しており、Petスキーマはedge.FromRefメソッドを使用して宣言された後方参照を持っています。

Refメソッドは、1つのスキーマから別のスキーマへの複数の参照が存在する可能性があるため、Userスキーマのどのエッジを参照しているのかを記述します。

エッジ/関連の多重度は、Uniqueメソッドを使用して制御できます。これについては、以下で詳しく説明します。

2. users / groupsエッジ;グループのユーザーとユーザーのグループ

ent/schema/group.go

package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
)
// Group schema.
type Group struct {
ent.Schema
}
// Fields of the group.
func (Group) Fields() []ent.Field {
return []ent.Field{
// ...
}
}
// Edges of the group.
func (Group) Edges() []ent.Edge {
return []ent.Edge{
edge.To("users", User.Type),
}
}

ent/schema/user.go

package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
)
// User schema.
type User struct {
ent.Schema
}
// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
// ...
}
}
// Edges of the user.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.From("groups", Group.Type).
Ref("users"),
// "pets" declared in the example above.
edge.To("pets", Pet.Type),
}
}

ご覧の通り、Groupエンティティは多くのuserを持つことができ、Userエンティティは多くのgroupを持つことができます。
関連の定義では、usersエッジはM2M(多対多)の関連であり、groupsエッジもM2M(多対多)の関連です。

To と From#

edge.Toedge.Fromはエッジ/関連を作るための2つのビルダーです。

関連の後方参照のみを与えるedge.Fromビルダーを使用するのとは異なり、edge.Toビルダーを使用してエッジを定義したスキーマは関連を持ちます。

エッジを使用してさまざまな関連を定義する方法を示すいくつかの例を見てみましょう。

関連#

O2O 2つの異なる型の場合#

er-user-card

この例では、ユーザーが持つクレジットカードは1つだけであり、カードの所有者も1人だけです。

Userスキーマではcardという名前でedge.Toを定義し,ます。Cardスキーマではownerという名前のedge.FromでUserへの後方参照を定義します。

ent/schema/user.go

// Edges of the user.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("card", Card.Type).
Unique(),
}
}

ent/schema/card.go

// Edges of the Card.
func (Card) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("card").
Unique().
// ビルダーに "Required "メソッドを追加して
// エンティティ作成にこのエッジを必須にします。
// よって、 カードはその所有者なしでは作成できません。
Required(),
}
}

これらのエッジを操作するためのAPIは次のとおりです:

func Do(ctx context.Context, client *ent.Client) error {
a8m, err := client.User.
Create().
SetAge(30).
SetName("Mashraki").
Save(ctx)
if err != nil {
return fmt.Errorf("creating user: %w", err)
}
log.Println("user:", a8m)
card1, err := client.Card.
Create().
SetOwner(a8m).
SetNumber("1020").
SetExpired(time.Now().Add(time.Minute)).
Save(ctx)
if err != nil {
return fmt.Errorf("creating card: %w", err)
}
log.Println("card:", card1)
// ユーザーのカードのみを返します。
// 1枚しかないことを想定しています。
card2, err := a8m.QueryCard().Only(ctx)
if err != nil {
return fmt.Errorf("querying card: %w", err)
}
log.Println("card:", card2)
// カードエンティティは、その所有者をクエリできます
// 後方参照を使います 
owner, err := card2.QueryOwner().Only(ctx)
if err != nil {
return fmt.Errorf("querying owner: %w", err)
}
log.Println("owner:", owner)
return nil
}

完全な例は GitHub にあります。

O2O 同じ型の場合#

er-linked-list

この連結リストの例では、 next/prevという再帰的な関連 があります。 リスト内の各ノードは たったひとつの next ノードを所有できます。 ノードAが(nextを使って) ノードBを取得できる場合、ノードBは prevを使ってノードAを取得できます。 (これが後方参照エッジです)

ent/schema/node.go

// Edges of the Node.
func (Node) Edges() []ent.Edge {
return []ent.Edge{
edge.To("next", Node.Type).
Unique().
From("prev").
Unique(),
}
}

このように、同じ型への関連の場合、エッジとその参照を同じビルダーで宣言することができます。

func (Node) Edges() []ent.Edge {
return []ent.Edge{
+ edge.To("next", Node.Type).
+ Unique().
+ From("prev").
+ Unique(),
- edge.To("next", Node.Type).
- Unique(),
- edge.From("prev", Node.Type).
- Ref("next).
- Unique(),
}
}

これらのエッジを操作するためのAPIは次のとおりです:

func Do(ctx context.Context, client *ent.Client) error {
head, err := client.Node.
Create().
SetValue(1).
Save(ctx)
if err != nil {
return fmt.Errorf("creating the head: %w", err)
}
curr := head
// 次のような連結リストを生成します: 1<->2<->3<->4<->5.
for i := 0; i < 4; i++ {
curr, err = client.Node.
Create().
SetValue(curr.Value + 1).
SetPrev(curr).
Save(ctx)
if err != nil {
return err
}
}
// リストをループして出力します。 エラーが発生した場合、`FirstX`はpanicになります。
for curr = head; curr != nil; curr = curr.QueryNext().FirstX(ctx) {
fmt.Printf("%d ", curr.Value)
}
// Output: 1 2 3 4 5
// 連結リストを円形にしましょう:
// リストの最後の要素は、"next"を持っていません
tail, err := client.Node.
Query().
Where(node.Not(node.HasNext())).
Only(ctx)
if err != nil {
return fmt.Errorf("getting the tail of the list: %v", tail)
}
tail, err = tail.Update().SetNext(head).Save(ctx)
if err != nil {
return err
}
// 変更が実際に適用されたことを確認します:
prev, err := head.QueryPrev().Only(ctx)
if err != nil {
return fmt.Errorf("getting head's prev: %w", err)
}
fmt.Printf("\n%v", prev.Value == tail.Value)
// Output: true
return nil
}

完全な例は GitHubにあります。

O2O 双方向#

er-user-spouse

このユーザーの配偶者の例では、 spouse という名の 対象的なO2O関連があります。 各ユーザーは 配偶者をひとりだけ持つことができます。 ユーザーAが( spouseにより) ユーザーBを配偶者に設定すると、ユーザーBも spouse エッジを使用して配偶者を得られます。

双方向エッジの場合、所有者/非所有者は存在しないことに注意してください。

ent/schema/user.go

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("spouse", User.Type).
Unique(),
}
}

このエッジを操作するためのAPIは次のとおりです:

func Do(ctx context.Context, client *ent.Client) error {
a8m, err := client.User.
Create().
SetAge(30).
SetName("a8m").
Save(ctx)
if err != nil {
return fmt.Errorf("creating user: %w", err)
}
nati, err := client.User.
Create().
SetAge(28).
SetName("nati").
SetSpouse(a8m).
Save(ctx)
if err != nil {
return fmt.Errorf("creating user: %w", err)
}
// Query the spouse edge.
// `Only`とは違い、`OnlyX`はエラーが発生するとpanicになります
spouse := nati.QuerySpouse().OnlyX(ctx)
fmt.Println(spouse.Name)
// Output: a8m
spouse = a8m.QuerySpouse().OnlyX(ctx)
fmt.Println(spouse.Name)
// Output: nati
// 配偶者を持つユーザーの数を照会します
// `Count` と異なり、`CountX` はエラーが発生するとパニックになります。
count := client.User.
Query().
Where(user.HasSpouse()).
CountX(ctx)
fmt.Println(count)
// Output: 2
// a8mを配偶者に持つユーザーを取得します。
spouse = client.User.
Query().
Where(user.HasSpouseWith(user.Name("a8m"))).
OnlyX(ctx)
fmt.Println(spouse.Name)
// Output: nati
return nil
}

Edge Field オプションを使用して、外部キーの列をエンティティフィールドとして設定して公開することができます。

// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("spouse_id").
Optional(),
}
}
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("spouse", User.Type).
Unique().
Field("spouse_id"),
}
}

完全なサンプルは、GitHubにあります。

O2M 2つの異なる型の場合#

er-user-pets

このuser-petsの例では、userとそのpetsの間にO2Mの関連があります。 各userは多くのpetを持ち、petは1人のみオーナーを持っています。 User Aがpetsエッジを使ってpet Bを追加した場合、Bはownerエッジ(後方参照エッジ)を使ってそのオーナーを取得できます。

この関連は,Petスキーマの視点からは,M2O(多対一) でもあることに注目してください。

ent/schema/user.go

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type),
}
}

ent/schema/pet.go

// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("pets").
Unique(),
}
}

これらのエッジを操作するためのAPIは次のとおりです:

func Do(ctx context.Context, client *ent.Client) error {
// petを2匹作成します
pedro, err := client.Pet.
Create().
SetName("pedro").
Save(ctx)
if err != nil {
return fmt.Errorf("creating pet: %w", err)
}
lola, err := client.Pet.
Create().
SetName("lola").
Save(ctx)
if err != nil {
return fmt.Errorf("creating pet: %w", err)
}
// userを作成し、先ほど作成したpetを加えます
a8m, err := client.User.
Create().
SetAge(30).
SetName("a8m").
AddPets(pedro, lola).
Save(ctx)
if err != nil {
return fmt.Errorf("creating user: %w", err)
}
fmt.Println("User created:", a8m)
// 出力: User(id=1, age=30, name=a8m)
// オーナーを取得します。 // `Only`とは違い、`OnlyX`はエラーが発生するとpanicになります
owner := pedro.QueryOwner().OnlyX(ctx)
fmt.Println(owner.Name)
// 出力: a8m
// サブグラフを走査します // `Count` と異なり、`CountX` はエラーが発生するとpanicになります。
count := pedro.
QueryOwner(). // a8m
QueryPets(). // pedro, lola
CountX(ctx) // count
fmt.Println(count)
// 出力: 2
return nil
}

なお、以下のようにエッジフィールドオプションを使って、外部キーカラムをエンティティフィールドとして設定し、公開することもできます。

// Fields of the Pet.
func (Pet) Fields() []ent.Field {
return []ent.Field{
field.Int("owner_id").
Optional(),
}
}
// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("pets").
Unique().
Field("owner_id"),
}
}

完全な例は GitHub にあります。

O2M 同じ型の場合#

er-tree

この例では、ツリーのノードとその子(またはその親) の間に再帰的なO2M関係があります。
ツリーの各ノードは、子を多く持ち、親を一つだけ持ちます。 ノード A が B を子要素に追加する場合、 B は owner エッジを使用してBの所有者を取得できます。

ent/schema/node.go

// Edges of the Node.
func (Node) Edges() []ent.Edge {
return []ent.Edge{
edge.To("children", Node.Type).
From("parent").
Unique(),
}
}

このように、同じ型への関連の場合、エッジとその参照を同じビルダーで宣言することができます。

func (Node) Edges() []ent.Edge {
return []ent.Edge{
+ edge.To("children", Node.Type).
+ From("parent").
+ Unique(),
- edge.To("children", Node.Type),
- edge.From("parent", Node.Type).
- Ref("children").
- Unique(),
}
}

これらのエッジを操作するためのAPIは次のとおりです:

func Do(ctx context.Context, client *ent.Client) error {
root, err := client.Node.
Create().
SetValue(2).
Save(ctx)
if err != nil {
return fmt.Errorf("creating the root: %w", err)
}
// ツリーにノードを追加します:
//
// 2
// / \
// 1 4
// / \
// 3 5
//
// `Save`と異なり、`SaveX`はエラーが起きた時panicになります
n1 := client.Node.
Create().
SetValue(1).
SetParent(root).
SaveX(ctx)
n4 := client.Node.
Create().
SetValue(4).
SetParent(root).
SaveX(ctx)
n3 := client.Node.
Create().
SetValue(3).
SetParent(n4).
SaveX(ctx)
n5 := client.Node.
Create().
SetValue(5).
SetParent(n4).
SaveX(ctx)
fmt.Println("Tree leafs", []int{n1.Value, n3.Value, n5.Value})
// 出力: Tree leafs [1 3 5]
// すべての葉ノードを取得します (子を持たないノードのことです)
// `Int` と異なり、`IntX` はエラーが発生するとパニックになります。
ints := client.Node.
Query(). // すべてのノード
Where(node.Not(node.HasChildren())). // 葉ノードのみ
Order(ent.Asc(node.FieldValue)). // `value`フィールドで並び替え
GroupBy(node.FieldValue). // 値フィールドのみ抽出する
IntsX(ctx)
fmt.Println(ints)
// Output: [1 3 5]
// 根ノードを取得する (親を持たないノードのこと)
// `Only`とは違い、`OnlyX`はエラーが発生するとpanicになります
orphan := client.Node.
Query().
Where(node.Not(node.HasParent())).
OnlyX(ctx)
fmt.Println(orphan)
// 出力: Node(id=1, value=2)
return nil
}

Edge Field オプションを使用して、外部キーの列をエンティティフィールドとして設定して公開することができます。

// Fields of the Node.
func (Node) Fields() []ent.Field {
return []ent.Field{
field.Int("parent_id").
Optional(),
}
}
// Edges of the Node.
func (Node) Edges() []ent.Edge {
return []ent.Edge{
edge.To("children", Node.Type).
From("parent").
Unique().
Field("parent_id"),
}
}

完全な例は GitHubにあります

M2M 2つの異なる型の場合#

er-user-groups

このグループとユーザーの例では、グループとそのユーザーの間にM2Mの関連があります。 それぞれのグループ には多くの ユーザーが参加し、各ユーザーは 多くの グループに参加できます。

ent/schema/group.go

// Edges of the Group.
func (Group) Edges() []ent.Edge {
return []ent.Edge{
edge.To("users", User.Type),
}
}

ent/schema/user.go

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.From("groups", Group.Type).
Ref("users"),
}
}

これらのエッジを操作するためのAPIは次のとおりです:

func Do(ctx context.Context, client *ent.Client) error {
// `Save`とは違い、`SaveX`はエラーが起きた場合panicになります
hub := client.Group.
Create().
SetName("GitHub").
SaveX(ctx)
lab := client.Group.
Create().
SetName("GitLab").
SaveX(ctx)
a8m := client.User.
Create().
SetAge(30).
SetName("a8m").
AddGroups(hub, lab).
SaveX(ctx)
nati := client.User.
Create().
SetAge(28).
SetName("nati").
AddGroups(hub).
SaveX(ctx)
// エッジをクエリします。
groups, err := a8m.
QueryGroups().
All(ctx)
if err != nil {
return fmt.Errorf("querying a8m groups: %w", err)
}
fmt.Println(groups)
// 出力: [Group(id=1, name=GitHub) Group(id=2, name=GitLab)]
groups, err = nati.
QueryGroups().
All(ctx)
if err != nil {
return fmt.Errorf("querying nati groups: %w", err)
}
fmt.Println(groups)
// 出力: [Group(id=1, name=GitHub)]
// グラフを走査します
users, err := a8m.
QueryGroups(). // [hub, lab]
Where(group.Not(group.HasUsersWith(user.Name("nati")))). // [lab]
QueryUsers(). // [a8m]
QueryGroups(). // [hub, lab]
QueryUsers(). // [a8m, nati]
All(ctx)
if err != nil {
return fmt.Errorf("traversing the graph: %w", err)
}
fmt.Println(users)
// 出力: [User(id=1, age=30, name=a8m) User(id=2, age=28, name=nati)]
return nil
}

完全な例は GitHub にあります。

M2M 同じ型の場合#

er-following-followers

このフォロイーとフォロワーの例では、ユーザーとフォロワーの間にM2Mの関連があります。 それぞれのユーザー は、 多くの ユーザーをフォローし、 多くの フォロワーを持つことができます。

ent/schema/user.go

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("following", User.Type).
From("followers"),
}
}

このように、同じ型への関連の場合、エッジとその参照を同じビルダーで宣言することができます。

func (User) Edges() []ent.Edge {
return []ent.Edge{
+ edge.To("following", User.Type).
+ From("followers"),
- edge.To("following", User.Type),
- edge.From("followers", User.Type).
- Ref("following"),
}
}

これらのエッジを操作するためのAPIは次のとおりです:

func Do(ctx context.Context, client *ent.Client) error {
// `Save`とは違い、`SaveX`はエラーが起きた場合panicになります
a8m := client.User.
Create().
SetAge(30).
SetName("a8m").
SaveX(ctx)
nati := client.User.
Create().
SetAge(28).
SetName("nati").
AddFollowers(a8m).
SaveX(ctx)
// フォロイーとフォロワーの取得:
flw := a8m.QueryFollowing().AllX(ctx)
fmt.Println(flw)
// 出力: [User(id=2, age=28, name=nati)]
flr := a8m.QueryFollowers().AllX(ctx)
fmt.Println(flr)
// 出力: []
flw = nati.QueryFollowing().AllX(ctx)
fmt.Println(flw)
// 出力: []
flr = nati.QueryFollowers().AllX(ctx)
fmt.Println(flr)
// 出力: [User(id=1, age=30, name=a8m)]
// グラフの走査:
ages := nati.
QueryFollowers(). // [a8m]
QueryFollowing(). // [nati]
GroupBy(user.FieldAge). // [28]
IntsX(ctx)
fmt.Println(ages)
// 出力: [28]
names := client.User.
Query().
Where(user.Not(user.HasFollowers())).
GroupBy(user.FieldName).
StringsX(ctx)
fmt.Println(names)
// 出力: [a8m]
return nil
}

完全な例は GitHub にあります。

M2M 双方向#

er-user-friends

このユーザ-フレンドの例では、friendsという対称的なM2M関連があります。 各ユーザは友人を多くを持つことができ、 ユーザAがBの友人になると、BもAの友人になります。

双方向エッジの場合、所有者/非所有者は存在しないことに注意してください。

ent/schema/user.go

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("friends", User.Type),
}
}

これらのエッジを操作するためのAPIは次のとおりです:

func Do(ctx context.Context, client *ent.Client) error {
// `Save`とは違い、`SaveX`はエラーが起きた場合panicになります
a8m := client.User.
Create().
SetAge(30).
SetName("a8m").
SaveX(ctx)
nati := client.User.
Create().
SetAge(28).
SetName("nati").
AddFriends(a8m).
SaveX(ctx)
// friendsをクエリします。 // `All`とは違い、`AllX`はエラーが発生するとpanicになります
friends := nati.
QueryFriends().
AllX(ctx)
fmt.Println(friends)
// 出力: [User(id=1, age=30, name=a8m)]
friends = a8m.
QueryFriends().
AllX(ctx)
fmt.Println(friends)
// 出力: [User(id=2, age=28, name=nati)]
// グラフをクエリします
friends = client.User.
Query().
Where(user.HasFriends()).
AllX(ctx)
fmt.Println(friends)
// 出力: [User(id=1, age=30, name=a8m) User(id=2, age=28, name=nati)]
return nil
}

完全な例はGitHubにあります

エッジフィールド#

エッジの Field オプションを使用すると、外部キーをスキーマのフィールドとして公開できます。 このオプションは外部キー(エッジID) を保持するリレーションのみが許可されることに注意してください。 Support for non-foreign-key fields in join tables is in progress (as of September 2021) and can be tracked with this GitHub Issue.

// Fields of the Post.
func (Post) Fields() []ent.Field {
return []ent.Field{
field.Int("author_id").
Optional(),
}
}
// Edges of the Post.
func (Post) Edges() []ent.Edge {
return []ent.Edge{
edge.To("author", User.Type).
// このエッジに "author_id" フィールドをバインドします。
Field("author_id").
Unique(),
}
}

これらのエッジフィールドを操作するためのAPIは次のとおりです:

func Do(ctx context.Context, client *ent.Client) error {
p, err := c.Post.Query().
Where(post.AuthorID(id)).
OnlyX(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println(p.AuthorID) // 外部キー"author"にアクセスします
}

いくつかの例がGitHubにあります

エッジフィールドへの移行#

StorageKey セクションで述べたように、Ent は edge.To でエッジストレージキー (たとえば外部キー) を設定します。 したがって、既存のエッジに(既にデータベース内に列として存在している) フィールドを追加したい場合 以下のように、 StorageKey オプションで設定する必要があります:

// Fields of the Post.
func (Post) Fields() []ent.Field {
return []ent.Field{
+ field.Int("author_id").
+ Optional(),
}
}
// Edges of the Post.
func (Post) Edges() []ent.Edge {
return []ent.Edge{
edge.From("author", User.Type).
+ Field("author_id").
+ StorageKey(edge.Column("post_author")).
Unique(),
}
}

代わりに、このオプションはエッジフィールドで設定することもできます。

// Fields of the Post.
func (Post) Fields() []ent.Field {
return []ent.Field{
+ field.Int("author_id").
+ StorageKey("post_author").
+ Optional(),
}
}

エッジフィールドオプションを使用しないとき、外部キーがどのように命名されるかがわからない場合は、プロジェクトに生成されたスキーマの記述を確認してください:<project>/ent/migrate/schema.go

Required#

ビルダーの Required メソッドを使用して、エンティティの作成時に必須なエッジを定義できます。

// Edges of the Card.
func (Card) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("card").
Unique().
Required(),
}
}

上の例では、Cardエンティティはownerがいない場合、作成できません。

StorageKey#

デフォルトでは、EntはエッジのDBのキーカラムを、後方参照(edge.From) によってではなく、エッジを所有する型(edge.Toを保持するスキーマ) によって構成します。 これは、後方参照はオプションであり、削除できるためです。

エッジのストレージ構成をカスタムするには、以下のようにStorageKeyメソッドを使用します:

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type).
// O2M 関係の "pets" テーブルにカラム名を設定します
StorageKey(edge.Column("owner_id")),
edge.To("cars", Car.Type).
// O2M 関係の外部キー制約のシンボルを設定します。
StorageKey(edge.Symbol("cars_owner_id")),
edge.To("friends", User.Type).
// M2M関連に交差テーブル名とカラム名を設定します。
StorageKey(edge.Table("friends"), edge.Columns("user_id", "friend_id")),
edge.To("groups", Group.Type).
// M2M関連に交差テーブル名とカラム名、
// 外部キー制約のシンボルを設定します
StorageKey(
edge.Table("groups"),
edge.Columns("user_id", "group_id"),
edge.Symbols("groups_id1", "groups_id2")
),
}
}

StructTag#

カスタムされた構造体タグは、StructTagメソッドを使用して、生成されるエンティティに追加できます。 このオプションが指定されていない場合か、指定していてもjsonタグが含まれていない場合は、デフォルトのjsonタグがフィールド名とともに追加されることに注意してください。

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type).
// O2M関連のデフォルトのjsonタグ "pets "を "owner "で上書きする。
StructTag(`json:"owner"`),
}
}

インデックス#

インデックスは、マルチフィールドや一部のタイプのエッジにも定義できます。 ただし、これは現在のところSQLのみの機能であることに注意してください。

詳しくはインデックスのセクションをご覧ください。

アノテーション#

Annotationsは、コード生成時にエッジオブジェクトへ任意のメタデータを付加するために使用されます。 テンプレート拡張は、このメタデータを取得して、テンプレート内で使用することができます。

なお、メタデータオブジェクトは、JSONのraw value(struct、map、sliceなど)にシリアライズ可能である必要があります。

// Pet schema.
type Pet struct {
ent.Schema
}
// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Field{
edge.To("owner", User.Type).
Ref("pets").
Unique().
Annotations(entgql.Annotation{
OrderField: "OWNER",
}),
}
}

アノテーションの詳細とその使用法については、 テンプレート ドキュメント を参照してください。

命名規則#

規約として、エッジ名はsnake_caseを使用します。 entで生成される対応する構造体フィールドは、PascalCaseを使用するGoの規約に従います。 PascalCaseが必要な場合は、StorageKeyまたはStructTagメソッドで行うことができます。