Skip to content

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 ObjectID field 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:

goodm.Populate(ctx, post, goodm.Refs{"author": author}, goodm.PopulateOptions{
    DB: otherDB,
})

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: Populate makes one query per ref field on a single model. For slices of models, always prefer BatchPopulate to avoid N+1 queries.