Strange behaviour deleting association happened.
The query generated an extra condition which i did not add.
type Client struct {
gorm.Model
Name string `gorm:"unique;not null" validate:"required,min=1,max=30"`
Kyc_status string `gorm:"not null" validate:"required,min=1,max=30"`
Kyc_remarks string `gorm:"default:null" validate:"omitempty,min=0,max=200"`
Operators []*Operator `gorm:"many2many:client_operators;"`
Op_ids []string `gorm:"-:all" validate:"omitempty,dive,numeric"` // placeholder field, wont be part of table
}
type Operator struct {
gorm.Model
Title string `gorm:"unique;not null" validate:"required,min=1,max=100"`
Email string `gorm:"not null" validate:"required,email"`
Mobile string `gorm:"not null" validate:"required,min=7,max=15,numeric"`
Last_online time.Time `gorm:"default:null" validate:"omitempty"`
Last_ip string `gorm:"default:null" validate:"omitempty,ip"`
Clients []*Client `gorm:"many2many:client_operators;"`
Cli_ids []string `gorm:"-:all" validate:"omitempty,dive,numeric"`
}
// find operators related to client
var client_query *Client
DBconnection.Where("id = ?", pk).Preload("Operators").First(&client_query)
// delete operators related to client
DBconnection.Model(&Client{}).Where("client_id = ?", pk).Association("Operators").Delete(&client_query.Operators)
I expect the deletion to be:
[2.000ms] [rows:0] DELETE FROM `client_operators` WHERE client_id = 5 AND `client_operators`.`operator_id` = 1
OR
[2.000ms] [rows:0] DELETE FROM `client_operators` WHERE `client_operators`.`client_id` = 5 AND `client_operators`.`operator_id` = 1
However it currently does:
[2.000ms] [rows:0] DELETE FROM `client_operators` WHERE client_id = 5 AND `client_operators`.`client_id` IN (NULL) AND `client_operators`.`operator_id` = 1
It adds the extra condition of " AND `client_operators`.`client_id` IN (NULL) "
I tried Association().Clear() and did not do anything too.
This is happening because you're passing &Client{} to Model.
Looking at gorm docs you need to construct a client with a valid Id first like this:
type Client struct {
gorm.Model
Name string `gorm:"unique;not null" validate:"required,min=1,max=30"`
Kyc_status string `gorm:"not null" validate:"required,min=1,max=30"`
Kyc_remarks string `gorm:"default:null" validate:"omitempty,min=0,max=200"`
Operators []*Operator `gorm:"many2many:client_operators;"`
Op_ids []string `gorm:"-:all" validate:"omitempty,dive,numeric"` // placeholder field, wont be part of table
}
type Operator struct {
gorm.Model
Title string `gorm:"unique;not null" validate:"required,min=1,max=100"`
Email string `gorm:"not null" validate:"required,email"`
Mobile string `gorm:"not null" validate:"required,min=7,max=15,numeric"`
Last_online time.Time `gorm:"default:null" validate:"omitempty"`
Last_ip string `gorm:"default:null" validate:"omitempty,ip"`
Clients []*Client `gorm:"many2many:client_operators;"`
Cli_ids []string `gorm:"-:all" validate:"omitempty,dive,numeric"`
}
// find operators related to client
var client_query *Client
DBconnection.Where("id = ?", pk).Preload("Operators").First(&client_query)
// delete operators related to client
DBconnection.Model(&Client{ID: pk}).Association("Operators").Delete(&client_query.Operators)
Related
I have database structure:
type User struct {
db.Base
Username string `gorm:"unique;not null" json:"username"`
Password string `gorm:"not null" json:"password"`
FullName string `gorm:"not null" json:"full_name"`
Email string `gorm:"unique;not null" json:"email"`
}
And Account DB structure:
type Account struct {
db.Base
UserID uuid.UUID `gorm:"not null" json:"user_id"`
User user.User `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"user"`
Name string `gorm:"not null" json:"name"`
Currency string `gorm:"not null" json:"currency"`
}
The Base looks like:
type Base struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `sql:"index" json:"deleted_at"`
}
func (base *Base) BeforeCreate(db *gorm.DB) error {
v4 := uuid.NewV4()
db.Set("ID", v4)
base.ID = v4
return nil
}
But when i tried to insert new value:
func Add(u *user.User) (account Account, err error) {
account = Account{
User: *u,
UserID: u.ID,
Currency: currency.EUR.String(),
Name: "Default",
}
err = consts.DB.Model(&Account{}).Save(&account).Error
return account, err
}
The error drops:
ERROR: insert or update on table "accounts" violates foreign key constraint "fk_accounts_user" (SQLSTATE 23503)
[9992.860ms] [rows:0] INSERT INTO "accounts" ("created_at","updated_at","deleted_at","user_id","name","currency","id") VALUES ('2023-01-30 14:55:20.261','2023-01-30 14:55:20.261',NULL,'f5e6984d-4945-4a90-9085-4d6b9d94a8c8','Default','EUR','74943fc5-d767-4e52-8044-d0e9e26e4568') RETURNING "id"
[GIN] 2023/01/30 - 14:55:21 | 400 | 4m56s | ::1 | POST "/api/v1/accounts"
In every request the reference uuid is different. On 27 of the Add function the Id and User is correct. But after saving attempt - UserID is random UUID.
The problem was in method BeforeCreate:
func (base *Base) BeforeCreate(db *gorm.DB) error {
v4 := uuid.NewV4()
db.Set("ID", v4)
base.ID = v4
return nil
}
It generated a new ID for each entity each time a record was created, and along with it, new IDs for all references
How to fix? In the Base class, add an ID field of type UUD tag default:
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid();" json:"id"`
Here gen_random_uuid() is internal PostgreSQL method available from, v14 (not sure)
I'm trying to create a belongs to relation between two database tables, using GORM, my code is the following:
type Shop struct {
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid"`
Name string `json:"name" gorm:"not null" validate:"required"`
City string `json:"city" gorm:"not null" validate:"required"`
State string `json:"state" gorm:"not null" validate:"required"`
}
type Employee struct {
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid"`
FirstName string `json:"first_name" gorm:"not null" validate:"required"`
LastName string `json:"last_name" gorm:"not null" validate:"required"`
Email string `json:"email" gorm:"not null;unique" validate:"required,email"`
Password string `json:"password" gorm:"not null" validate:"required"`
Active bool `json:"active" gorm:"not null;default:false"`
ShopId uuid.UUID `json:"shop_id" gorm:"type:uuid"`
Shop Shop `gorm:"foreignKey:ShopID"`
}
When I run the migrations, this error pops up:
[error] invalid field found for struct .../.../.../api/models.Employee's field Shop: define a valid foreign key for relations or implement the Valuer/Scanner interface
I've found some references using number primary keys and they seem to work fine, but I can't find any solution to work with uuids...
I'm not sure, but what I understand of the message error is that the type uuid.UUID doesn't have implemented the methods for the interfaces Valuer and Scanner.
You should create your own type UUID, which can be something like this:
type UUID uuid.UUID
func(id UUID) Value() (driver.Value, error) {
return id.String(), nil
}
func (id *UUID) Scan(value interface{}) error {
dbID, ok := value.(string)
if !ok {
return errors.New("id scan: invalid value")
}
*e = uuid.MustParse(dbID)
return nil
}
And use it on your struct's definitions:
type Shop struct {
ID UUID `json:"id" gorm:"primaryKey;type:uuid"`
//...
}
Is it possible to use many to many between 3 models?
I have 3 models I want to join into a bridge table "client_operator_role"
Operator
type Operator struct {
gorm.Model
Title string `gorm:"unique;not null" validate:"required,min=1,max=100"`
Email string `gorm:"not null;unique" validate:"required,email"`
Mobile string `gorm:"not null" validate:"required,min=7,max=15,numeric"`
Last_online time.Time `gorm:"default:null" validate:"omitempty"`
Last_ip string `gorm:"default:null" validate:"omitempty,ip"`
Clients []*Client `gorm:"many2many:client_operator_role;"`
Cli_ids []string `gorm:"-:all" validate:"omitempty,dive,numeric"` // placeholder field, wont be part of table
GoadminCustomUser GoadminCustomUser `validate:"omitempty,dive"`
}
Client
type Client struct {
gorm.Model
Name string `gorm:"unique;not null" validate:"required,min=1,max=30"`
Kyc_status string `gorm:"not null" validate:"required,min=1,max=30"`
Kyc_remarks string `gorm:"default:null" validate:"omitempty,min=0,max=200"`
Operators []*Operator `gorm:"many2many:client_operator_role;"`
Op_ids []string `gorm:"-:all" validate:"omitempty,dive,numeric"` // placeholder field, wont be part of table
Users []User
}
Role
type GoadminRole struct {
ID uint `gorm:"primaryKey"`
Name string
Slug string
CreatedAt time.Time
UpdatedAt time.Time
Operators []*Operator `gorm:"many2many:client_operator_role;"`
}
Gorm docs state many2many is only for 2 models. Is there a workaround to join these three?
Summary
I defined a 'UserFollowing' as a 'struct' type
type UserFollowing struct {
ID string `gorm:"primaryKey;not null;unique" json:"id"`
User User `gorm:"foreignKey:ID;references:UserId"`
Followings []*User `gorm:"many2many:user_relation;joinForeignKey:UserId;JoinReferences:following_id"`
}
Where type 'User' is defined as
type User struct {
Email string `gorm:"size:100;not null;unique" json:"email"`
Password string `gorm:"size:60;not null" json:"password,omitempty"`
CreatedAt time.Time `gorm:"default:current_timestamp()" json:"created_at"`
UpdatedAt time.Time `gorm:"default:current_timestamp()" json:"updated_at"`
Verified bool `gorm:"default:false" json:"verified"`
AToken string `gorm:"size:100;not null;unique" json:"accessToken"`
RToken string `gorm:"size:100;not null;unique" json:"refreshToken"`
YouAreFollowing bool `json:"youAreFollowing"`
Username string `json:"username"`
Online bool `json:"online"`
NumFollowing uint64 `json:"numFollowing"`
NumFollowers uint64 `json:"numFollowers"`
LastOnline time.Time `gorm:"default:current_timestamp()" json:"lastOnline"`
UserId string `gorm:"primaryKey;not null;unique" json:"userid"`
FollowsYou bool `json:"followsYou"`
BotOwnerId string `json:"botOwnerId"`
Contributions uint64 `json:"contributions"`
Staff bool `json:"staff"`
DisplayName string `gorm:"size:20" json:"displayName"`
CurrentRoomId string `json:"currentRoomId"`
CurrentRoom Room `json:"currentRoom"`
Bio string `gorm:"size:250" json:"bio"`
Avatar string `gorm:"size:100" json:"avatarUrl"`
BannerUrl string `json:"bannerUrl"`
WhisperPrivacySetting string `json:"whisperPrivacySetting"`
Room_Permissions RoomPermissions `json:"roomPermissions"`
}
I tried to append 'User' type elements to the 'Followings' field by doing the following:
for i, _ := range users {
...
userfollowings[i].ID = users[i].UserId
userfollowings[i].Followings = append(userfollowings[i].Followings, users[i])
...
}
Error
cannot use users[i] (type models.User) as type *models.User in append
Unsuccessful Attempt
*userfollowings[i].Followings = append(*userfollowings[i].Followings, users[i])
References consulted
difference-using-pointer-in-struct-fields
Any suggestions on how to solve this problem?
You should use address of the structure instead of direct value if you want to store pointers []*User.
Try to visit this page and run this code:
package main
import "fmt"
type User struct {
UserId string
}
type UserFollowing struct {
ID string
Followings []*User
}
var (
users = []User{{"user-1"}, {"user-2"}, {"user-3"}}
userFollowings = make([]UserFollowing, len(users))
)
func main() {
for i, _ := range users {
// ...
userFollowings[i].ID = users[i].UserId
userFollowings[i].Followings = append(userFollowings[i].Followings, &users[i])
// ...
}
fmt.Println("%#v", userFollowings)
}
I have a query that fetches rows from jobs table and its author (each job has an author), but I want to select specific fields.
type User struct {
ID uint `gorm:"primarykey" json:"-"`
UUID uuid.UUID `gorm:"type:uuid not null" json:"-"`
Email string `gorm:"type:varchar(255); not null" json:"email"`
Name string `gorm:"type:varchar(255); not null" json:"name"`
AvatarURL string `gorm:"type:varchar(255); not null" json:"avatar_url"`
Provider string `gorm:"type:varchar(255); not null" json:"provider"`
ProviderID string `gorm:"type:varchar(255); not null" json:"-"`
Jobs []Job `json:"-"`
}
type Job struct {
ID uint `gorm:"primarykey" json:"id"`
Title string `gorm:"type:varchar(255); not null" json:"title"`
Content string `gorm:"not null" json:"content"`
UserID uint `json:"-"`
User User `json:"author"`
}
func (jobRepo repository) FindAll() ([]entity.Job, error) {
var jobs []entity.Job
if dbc := jobRepo.db.Preload("User", func(db *gorm.DB) *gorm.DB {
return db.Select("Name", "Email")
}).Find(&jobs); dbc.Error != nil {
return nil, dbc.Error
}
return jobs, nil
}
The custom preload does not behave as desired. If I do not specify concrete fields, the query works and returns everything. Otherwise, If I specify some fields, it returns nothing.
This is because you didn't select primary key. Add "ID" into select clause:
func(db *gorm.DB) *gorm.DB {
return db.Select("ID", "Name", "Email")
}
Otherwise GORM doesn't know how to join users to jobs.