Skip to main content

Shared tables and views

A model usually has a table to itself. These are the cases where it doesn't.

One table, several models​

Go has no inheritance, so each model is its own plain struct, and a discriminator column says which rows belong to which. Inserts set it for you, and each model's queries only see its own rows.

type Dog struct {
ID int64
Kind string
Name string
Barks *int
}

func (Dog) Configure(b *ant.ModelBuilder[Dog]) {
b.Table("animals")
b.Discriminator(&DG.Kind, "dog")
}

type Cat struct {
ID int64
Kind string
Name string
Lives *int
}

func (Cat) Configure(b *ant.ModelBuilder[Cat]) {
b.Table("animals")
b.Discriminator(&CT.Kind, "cat")
}

var (
DG = ant.Fields[Dog]()
CT = ant.Fields[Cat]()
)

Migrations see one table, and a column only one model declares allows NULL.

Models can also share a table with no discriminator, which is table splitting: a small model for the common read, and a full one for the rest.

type UserName struct {
ID int64
Name string
}

func (UserName) Configure(b *ant.ModelBuilder[UserName]) {
b.Table("users")
}

For a separate table per type, embed a shared struct in each model. That's plain Go, and no ant feature is involved.

Views​

b.View maps a model to a view. It reads like a table, and refuses every write with ant.ErrReadOnly. Define the view itself in migrations/code.

type ActiveUser struct {
ID int64
Name string
}

func (ActiveUser) Configure(b *ant.ModelBuilder[ActiveUser]) {
b.View("active_users")
}

Schemas​

b.Schema places a table in a database schema. Migrations create the schema and qualify everything.

func (Invoice) Configure(b *ant.ModelBuilder[Invoice]) {
b.Schema("billing")
}