Population¶
Population resolves ref= tagged fields by fetching the referenced documents from their collections. It works like Mongoose's .populate().
Setup¶
Tag an ObjectID field with ref=collection to mark it as a reference. Both single refs and array refs are supported:
type Post struct {
goodm.Model `bson:",inline"`
Title string `bson:"title" goodm:"required"`
AuthorID bson.ObjectID `bson:"author" goodm:"ref=users"`
TagIDs []bson.ObjectID `bson:"tags" goodm:"ref=tags"`
}
type User struct {
goodm.Model `bson:",inline"`
Name string `bson:"name" goodm:"required"`
}
type Tag struct {
goodm.Model `bson:",inline"`
Label string `bson:"label" goodm:"required"`
}
Using Populate¶
After loading a document, call Populate with a Refs map. Keys are bson field names, values are pointers to structs where the referenced documents will be decoded:
// Load the post
post := &Post{}
goodm.FindOne(ctx, bson.D{{Key: "title", Value: "Hello World"}}, post)
// Populate the author
author := &User{}
err := goodm.Populate(ctx, post, goodm.Refs{
"author": author,
})
fmt.Println(author.Name) // "Alice"
Multiple References¶
Populate multiple refs in a single call:
type Post struct {
goodm.Model `bson:",inline"`
Title string `bson:"title" goodm:"required"`
AuthorID bson.ObjectID `bson:"author" goodm:"ref=users"`
CategoryID bson.ObjectID `bson:"category" goodm:"ref=categories"`
}
author := &User{}
category := &Category{}
err := goodm.Populate(ctx, post, goodm.Refs{
"author": author,
"category": category,
})
Array References¶
For []bson.ObjectID fields, pass a pointer to a slice as the target. All referenced documents are fetched in a single $in query:
var tags []Tag
err := goodm.Populate(ctx, post, goodm.Refs{
"tags": &tags,
})
for _, tag := range tags {
fmt.Println(tag.Label)
}
You can mix single and array refs in one call:
author := &User{}
var tags []Tag
err := goodm.Populate(ctx, post, goodm.Refs{
"author": author,
"tags": &tags,
})
Behavior¶
- Zero refs are skipped — if the
ObjectIDfield is zero, the target struct is left untouched. - Empty/nil array refs are skipped — the target slice is left empty.
- Dangling refs (ID points to a nonexistent document) are skipped silently. The target struct remains at its zero value.
- Missing field or no ref tag returns an error immediately.
Options¶
Override the database connection:
Batch Population¶
Use BatchPopulate to resolve a ref field across a slice of models in a single $in query, avoiding N+1 overhead:
var posts []Post
goodm.Find(ctx, bson.D{}, &posts)
var authors []User
err := goodm.BatchPopulate(ctx, posts, "author", &authors)
This collects unique IDs from the author field, fetches all referenced documents in one query, and decodes them into the authors slice. To map authors back to posts, build a lookup map:
authorMap := make(map[bson.ObjectID]*User)
for i := range authors {
authorMap[authors[i].ID] = &authors[i]
}
for _, post := range posts {
author := authorMap[post.AuthorID]
// ...
}
Note:
Populatemakes one query per ref field on a single model. For slices of models, always preferBatchPopulateto avoid N+1 queries.