Skip to main content

Defining models

A model is just a struct. It doesn't need a base type, tags, or generated code.

A model​

type Article struct {
ID int64
Title string
Views int
}

type BlogDB struct {
ant.Context
Articles ant.Table[Article]
}

var A = ant.Fields[Article]()

That maps to a table called articles, with id as a primary key the database generates.

ant.Base​

Embed ant.Base for the three fields most tables want. It's optional, and the fields behave like any others.

type Article struct {
ant.Base
Title string
}

article := &Article{Title: "Hello"}

err := db.Articles.Insert(ctx, article)

fmt.Println(article.ID, article.CreatedAt, article.UpdatedAt) // all filled in

There's nothing special about ant.Base itself: any model with a CreatedAt or UpdatedAt field gets the same timestamps.

Naming conventions​

GoDatabase
type OrderItemtable order_items
UserIDcolumn user_id
HTTPPortcolumn http_port
IDprimary key id
Plurals are simple

ant adds s, or es after s, x, ch, and sh, and turns a consonant and y into ies. That gives categories for Category, but also persons for Person and childs for Child. Use b.Table when that reads wrong.

Overriding names​

A tag renames a column, and Configure renames a table.

type Device struct {
ID int64
Name string `ant:"column=display_name"`
}

func (Device) Configure(b *ant.ModelBuilder[Device]) {
b.Table("hardware")
}

Leaving a field out​

ant:"-" and unexported fields aren't columns, so they're never read, written, or queried.

type Session struct {
ID int64
Token string
Cached string `ant:"-"`
secret string
}

Your own naming strategy​

ant.WithNaming replaces the conventions, which is useful for an existing database with its own style. A nil function keeps the default, and any name you set explicitly stays.

db, err := ant.Open[AppDB](ctx, postgres.Dialect, dsn,
ant.WithNaming(ant.Naming{
Table: func(name string) string { return "tbl_" + name },
Column: func(name string) string { return "col_" + name },
}),
)

ant.WithConvention goes a step further and applies a rule to every model. Configure can still override it.

rule := func(m *ant.ModelConvention) {
for _, f := range m.Fields() {
if f.Type.Kind() == reflect.String {
f.SetLength(100)
}
}
}

db, err := ant.Open[AppDB](ctx, postgres.Dialect, dsn, ant.WithConvention(rule))
Set once per process

Models are reflected once per process, so naming and conventions are global, and they come from the first Open.