or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

associations.mdclause.mddatabase-operations.mdhooks.mdindex.mdlogger.mdmigrations.mdquery-building.mdschema.mdtransactions.md
tile.json

associations.mddocs/

Associations

GORM provides comprehensive support for managing relationships between models, including belongs to, has one, has many, and many to many relationships.

Defining Associations

Belongs To

A belongs to association sets up a one-to-one connection with another model, where the declaring model has a foreign key.

type User struct {
    gorm.Model
    Name      string
    CompanyID int
    Company   Company  // Belongs to Company
}

type Company struct {
    gorm.Model
    Name string
}

Customization:

type User struct {
    gorm.Model
    Name      string
    CompanyID int
    Company   Company `gorm:"foreignKey:CompanyID;references:ID"`
}

Has One

A has one association sets up a one-to-one connection with another model, where the other model has a foreign key.

type User struct {
    gorm.Model
    Name    string
    Profile Profile  // Has one Profile
}

type Profile struct {
    gorm.Model
    UserID int    // Foreign key
    Bio    string
}

Customization:

type User struct {
    gorm.Model
    Name    string
    Profile Profile `gorm:"foreignKey:UserID;references:ID"`
}

Has Many

A has many association sets up a one-to-many connection with another model.

type User struct {
    gorm.Model
    Name   string
    Orders []Order  // Has many Orders
}

type Order struct {
    gorm.Model
    UserID int    // Foreign key
    Amount float64
}

Customization:

type User struct {
    gorm.Model
    Name   string
    Orders []Order `gorm:"foreignKey:UserID;references:ID"`
}

Many To Many

A many to many association creates a join table between two models.

type User struct {
    gorm.Model
    Name      string
    Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
    gorm.Model
    Name string
}

// GORM will create a join table named 'user_languages'
// with columns: user_id, language_id

Custom Join Table:

type User struct {
    gorm.Model
    Name      string
    Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
    gorm.Model
    Name string
}

type UserLanguage struct {
    UserID     uint `gorm:"primaryKey"`
    LanguageID uint `gorm:"primaryKey"`
    Proficiency string
    CreatedAt   time.Time
}

// Setup custom join table
db.SetupJoinTable(&User{}, "Languages", &UserLanguage{})

Polymorphic Associations

Polymorphic associations allow a model to belong to more than one type of model on a single association.

type Image struct {
    gorm.Model
    URL          string
    OwnerID      uint
    OwnerType    string  // "User" or "Company"
}

type User struct {
    gorm.Model
    Name   string
    Images []Image `gorm:"polymorphic:Owner;"`
}

type Company struct {
    gorm.Model
    Name   string
    Images []Image `gorm:"polymorphic:Owner;"`
}

Association API

Get an association manager to work with relationships.

func (db *DB) Association(column string) *Association

Finding Associations

Find associated records.

func (association *Association) Find(out interface{}, conds ...interface{}) error

Usage:

var user User
db.First(&user, 1)

// Find all associated orders
var orders []Order
db.Model(&user).Association("Orders").Find(&orders)

// Find with conditions
db.Model(&user).Association("Orders").Find(&orders, "amount > ?", 100)

Appending Associations

Add new associations without replacing existing ones.

func (association *Association) Append(values ...interface{}) error

Usage:

var user User
db.First(&user, 1)

// Append new orders
order1 := Order{Amount: 100}
order2 := Order{Amount: 200}
db.Model(&user).Association("Orders").Append(&order1, &order2)

// Append existing records (many2many)
var languages []Language
db.Find(&languages, []int{1, 2, 3})
db.Model(&user).Association("Languages").Append(&languages)

Replacing Associations

Replace all associations with new ones.

func (association *Association) Replace(values ...interface{}) error

Usage:

var user User
db.First(&user, 1)

// Replace all orders
newOrders := []Order{
    {Amount: 100},
    {Amount: 200},
}
db.Model(&user).Association("Orders").Replace(&newOrders)

// Replace with empty slice to clear all
db.Model(&user).Association("Orders").Replace([]Order{})

Deleting Associations

Delete the relationship between source and arguments, only delete the reference.

func (association *Association) Delete(values ...interface{}) error

Usage:

var user User
db.First(&user, 1)

// Delete specific associations
var order Order
db.First(&order, 1)
db.Model(&user).Association("Orders").Delete(&order)

// Delete multiple
var orders []Order
db.Where("amount < ?", 10).Find(&orders)
db.Model(&user).Association("Orders").Delete(&orders)

// For belongs to and has one/many: sets foreign key to null
// For many2many: deletes join table records

Clearing Associations

Remove all associations.

func (association *Association) Clear() error

Usage:

var user User
db.First(&user, 1)

// Clear all orders
db.Model(&user).Association("Orders").Clear()

// Clear many2many associations
db.Model(&user).Association("Languages").Clear()

Counting Associations

Count the number of associated records.

func (association *Association) Count() (int64, error)

Usage:

var user User
db.First(&user, 1)

// Count associated orders
count, err := db.Model(&user).Association("Orders").Count()
if err != nil {
    // Handle error
}
fmt.Println("Order count:", count)

Eager Loading (Preloading)

Preload associations when querying to avoid N+1 queries.

func (db *DB) Preload(query string, args ...interface{}) *DB

Usage:

// Preload single association
var users []User
db.Preload("Orders").Find(&users)

// Preload with conditions
db.Preload("Orders", "amount > ?", 100).Find(&users)

// Preload nested associations
db.Preload("Orders.Items").Find(&users)
db.Preload("Orders.Items.Product").Find(&users)

// Preload multiple associations
db.Preload("Orders").Preload("Profile").Find(&users)

// Preload all associations
db.Preload(clause.Associations).Find(&users)

// Custom preload query
db.Preload("Orders", func(db *gorm.DB) *gorm.DB {
    return db.Order("orders.amount DESC").Limit(5)
}).Find(&users)

Joining Associations

Use joins to load associations in a single query.

// INNER JOIN with association
var users []User
db.Joins("Company").Find(&users)

// Specify join conditions
db.Joins("LEFT JOIN companies ON companies.id = users.company_id").Find(&users)

// Join with conditions
db.Joins("Company", db.Where(&Company{Name: "Acme Corp"})).Find(&users)

// Join nested associations
db.Joins("Orders.Items").Find(&users)

Creating with Associations

Create with Nested Data

// Create user with profile
user := User{
    Name: "Alice",
    Profile: Profile{
        Bio: "Software Developer",
    },
}
db.Create(&user)

// Create with has many
user := User{
    Name: "Alice",
    Orders: []Order{
        {Amount: 100},
        {Amount: 200},
    },
}
db.Create(&user)

// Create with many2many
user := User{
    Name: "Alice",
    Languages: []Language{
        {Name: "English"},
        {Name: "Spanish"},
    },
}
db.Create(&user)

Skip Auto-Creation

// Skip creating associations
db.Omit("Profile").Create(&user)
db.Omit("Orders").Create(&user)

// Skip all associations
db.Omit(clause.Associations).Create(&user)

Select Fields for Associations

// Create user and only specified fields of associations
db.Select("Name", "Profile").Create(&user)

Updating with Associations

Full Save Associations

Save all fields including associations.

// Update user and all associations
db.Session(&gorm.Session{FullSaveAssociations: true}).Save(&user)

// Or configure globally
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    FullSaveAssociations: true,
})

Update Associations

var user User
db.Preload("Orders").First(&user, 1)

// Modify associations
user.Orders[0].Amount = 150
user.Orders = append(user.Orders, Order{Amount: 300})

// Save with associations
db.Session(&gorm.Session{FullSaveAssociations: true}).Save(&user)

Deleting with Associations

Select Delete

Delete associations when deleting a record.

// Delete user and their orders
db.Select("Orders").Delete(&user)

// Delete multiple associations
db.Select("Orders", "Profile").Delete(&user)

// Delete all associations
db.Select(clause.Associations).Delete(&user)

CASCADE Delete

Configure foreign key constraints for cascade delete.

type User struct {
    gorm.Model
    Name   string
    Orders []Order `gorm:"constraint:OnDelete:CASCADE;"`
}

type Order struct {
    gorm.Model
    UserID int
    Amount float64
}

// When user is deleted, orders will be automatically deleted by database

Association Tags

Configure associations using struct tags.

type User struct {
    gorm.Model

    // Foreign key configuration
    CompanyID int
    Company   Company `gorm:"foreignKey:CompanyID;references:ID"`

    // Constraint configuration
    Profile Profile `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`

    // Many2many configuration
    Languages []Language `gorm:"many2many:user_languages;joinForeignKey:UserID;joinReferences:LanguageID"`

    // Polymorphic configuration
    Images []Image `gorm:"polymorphic:Owner;polymorphicValue:user"`
}

Common Association Tags

  • foreignKey: Specify foreign key column name
  • references: Specify reference column name
  • polymorphic: Specify polymorphic type
  • polymorphicValue: Specify polymorphic value
  • many2many: Specify join table name
  • joinForeignKey: Specify foreign key in join table
  • joinReferences: Specify reference key in join table
  • constraint: Specify foreign key constraint (OnUpdate, OnDelete)

Association Mode

Association mode provides helper methods without needing to reload data.

var user User
db.First(&user, 1)

// Get association mode
association := db.Model(&user).Association("Orders")

// Check if association is loaded
if association.Error != nil {
    // Handle error
}

// Work with association
association.Append(&Order{Amount: 100})
association.Count()

Custom Join Table

For many2many relationships, you can specify a custom join table model.

func (db *DB) SetupJoinTable(model interface{}, field string, joinTable interface{}) error

Usage:

type User struct {
    gorm.Model
    Name      string
    Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
    gorm.Model
    Name string
}

type UserLanguage struct {
    UserID      uint      `gorm:"primaryKey"`
    LanguageID  uint      `gorm:"primaryKey"`
    Proficiency string
    CreatedAt   time.Time
}

// Setup custom join table
err := db.SetupJoinTable(&User{}, "Languages", &UserLanguage{})
if err != nil {
    // Handle error
}

// Now creates and queries will use UserLanguage table with all fields

Composite Foreign Keys

Use composite foreign keys for complex relationships.

type User struct {
    ID         uint
    LocationID uint
    Name       string
}

type Location struct {
    ID   uint
    Name string
}

type Order struct {
    ID         uint
    UserID     uint
    LocationID uint
    User       User `gorm:"foreignKey:UserID,LocationID;references:ID,LocationID"`
}

Self-Referential Associations

Create relationships within the same model.

type User struct {
    gorm.Model
    Name      string
    ManagerID *uint
    Manager   *User  `gorm:"foreignKey:ManagerID"`
    Reports   []User `gorm:"foreignKey:ManagerID"`
}

// Usage
var manager User
db.Preload("Reports").First(&manager, 1)

var employee User
db.Preload("Manager").First(&employee, 10)

Association Configuration

Configure association behavior globally or per-session.

// Global configuration
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    FullSaveAssociations: true,
})

// Per-session configuration
db.Session(&gorm.Session{
    FullSaveAssociations: true,
}).Create(&user)

// Skip hooks for associations
db.Session(&gorm.Session{
    SkipHooks: true,
}).Create(&user)