I'm really new to go and I decided to create a RestAPI for skill practising. So now I'm trying to make a one to many relationship between my User and Book entities. But when I try to save my Book entity to database, it shows me such error:
invalid field found for struct test/entity.UserEntity's field Books: define a valid foreign key for relations or implement the Valuer
/Scanner interface
Here's my code:
UserEntity
type UserEntity struct {
gorm.Model
Username string `gorm:"unique;not null"`
Email string `gorm:"unique;not null"`
Password string `gorm:"not null"`
Books []BookEntity `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
func (*UserEntity) TableName() string {
return "users"
}
BookEntity
type BookEntity struct {
gorm.Model
Name string `gorm:"not null"`
Isbn int `gorm:"unique;not null"`
AuthorID uint
}
func (*BookEntity) TableName() string {
return "books"
}
BookAddModel
type BookAddModel struct {
Name string `json:"name"`
Isbn int `json:"isbn"`
}
func (bookModel *BookAddModel) ToModel(ctx *gin.Context) {
ctx.Bind(&bookModel)
}
Here's the code where I add entities to database
func AddBook(bookModel models.BookAddModel, author *entity.UserEntity) entity.BookEntity {
var book entity.BookEntity
mapstructure.Decode(bookModel, &book)
config.DbSession.Create(&book) // <<<< here's error
author.Books = append(author.Books, book)
config.DbSession.Save(author)
return book
}
Config
var DbSession *gorm.DB
var PostgresData = "host=localhost user=postgres password=4122 dbname=gorm port=5432 sslmode=disable TimeZone=Asia/Shanghai"
So what can bee the problem? If you know, please tell me. I'd really appreciate it!
You should set foreignKey for UserEntity.Books:
type UserEntity struct {
...
Books []BookEntity `gorm:"foreignKey:AuthorID,..."`
}
Just don't forget to drop tables first and rerun AutoMigrations:
db.AutoMigrate(&UserEntity{})
db.AutoMigrate(&BookEntity{})
Related
What is the difference between Has One, Has Many and Belong To
I have 3 Models
User
Profile Where profile and user should have one to one relationship
Category Where category should be foreign key to user
type User struct {
gorm.Model
Email *string
Name string
...
}
type Profile struct {
gorm.Model
Phone string
Address string
...
}
type Category struct {
gorm.Model
Name string
}
For User Has One Profile
type User struct {
gorm.Model
Email *string
Name string
Profile Profile //this is the key different
}
type Profile struct {
gorm.Model
UserId int //this is important
Phone string
Address string
}
For Profile Belong To User
type User struct {
gorm.Model
Email *string
Name string
}
type Profile struct {
gorm.Model
UserId int //this is important
User User //this is the key different
Phone string
Address string
}
For User Has Many Category
type User struct {
gorm.Model
Email *string
Name string
CategoryList []Category
}
type Category struct {
gorm.Model
UserId int //this is important
Name string
}
Edit: UserId field will become your foreign key.
If you want gorm to automatically create table for you, you can use AutoMigrate in main.go
err := db.AutoMigrate(your_model_package.User{})
if err != nil {
return err
}
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"`
//...
}
Gorm's half-baked, magic-out-the-box support of foreign keys has been an annoyance for years and I'm finally trying to figure it out once and for all. I'm using Postgres 12, gorm 1.23.3 and go 1.18.
I have a base model similar to gorm.Model but with a little bit extra:
type BaseModel struct {
ID string `json:"id" gorm:"type:uuid;primarykey;default:uuid_generate_v4()"`
InstanceVersion int `json:"instanceVersion"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
UpdatedAt time.Time `json:"updatedAt" gorm:"type:timestamp"`
DeletedAt *time.Time `json:"deletedAt,omitempty" gorm:"type:timestamp" sql:"index"`
CreatedBy string `json:"createdBy"`
UpdatedBy string `json:"updatedBy"`
DeletedBy string `json:"deletedBy,omitempty"`
MetaData json.RawMessage `json:"metadata" gorm:"type:jsonb;default:'{}'"`
}
Every model in my DB uses this BaseModel as follows:
type Profile struct {
BaseModel
Name string `json:"name"`
UserID string `json:"userId"`
}
It generates the tables as follows (UML generated by DBeaver and double checked to be true):
I'm trying to add a foreign key to the CreatedBy and UpdatedBy columns such that they must point to an existing Profile. So I add the following field to the BaseModel type:
CreatedByProfile *Profile `json:"-" gorm:"foreignKey:CreatedBy"`
I expected the foreign key to be created for every model that BaseModel is a part of and point back to the Profiles table. However, it only makes the FK on the Profiles table.
Minimal recreation of the problem:
package main
import (
"encoding/json"
"time"
"github.com/lib/pq"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type BaseModel struct {
ID string `json:"id" gorm:"type:uuid;primarykey;default:uuid_generate_v4()"`
InstanceVersion int `json:"instanceVersion"`
CreatedAt time.Time `json:"createdAt" gorm:"type:timestamp"`
UpdatedAt time.Time `json:"updatedAt" gorm:"type:timestamp"`
DeletedAt *time.Time `json:"deletedAt,omitempty" gorm:"type:timestamp" sql:"index"`
CreatedBy string `json:"createdBy"`
UpdatedBy string `json:"updatedBy"`
DeletedBy *string `json:"deletedBy,omitempty"`
MetaData json.RawMessage `json:"metadata" gorm:"type:jsonb;default:'{}'"`
CreatedByProfile *Profile `json:"-" gorm:"foreignKey:CreatedBy"`
}
type ActivityType string
type Activity struct {
Base BaseModel `gorm:"embedded"`
Type ActivityType `json:"type"`
Message string `json:"message"`
Content string `json:"content"`
ImageUrl string `json:"imageUrl"`
DisplayProfileIds pq.StringArray `json:"displayProfileIds" gorm:"type:uuid[]"`
RecipientProfileIds pq.StringArray `json:"recipientProfileIds" gorm:"-"`
// Preload
ActivityProfiles []*ActivityProfile `json:"activityProfiles"` // has many
}
type ActivityProfile struct {
Base BaseModel `gorm:"embedded"`
ReadAt *time.Time `json:"readAt,omitempty" gorm:"type:timestamp" sql:"index"`
Route string `json:"route"`
ActivityID string `json:"activityId"`
ProfileID string `json:"profileId"`
// Preload
Activity *Activity `json:"activity"` // belongs to
Profile *Profile `json:"profile"` // belongs to
}
type Profile struct {
BaseModel
Name string `json:"name"`
UserID string `json:"userId"`
}
func main() {
db, err := gorm.Open(postgres.Open("host=localhost port=5432 user=corey dbname=corey password= sslmode=disable"))
if err != nil {
panic(err)
}
models := []interface{}{
&Activity{},
&ActivityProfile{},
&Profile{},
}
err = db.AutoMigrate(models...)
if err != nil {
panic(err)
}
}
I've also tried using gorm:"embedded" tags instead of nested structs but it didn't help. Nothing in this question helps: DB.Model(...).AddForeignKey(...) doesn't exist anymore, the db.Migrator().CreateConstraint(...) lines don't work (how does it know what column is the FK and which column it matches to of what other type? Do I have to run both lines? How could this ever possibly work?!?), and I don't want OnUpdate or OnDelete constraints, just foreign keys.
If I put the CreatedByProfile field on Activity, then I get a FK where Profile.CreatedBy is an FK to Activity.ID which is 100% backwards.
I can add this field to my Profile model:
Activities []*Activity `json:"-" gorm:"foreignKey:CreatedBy"`
and it creates the FK like I want, but I don't actually want that field present on the model. Plus, I'd have to add this unnecessary boilerplate for every model in my DB (and _ fields don't end up with the FK being made).
How can I make Gorm do simple things like create foreign keys without decorating my models with unused fields?
I am trying to add a custom attribute to my Golang struct just like how I usually add custom attribute on a Laravel model using the $appends variable.
This is the code:
package models
import (
"time"
)
type Dummy struct {
ID string `json:"id" gorm:"primary_key"`
Description string `json:"description"`
Image string `json:"image"`
ImageUrl ImageUrlDummy
Number int `json:"number"`
UserId string `json:"user_id"`
User User `json:"user"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func ImageUrlDummy() string {
return "test"
}
However, the ImageUrlDummy inside the struct does not work, it return error such as:
ImageUrlDummy (value of type func() string) is not a type
How do I achieve this same code from Laravel to Golang?
class Dummy extends Model
{
protected $appends = ['image_url'];
public function getImageUrlAttribute(){
return "this is the image url";
}
}
Please pardon me I am still learning Golang, thank you
You are not far off..
Change your struct to (remove ImageUrlDummy, fix json tag for Image):
type Dummy struct {
ID string `json:"id" gorm:"primary_key"`
Description string `json:"description"`
Image string `json:"image"`
Number int `json:"number"`
UserId string `json:"user_id"`
User User `json:"user"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Then define a method with a receiver of type Dummy pointer
func (d *Dummy) ImageUrl() string {
return "test"
}
Example with a few more things: https://play.golang.com/p/olGSFhBgqkG
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)
}