GORM: Reference replaced with random values - go

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)

Related

Cannot bind JSON in Golang Gin to struct with ManyToMany field

I have the following structs as relational models:
type Product struct {
ID uint32 `gorm:"primary_key;auto_increment" json:"id"`
Name string `gorm:"size:100;not null" json:"name"`
Description string `gorm:"size:512" json:"description"`
Price float64 `gorm:"default:0" json:"price"`
Shop Shop `json:"shop"`
ShopID uint32 `gorm:"not null" json:"shop_id"`
Categories []Category `gorm:"many2many:product_categories;" json:"categories"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
}
type Category struct {
ID uint32 `gorm:"primary_key;auto_increment" json:"id"`
Name string `gorm:"size:100;not null" json:"name"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
}
Now, on my controller I want to create a Product with multiple categories from a JSON object but I can't find a way on how to do it; thus, it returns the message Unable to bind data to product. Here is the code:
func (s *Server) createProduct(c *gin.Context) {
uid, err := auth.ExtractTokenID(c.Request)
if err != nil {
c.IndentedJSON(http.StatusUnauthorized, gin.H{"message": "Unauthorized"})
return
}
var product = models.Product{}
var shop = models.Shop{}
if err := c.BindJSON(&product); err != nil {
fmt.Println(err)
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unable to bind data to product"})
return
}
Is there a solution or a simple way on how to achieve this? As I haven't been able to find anything on gorm documentation.
Edit; Here is the JSON object I'm sending:
{
"name": "Soap",
"description": "Smelly Soap",
"Price": 12.99,
"categories": [1]
}

How to return a newly created record back from the database using gorm

I have a function that creates a new user, however the reccomended way to get the values of the user does not include the autogenerated values created by the database (id, created_at)
type User struct {
Id string `json:"id" gorm:"primaryKey"`
Email string `json:"email"`
Password string `json:"password"`
Created_At string `json:"created_at"`
Updated_At string `json:"updated_at"`
Deleted_At string `json:"deleted_at"`
}
type UserRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
func CreateUserRepo(newUser UserRequest) (*User, error) {
user := User{Email: newUser.Email, Password: newUser.Password}
fmt.Println(user)
result := services.PostgresSQLDB.Select("Email", "Password").Create(&user)
fmt.Println(result.RowsAffected)
if result.Error != nil {
modules.Logger.Error(result.Error)
return nil, result.Error
}
return &user, nil
}
You can see i get an empty string for both id and created_at although those values have been auto generated by my database.
{
"id": "",
"email": "test#test.com",
"password": "password",
"created_at": "",
"updated_at": "",
"deleted_at": ""
}
testing
To some extend i think this comes down to writing the "proper" types that gorm expects.
It seems like Id will return the uuid if i add gorm.Model to my struct, but it also returns back a bunch of fields that seem like duplicates that i dont want (ID, CreatedAt, UpdatedAt, DeletedAt). Removing gorm.Model, Id no longer gives me back the uuid generated by postgres
type User struct {
gorm.Model
Id uuid.UUID `json:"id"`
Email string `json:"email"`
Password string `json:"password"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}
{
"ID": 0,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"id": "b4dea226-3be2-4ee7-8548-67ccbbbcbcca",
"email": "test#test.com",
"password": "password",
"created_at": "2022-06-25T20:59:27.872198797+01:00",
"updated_at": "2022-06-25T20:59:27.872198797+01:00",
"deleted_at": null
}
database postgres
-- CreateTable
CREATE TABLE "users" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"email" TEXT NOT NULL,
"password" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"deleted_at" TIMESTAMP(3),
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
I finally found the answer without comprising.
Adding the default return clause, returns all the newly created values.
https://gorm.io/docs/update.html#Returning-Data-From-Modified-Rows
Clauses.(clause.Returning{})
model.go
type User struct {
ID uuid.UUID `json:"id" gorm:"primaryKey:type:uuid"`
Email string `json:"email"`
Password string `json:"password"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}
repository.go
func CreateUserRepo(newUser UserRequest) (*User, error) {
hash, errHash := modules.HashPassword(newUser.Password)
if errHash != nil {
return nil, errHash
}
user := User{Email: newUser.Email, Password: *hash}
result := services.PostgresSQLDB.Clauses(clause.Returning{}).Select("Email", "Password").Create(&user)
fmt.Println(result.RowsAffected)
fmt.Println(&user)
if result.Error != nil {
modules.Logger.Error(result.Error)
return nil, result.Error
}
return &user, nil
}
thanks to #TheFool comment for this find.
As far as I know, GORM doesn't support anything else that integer auto increment for the primary key. So either you keep that on PostgreSQL side (like you did) or you generate your own UUID in Go before creating the object (with BeforeCreate hook).
Also, the gorm.Model definition is the following:
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
You don't have to use it, but if you don't, add gorm:"primaryKey" to your primary key field. By default primary field have auto increment, for your case, I would recommend to disable it with autoIncrement:false.
type User struct {
Id uuid.UUID `gorm:"primaryKey;autoIncrement:false" json:"id"`
Email string `json:"email"`
Password string `json:"password"`
}
As you let PostgreSQL handle default value (and not GORM), you have to query again the object in order to access your id UUID and created_at TIMESTAMP.
Also note that you can use GormValuerInterface to use an SQL Expr on creation. But you will still have to query again your record. (https://gorm.io/docs/data_types.html#GormValuerInterface)
In case you are interested to handle all on Go side, here an example with and without gorm.Model.
package main
import (
"fmt"
"time"
"github.com/google/uuid"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
var DB *gorm.DB
type User struct {
UUID uuid.UUID `gorm:"primaryKey;autoIncrement:false"`
Name string
CreatedAt time.Time
}
type UserWithGormModel struct {
gorm.Model
UUID uuid.UUID `gorm:"primaryKey;autoIncrement:false"`
Name string
}
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
// This is an example, refer to https://pkg.go.dev/github.com/google/UUID for good usage
u.UUID = uuid.New()
u.CreatedAt = time.Now()
return
}
func (u *UserWithGormModel) BeforeCreate(tx *gorm.DB) (err error) {
// This is an example, refer to https://pkg.go.dev/github.com/google/UUID for good usage
u.UUID = uuid.New()
return
}
func ConnectDatabase() {
database, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
if err != nil {
panic("Failed to connect to database!")
}
database.AutoMigrate(&User{}, &UserWithGormModel{})
DB = database
}
func main() {
ConnectDatabase()
user := User{Name: "Bob"}
DB.Create(&user)
fmt.Printf("User{UUID: %s, User.Name: %s, CreatedAt: %s}\n", user.UUID, user.Name, user.CreatedAt)
user2 := UserWithGormModel{Name: "John"}
DB.Create(&user2)
fmt.Printf("UserWithGormModel{UUID: %s, Name: %s, CreatedAt: %s}\n", user2.UUID, user2.Name, user2.CreatedAt)
}
Output:
User{UUID: 8551636a-540f-4733-8179-3f0cc45daf4f, User.Name: Bob, CreatedAt: 2022-06-28 11:40:10.9225724 +0200 CEST}
UserWithGormModel{UUID: 2a6f94bd-a42b-4316-90be-0e93ea5091a6, Name: John, CreatedAt: 2022-06-28 11:40:10.9318004 +0200 CEST}

Gorm UUID foreign key - belongs to

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"`
//...
}

A foreign key constraint fails when insert to table with gorm

When I try to insert to a table with gorm which has one to many relationship, I get this error:
Error 1452: Cannot add or update a child row: a foreign key constraint fails (`Todo`.`todos`, CONSTRAINT `fk_users_todo_list` FOREIGN KEY (`fk_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE)....
These are my models:
type Base struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `gorm:"index"`
}
type User struct {
Base
FirstName string `form:"first_name" gorm:"type:varchar(20)"`
LastName string `form:"last_name" gorm:"type:varchar(20)"`
UserName string `form:"user_name" gorm:"unique;type:varchar(20)"`
Email string `form:"email" gorm:"type:varchar(100)"`
Password string `form:"password" gorm:"type:varchar(30)"`
Gender types.UserGender `form:"gender" gorm:"type:smallint"`
TodoList []Todo `gorm:"foreignKey:FkID;constraint:onDelete:SET NULL,onUpdate:CASCADE" json:"omitempty"`
}
type Todo struct {
Base
Name string
Description string
Status types.TaskStatus
FkID uint
}
And this is the function that I wrote:
func (d *DB) AddTODO(todo *models.Todo, userName string) error {
user := &models.User{UserName: userName}
err := d.db.Model(&user).Where("user_name = ?", userName).Association("TodoList").
Append([]models.Todo{*todo})
if err != nil {
return err
}
return nil
}
I'm using MariaDB.
It want user from gorm result. Change your method maybe like this:
// arg user is from gorm result somewhere
func (d *DB) AddTODO(user *models.User, todo *models.Todo) error {
return d.db.Model(user).Association("TodoList").Append([]models.Todo{*todo})
}
TodoList lack references keyword. It should be like this.
type User struct {
Base
FirstName string `form:"first_name" gorm:"type:varchar(20)"`
LastName string `form:"last_name" gorm:"type:varchar(20)"`
UserName string `form:"user_name" gorm:"unique;type:varchar(20)"`
Email string `form:"email" gorm:"type:varchar(100)"`
Password string `form:"password" gorm:"type:varchar(30)"`
Gender types.UserGender `form:"gender" gorm:"type:smallint"`
TodoList []Todo `gorm:"foreignKey:FkID;references:ID;constraint:onDelete:SET NULL,onUpdate:CASCADE" json:"omitempty"`
}

Custom Gorm preloading does not fetch data

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.

Resources