A has many association creates a one-to-many relationship. One owner record can be associated with any number of child records. The foreign key is stored on the child model’s table.
Struct definition
type User struct {
gorm.Model
CreditCards []CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserID uint // foreign key — references users.id
}
GORM detects has many when the association field is a slice. It looks for a field named <OwnerType>ID on the child struct as the foreign key.
Override the foreign key
Use foreignKey to specify a different field on the child model:
type User struct {
gorm.Model
CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}
type CreditCard struct {
gorm.Model
Number string
UserRefer uint
}
Override references
Point the foreign key at a non-primary field on the owner with references:
type User struct {
gorm.Model
MemberNumber string `gorm:"unique"`
CreditCards []CreditCard `gorm:"foreignKey:UserMemberNumber;references:MemberNumber"`
}
type CreditCard struct {
gorm.Model
Number string
UserMemberNumber string
}
Create with a HasMany association
Populate the slice field when creating the owner record:
user := User{
CreditCards: []CreditCard{
{Number: "4111-1111-1111-1111"},
{Number: "5500-0000-0000-0004"},
},
}
db.Create(&user)
// INSERT INTO users DEFAULT VALUES;
// INSERT INTO credit_cards (number, user_id) VALUES ('4111-1111-1111-1111', 1), ('5500-0000-0000-0004', 1);
To skip saving associations when creating:
db.Omit("CreditCards").Create(&user)
// INSERT INTO users DEFAULT VALUES; (CreditCards are not inserted)
Query with Preload
Load all associated records alongside the owner:
var users []User
db.Preload("CreditCards").Find(&users)
// SELECT * FROM users;
// SELECT * FROM credit_cards WHERE user_id IN (1, 2, 3);
Load a single user with all their credit cards:
var user User
db.Preload("CreditCards").First(&user, 1)
Conditional preload
Filter the associated records during preload:
// Only load non-expired cards
db.Preload("CreditCards", "expires_at > ?", time.Now()).Find(&users)
// Using a function for complex conditions
db.Preload("CreditCards", func(db *gorm.DB) *gorm.DB {
return db.Order("credit_cards.number ASC")
}).Find(&users)
Preload All
Use clause.Associations to preload all first-level associations at once:
db.Preload(clause.Associations).Find(&users)
Association mode operations
Use Association mode to manage the child records of a loaded owner:
// Append new credit cards to an existing user
db.Model(&user).Association("CreditCards").Append(
&CreditCard{Number: "3782-8224-6310-005"},
)
// Replace all credit cards
db.Model(&user).Association("CreditCards").Replace(
[]CreditCard{{Number: "4111-1111-1111-1111"}},
)
// Delete a specific credit card from the association
db.Model(&user).Association("CreditCards").Delete(&CreditCard{ID: 3})
// Remove all credit cards from the association (sets user_id to NULL)
db.Model(&user).Association("CreditCards").Clear()
// Count associated credit cards
count := db.Model(&user).Association("CreditCards").Count()
Clear and Delete nullify the foreign key by default. To permanently delete the child records, use Unscoped().Association(...).Clear() or call Unscoped().Association(...).Delete(...). See Association mode for details.
Batch operations
Association mode supports batch operations when your model value is a slice:
var users []User
db.Find(&users)
// Append a card to each user
db.Model(&users).Association("CreditCards").Append(
[]CreditCard{{Number: "4111"}, {Number: "5500"}},
)
// Each element in the slice maps to the corresponding user
When performing batch operations, the length of the values slice must equal the length of the model slice, or GORM returns ErrInvalidValueOfLength.
Constraints
Define referential actions on the foreign key:
type User struct {
gorm.Model
CreditCards []CreditCard `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}