Jackk-Doe
Jackk-Doe

Reputation: 179

Specifying `gorm:"unique"`, causing preload of belongs to relation a problem

Here's my Post & User struct model :

type Post struct {
    ID     string `gorm:"primaryKey"`
    Title  string `gorm:"unique"`
    UserID string
    User   User
}


type User struct {
    ID   string `gorm:"primaryKey"`
    Name string
}

// Code to create a Post with title & user input
func CreatePost(title string, u User) (Post, error) {
    b := Post{Title: title, User: u}
    dbIns := GetDB()
    if err := dbIns.Create(&b).Error; err != nil {
        return Post{}, err
    }
    return b, nil
}

// Code to get a Post, also preload its User field
func GetOnePost() (Post, error) {
    var p Post
    dbIns := GetDB()
    if err := dbIns.Model(&Post{}).Preload("User").First(&p).Error; err != nil {
        return Post{}, err
    }
    return p, nil
}

Here's is an output of what I got from GetOnePost(), which is exactly what I expected, Post.User is not empty.

2022/11/27 15:42:48 Post title: John-Doe's Title, UUID: 32158510-6dcc-4c26-bcb8-7d76a88bda72, authorUUID: 73d020d3-eb8d-4bf7-9f57-e821c5deceed 
2022/11/27 15:42:48 User name: John-Doe, UUID: 73d020d3-eb8d-4bf7-9f57-e821c5deceed

However when I specified the User.Name to be unique (since I want each user to have different Name)
note : yes I did migrate into a new table.

type User struct {
    ID   string `gorm:"primaryKey"`
    Name string `gorm:"unique"`
}

The output from GetOnePost() changed to this : (please ignore the UUID)

2022/11/27 15:57:49 Post title: John-Doe's Title, UUID: 26355816-c376-497a-a250-be118c5933e2, authorUUID: e858cd72-56b9-4884-b6e6-94121c36719b 
2022/11/27 15:57:49 User name: , UUID:

A problem is that the returned Post.User seems to be empty, when I add unique constraint to User model.

If I remove the unique constraint from User model, it returns to normal (Post.User not-empty). So it seems to me that the problem is with the gorm:"unique" part.

Upvotes: 2

Views: 579

Answers (1)

ossan
ossan

Reputation: 1855

Honestly, I was not able to repro your issue. Anyway, I added my attempt to repro your issue that works as expected. Let me show the code first and then I'll provide you with a brief explanation:

package main

import (
    "fmt"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"

    "github.com/google/uuid"
    _ "github.com/lib/pq"
)

type Post struct {
    ID     string `gorm:"primaryKey"`
    Title  string `gorm:"unique"`
    UserID string
    User   User
}

type User struct {
    ID   string `gorm:"primaryKey"`
    Name string `gorm:"unique"`
}

// Code to create a Post with title & user input
func CreatePost(db *gorm.DB, title string, u User) (Post, error) {
    id := uuid.NewString()
    b := Post{ID: id, Title: title, User: u}
    if err := db.Create(&b).Error; err != nil {
        return Post{}, err
    }
    return b, nil
}

// Code to get a Post, also preload its User field
func GetOnePost(db *gorm.DB) (Post, error) {
    var p Post
    if err := db.Model(&Post{}).Preload("User").First(&p).Error; err != nil {
        return Post{}, err
    }
    return p, nil
}

func main() {
    dsn := "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable"
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }

    db.AutoMigrate(&User{})
    db.AutoMigrate(&Post{})

    defer db.Exec("DROP TABLE users")
    defer db.Exec("DROP TABLE posts")

    userId := uuid.NewString()

    _, err = CreatePost(db, "Learn Go", User{
        ID:   userId,
        Name: "John Doe",
    })
    if err != nil {
        panic(err)
    }

    post, err := GetOnePost(db)
    if err != nil {
        panic(err)
    }

    // this leads to a Duplicate error SQLSTATE 23505 in DB
    // if err := db.Create(&User{
    //  ID:   uuid.NewString(),
    //  Name: "John Doe",
    // }).Error; err != nil {
    //  panic(err)
    // }

    fmt.Printf("post's details: %v\n", post)
}

More or less the code is the same as you provided. I added two deferred function calls to delete the tables after every run of the code. Thanks to this we're always sure to work with the latest table definition provided. Then, I check in Postgres if the UNIQUE constraint was present and I correctly found it. However, when I try to run my code I was successfully able to retrieve also the User information. If you try to uncomment the code block starting with this comment // this leads to a Duplicate error SQLSTATE 23505 in DB you'll get a duplicate error so the constraint is enforced on the database side.

Let me know if by using my solution your code works too!

Upvotes: 1

Related Questions