I'm trying to follow Gorm's documentation to create a generated field, defined by a function:
type Foo struct {
ID int64 `json:"id"`
AmountOfBars string `json:"amount_of_bars" gorm:"default:amount_of_bars()"`
}
type RelatedBar struct {
FooId int64 `json:"foo_id"`
}
However, I don't understand where and how to define amount_of_bars, so I'll be able to return the amount of the RelatedBar related rows.
You don't define such a function in Go, you define it in your database via CREATE FUNCTION. See https://www.postgresql.org/docs/9.1/sql-createfunction.html.
type User struct {
ID int `json:"id" gorm:"autoIncrement; primaryKey"`
Username string `json:"username" `
FirstName string `json:"first_name" `
LastName string `json:"last_name" `
FullName string `json:"full_name" `
Name string `json:"name" `
AvatarURL string `json:"avatar_url" gorm:"default:'http://www.google/.com'" `
CreatedAt int // Set to current time if it is zero on creating
UpdatedAt int // Set to current unix seconds on updating or if it is zero on creating
Deleted gorm.DeletedAt
}
Related
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)
}
Omit not working.
Retrieve Error:
invalid field found for struct deliveryFood/models.Restaurant's field
DeliveryZone, need to define a foreign key for relations or it need to
implement the Valuer/Scanner interface
type Restaurant struct {
ID uint
Name string `json:"name"`
EmployeeId uint `json:"employee_id"`
Phone string `json:"phone"`
Address string `json:"address"`
ImagesUrl *string `json:"images_url"`
Position string `json:"position"`
WorkDays string `json:"work_days"`
StartWorkTime string `json:"start_work_time"`
EndWorkTime string `json:"end_work_time"`
Blocked bool `json:"blocked"`
DeliveryZone []*DeliveryZone `json:",omitempty"`
}
type DeliveryZone struct {
ID uint `json:"id"`
RestaurantId uint `json:"restaurant_id"`
Zone string `json:"zone"`
Price float32 `sql:"-"`
}
err := GetDB().Omit(clause.Associations).Model(Restaurant{}).Create(map[string]interface{} {
"name": rest.Name,
"EmployeeId": rest.EmployeeId,
"Phone": rest.Phone,
"Address": rest.Address,
"ImagesUrl": rest.ImagesUrl,
"WorkDays": rest.WorkDays,
"StartWorkTime": rest.StartWorkTime,
"EndWorkTime": rest.EndWorkTime,
"Blocked": rest.Blocked,
"Position": clause.Expr{
SQL: "ST_GeomFromText(?)",
Vars: []interface{}{fmt.Sprintf("POINT((%s))", rest.Position)},
},
}).Error
Change RestaurantId to RestaurantID in DeliveryZone struct.
type Restaurant struct {
ID uint
Name string `json:"name"`
EmployeeId uint `json:"employee_id"`
Phone string `json:"phone"`
Address string `json:"address"`
ImagesUrl *string `json:"images_url"`
Position string `json:"position"`
WorkDays string `json:"work_days"`
StartWorkTime string `json:"start_work_time"`
EndWorkTime string `json:"end_work_time"`
Blocked bool `json:"blocked"`
DeliveryZone []*DeliveryZone `json:",omitempty"`
}
type DeliveryZone struct {
ID uint `json:"id"`
RestaurantID uint `json:"restaurant_id"`
Zone string `json:"zone"`
Price float32 `sql:"-"`
}
Or you can define the foreignkey manually by adding foreignKey tag in Restaurant struct. e.g.
DeliveryZone []*DeliveryZone json:",omitempty" gorm:"foreignKey:RestaurantId"
try
DeliveryZone []*DeliveryZone `gorm:"-"`
https://gorm.io/docs/models.html -> ctrl+F -> ignore this field
I've two structs:
type UpdateableUser struct {
FirstName string
LastName string
Email string
Tlm float64
Dob time.Time
}
type User struct {
Id string
FirstName string
LastName string
Email string
DOB time.Time
Tlm float64
created time.Time
updated time.Time
}
Through a binder I bind request data to the updateableUser struct, therefore I might have an updateableUser with only one "real" value, like uu here:
uu := UpdateableUser{Lastname: "Smith"}
Now I want to set only the not "emtpy" values from UpdateableUser to User. Can you please give me a hint or more?
I would recommend embedding the Updateable struct into the larger struct:
type UpdateableUser struct {
FirstName string
LastName string
Email string
Tlm float64
Dob time.Time
}
type User struct {
UpdateableUser
ID string
created time.Time
updated time.Time
}
func (u *User) UpdateFrom(src *UpdateableUser) {
if src.FirstName != "" {
u.FirstName = src.FirstName
}
if src.LastName != "" {
u.LastName = src.LastName
}
// ... And other properties. Tedious, but simple and avoids Reflection
}
This allows you to use UpdateableUser as an interface to make it explicit which properties can be updated.