gORM belongs to association doesnt preload - go

I am trying to make a relational database with gORM and I have a problem with the belongs to method that I can't get to work.
I am expecting to see the booking with the table it is related to but the booking just shows a table with nil values. and the TableID that it should refer to is also null.
I have created a small test which creates some data and prints out the results as json.
package main
import (
"encoding/json"
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Username string
Password string
Bookings []Booking `gorm:"polymorphic:Owner;"`
}
type Booking struct {
gorm.Model
TableID *int
Table Table
OwnerID uint
Time int
OwnerType string
}
type Table struct {
gorm.Model
Number int
Price int
}
func DatabaseConnection() *gorm.DB {
db, err := gorm.Open(sqlite.Open("./test.db"), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Booking{})
db.AutoMigrate(&Table{})
db.AutoMigrate(&User{})
return db
}
func main() {
fmt.Println("creating stuff..")
createStuff()
fmt.Println("checking table..")
checkTable()
fmt.Println("checking booking..")
checkBooking()
fmt.Println("checking user..")
checkUser()
}
func checkTable() {
db := DatabaseConnection()
var table Table
db.First(&table)
u, _ := json.Marshal(&table)
fmt.Println(string(u))
}
func checkUser() {
db := DatabaseConnection()
var user User
db.First(&user)
db.Preload("Bookings.Table").Preload("Bookings").First(&user)
u, _ := json.Marshal(&user)
fmt.Println(string(u))
}
func checkBooking() {
db := DatabaseConnection()
var booking Booking
db.Preload("Table").First(&booking)
u, _ := json.Marshal(&booking)
fmt.Println(string(u))
}
func createStuff() {
db := DatabaseConnection()
tables := []Table{
{Number: 1, Price: 100},
{Number: 2, Price: 100},
{Number: 3, Price: 100},
{Number: 4, Price: 100},
{Number: 5, Price: 100},
{Number: 6, Price: 100},
{Number: 7, Price: 100},
}
user := User{
Username: "martin",
Password: "secret",
}
db.Create(&user)
db.Create(&tables)
booking := Booking{Time: 1234}
db.Model(&tables[0]).Association("Table").Append(
&booking,
)
db.Model(&user).Association("Bookings").Append(
&booking,
)
}
I get the following output from my test:
checking table..
{
"ID": 1,
"CreatedAt": "2021-12-06T14:56:27.169847346+01:00",
"UpdatedAt": "2021-12-06T14:56:27.169847346+01:00",
"DeletedAt": null,
"Number": 1,
"Price": 100
}
checking booking..
{
"ID": 1,
"CreatedAt": "2021-12-06T14:56:27.173323491+01:00",
"UpdatedAt": "2021-12-06T14:56:27.173323491+01:00",
"DeletedAt": null,
"TableID": null,
"Table": {
"ID": 0,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"Number": 0,
"Price": 0
},
"OwnerID": 1,
"Time": 1234,
"OwnerType": "users"
}
checking user..
{
"ID": 1,
"CreatedAt": "2021-12-06T14:56:27.166274122+01:00",
"UpdatedAt": "2021-12-06T14:56:27.17309057+01:00",
"DeletedAt": null,
"Username": "martin",
"Password": "secret",
"Bookings": [
{
"ID": 1,
"CreatedAt": "2021-12-06T14:56:27.173323491+01:00",
"UpdatedAt": "2021-12-06T14:56:27.173323491+01:00",
"DeletedAt": null,
"TableID": null,
"Table": {
"ID": 0,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"Number": 0,
"Price": 0
},
"OwnerID": 1,
"Time": 1234,
"OwnerType": "users"
}
]
}

Related

How to create Category Tree in GoLang

I have a struct like that.
type Category struct {
ID int `json:"id"`
MetaDescription string `json:"metaDescription" gorm:"column:metadescription"`
MetaTitle string `json:"metaTitle" gorm:"column:metatitle"`
Name string `json:"name" gorm:"column:categoryname"`
Order int `json:"order" gorm:"column:categoryorder"`
ParentId int `json:"parentId" gorm:"column:parentid"`
Rank float64 `json:"rank" gorm:"column:rank"`
Url string `json:"url" gorm:"column:catname4seo"`
}
I'm fetching this data from a database and I want to manipulate it to create a category tree. My tree would be like that
type CategoryTree struct {
ID int `json:"id"`
Name string `json:"name" gorm:"column:categoryname"`
Url string `json:"url" gorm:"column:catname4seo"`
SubCategories []CategoryTree `json:"subCategories"`
}
Normally, I am a JavaScript dev and a newbie in GoLang so especially static type made me harder. That's why I couldn't achieve it. Can you help me to do so?
Thanks in advance.
I think this is an algorithm question. Just iterating over the Category list, and push elements into CategoryTree. Go code like this:
package main
import (
"encoding/json"
"fmt"
)
type Category struct {
ID int `json:"id"`
MetaDescription string `json:"metaDescription" gorm:"column:metadescription"`
MetaTitle string `json:"metaTitle" gorm:"column:metatitle"`
Name string `json:"name" gorm:"column:categoryname"`
Order int `json:"order" gorm:"column:categoryorder"`
ParentId int `json:"parentId" gorm:"column:parentid"`
Rank float64 `json:"rank" gorm:"column:rank"`
Url string `json:"url" gorm:"column:catname4seo"`
}
// load db data by CategoryTree instead of Category
type CategoryTree struct {
Category
SubCategories []*CategoryTree `json:"subCategories"`
}
func main() {
input := []*CategoryTree{
{
Category: Category{
ID: 1,
ParentId: 0,
},
},
{
Category: Category{
ID: 2,
ParentId: 1,
},
},
{
Category: Category{
ID: 3,
ParentId: 1,
},
},
{
Category: Category{
ID: 4,
ParentId: 2,
},
},
}
tree := makeTree(input)
output, _ := json.MarshalIndent(tree, "", " ")
fmt.Printf("%s\n", string(output))
}
func makeTree(list []*CategoryTree) *CategoryTree {
dataMap := make(map[int]*CategoryTree, len(list))
var rootNode *CategoryTree
for i := range list {
// root node has min ParentID
if rootNode == nil || rootNode.ParentId > list[i].ParentId {
rootNode = list[i]
}
// list to map
dataMap[list[i].ID] = list[i]
}
// build tree
for i := range list {
parentNode, ok := dataMap[list[i].ParentId]
if !ok {
continue
}
if parentNode.SubCategories == nil {
parentNode.SubCategories = []*CategoryTree{}
}
parentNode.SubCategories = append(parentNode.SubCategories, list[i])
}
return rootNode
}
and output:
{
"id": 1,
"metaDescription": "",
"metaTitle": "",
"name": "",
"order": 0,
"parentId": 0,
"rank": 0,
"url": "",
"subCategories": [
{
"id": 2,
"metaDescription": "",
"metaTitle": "",
"name": "",
"order": 0,
"parentId": 1,
"rank": 0,
"url": "",
"subCategories": [
{
"id": 4,
"metaDescription": "",
"metaTitle": "",
"name": "",
"order": 0,
"parentId": 2,
"rank": 0,
"url": "",
"subCategories": null
}
]
},
{
"id": 3,
"metaDescription": "",
"metaTitle": "",
"name": "",
"order": 0,
"parentId": 1,
"rank": 0,
"url": "",
"subCategories": null
}
]
}

How to get particular fields in Preload while using Gorm

I have two models which have many to many relations.
type User struct {
ID uint `gorm:"primary_key" json:"id"`
Name string `json:"name"`
Surname string `json:"surname"`
Email string `json:"email"`
Password string `json:"password"`
RoleID uint `json:"role_id"`
Classes []*Class `gorm:"many2many:user_classes;" json:"classes"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type Class struct {
ID uint `gorm:"primary_key" json:"id"`
Name string `json:"name"`
Homeworks []Homework `gorm:"foreignKey:ClassID" json:"homeworks"`
Materials []Material `gorm:"foreignKey:ClassID" json:"materials"`
Users []*User `gorm:"many2many:classes;" json:"users"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
I want to get users with their classes but with only class id and class name. I did a lot of research and found this solution. However, it didn't work for me. I don't know why. Everything seems fine but didn't work.
My repository function's body is like that
var users []models.User
err := repo.db.Preload("Classes", func(db *gorm.DB) *gorm.DB {
return db.Select("ID", "Name")
}).Find(&users).Error
return users, err
I am expecting results like that
{
"id": 4,
"name": "Furkan",
"surname": "Topaloğlu",
"email": "furkantopalogluu#gmail.com",
"password": "fuki123",
"role_id": 2,
"classes": [
{
"id": 1,
"name": "IELTS Group 1"
},
{
"id": 2,
"name": "Speaking Club Grup 4"
}
],
"created_at": "2022-09-20T02:54:07.185756+03:00",
"updated_at": "2022-09-20T02:54:07.185756+03:00"
}
However, I'm getting this :
{
"id": 4,
"name": "Furkan",
"surname": "Topaloğlu",
"email": "furkantopalogluu#gmail.com",
"password": "fuki123",
"role_id": 2,
"classes": [
{
"id": 1,
"name": "IELTS Group 1",
"homeworks": null,
"materials": null,
"users": null,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": 2,
"name": "Speaking Club Grup 4",
"homeworks": null,
"materials": null,
"users": null,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
}
],
"created_at": "2022-09-20T02:54:07.185756+03:00",
"updated_at": "2022-09-20T02:54:07.185756+03:00"
}
What am I doing wrong? Please help me.

GORM .Save don't save "has one" relation to the database

I have struct:
type Book struct {
gorm.Model
Title string `json:"title"`
Author string `json:"author"`
Description string `json:"description"`
Category string `json:"Category"`
Publisher string `json:"publisher"`
AuthorsCard AuthorsCard `gorm:"foreignKey:BookID" json:"authorscard"`
}
type AuthorsCard struct {
//gorm.Model // I purposely Don't want to use gorm.model
ID uint `gorm:"primarykey"`
BookID uint
Name string `json:"name"`
Age int `json:"age"`
YearOfBirth int `json:"year"`
Biography string `json:"biography"`
}
And I'm trying to make update function:
// controller.go
func UpdateBook(ctx *gin.Context) {
enableCors(&ctx.Writer)
id := ctx.Param("ID")
var updateBook = &models.Book{}
if err := ctx.BindJSON(updateBook); err != nil {
ctx.AbortWithStatus(http.StatusBadRequest)
log.Fatal(err)
} else {
repository.UpdateBook(updateBook, id)
ctx.JSON(http.StatusOK, updateBook)
log.Println(updateBook)
}
}
//repository.go
func UpdateBook(updateBook *models.Book, ID string) {
book, db := GetBookById(ID)
if updateBook.Title != "" {
book.Title = updateBook.Title
}
if updateBook.Author != "" {
book.Author = updateBook.Author
}
if updateBook.Publisher != "" {
book.Publisher = updateBook.Publisher
}
if updateBook.Description != "" {
book.Description = updateBook.Description
}
if updateBook.Category != "" {
book.Category = updateBook.Category
}
if updateBook.AuthorsCard.Name != "" {
book.AuthorsCard.Name = updateBook.AuthorsCard.Name
}
if updateBook.AuthorsCard.Age != 0 {
book.AuthorsCard.Age = updateBook.AuthorsCard.Age
}
if updateBook.AuthorsCard.YearOfBirth != 0 {
book.AuthorsCard.YearOfBirth = updateBook.AuthorsCard.YearOfBirth
}
if updateBook.AuthorsCard.Biography != "" {
book.AuthorsCard.Biography = updateBook.AuthorsCard.Biography
}
db.Save(&book)
// same with db.Preload("AuthorsCard").Save(&book)
}
The issue is: When I make an PUT request, I receive Fully updated data. And when I'm trying to make GET request all my fields, except related AuthorsCard, are been updated.
PUT response:
200 Code
{
"ID": 0,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"title": "Test",
"author": "author",
"description": "something",
"Category": "Category",
"publisher": "PB",
"authorscard": {
"ID": 0,
"BookID": 0,
"name": "Updated",
"age": 22,
"year": 1999,
"biography": "biography Updated"
}
}
Get response after that:
[
{
"ID": 1,
"CreatedAt": "2022-06-29T14:57:37.489639+03:00",
"UpdatedAt": "2022-06-29T15:50:11.578724+03:00",
"DeletedAt": null,
"title": "Test",
"author": "author",
"description": "something",
"Category": "Category",
"publisher": "PB",
"authorscard": {
"ID": 1,
"BookID": 1,
"name": "test",
"age": 23,
"year": 1999,
"biography": "23fdgsdddTEST"
}
}
]
As you can see "authorscard" hasn't changed. Please, can someone tell me what I'm doing wrong?
You need to explicitly tell Gorm to store/update associations.
db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&book)

How to build a structure tree from a slice of struct?

here are my initial model
type Team struct {
Id string `json:"id"`
Name string `json:"name"`
Level int64 `json:"level"`
}
type TeamTree struct {
Teams []Team `json:"teams"`
Child []TeamTree `json:"child"`
}
Sample data:
id team level
a BOD 1
b Marketing 2
c Dev 2
d Worker 3
I want to have this result (should be a mapping):
{
"teams": [
{"id": "a", "team": "BOD", "level": 1}
],
"child": {
"teams": [
{"id":"b", "team": "marketing", "level": 2},
{"id":"c", "team": "marketing", "level": 2}
],
"child": {
"teams": [
{"id": "d", "team": "worker", "level": 3}
],
"child"": {}
}
}
}
I was told to do self loop, but I do not know how to do it, since I keep getting error and my code cant even run.
This is a full example of what you want
package main
import "log"
type Team struct {
id string
name string
level int64
}
type TeamTree struct {
teams []Team
children []TeamTree
}
func main() {
firstChild := TeamTree{teams: []Team{{id: "firstChildId", name: "First", level: 10}}, children: make([]TeamTree, 0)}
secondChild := TeamTree{teams: []Team{{id: "secondChildId", name: "Second", level: 20}}, children: make([]TeamTree, 0)}
thirdChild := TeamTree{teams: []Team{{id: "thirdChildId", name: "Third", level: 30}}, children: make([]TeamTree, 0)}
rootTeamTree := TeamTree{teams: []Team{{id: "rootId", name: "Root", level: 100}}, children: []TeamTree{firstChild, secondChild, thirdChild}}
log.Println("What's the second child's level:", rootTeamTree.children[1].teams[0].level)
}
And this is the result
2022/06/19 08:24:55 What's the second child's level: 20

Golang group and merge by value in goroutine

I'm new in go and tried to populate slice data by same values in GO.
Refer to the following example
input struct {
ID string `json:"id"`
Name string `json:"name"`
Image string `json:"image"`
}
output struct {
ID string `json:"id"`
Name string `json:"name"`
Image []img `json:"image"`
}
img struct {
Name string `json:"name"`
Width int `json:"width"`
Height int `json:"height"`
}
input = [{
"id": 10,
"name": "product 10",
"image": {"name": "https://i.imgur.com/eKSk6Fq.jpg"}
}, {
"id": 10,
"name": "product 10",
"image": {"name": "https://i.imgur.com/np1wmxw.jpg"}
}, {
"id": 11,
"name": "product 11",
"image": {"name": "https://i.imgur.com/jlFgGpe.jpg"}
}, {
"id": 11,
"name": "product 11",
"image": {"name": "https://i.imgur.com/B0D4iRk.jpg"}
}, {
"id": 11,
"name": "product 11",
"image": {"name": "https://i.imgur.com/4AiXzf8.jpg"}
}]
// expected output
output = [{
"id": 10,
"name": "product 10",
"image": [{
"name": "https://i.imgur.com/eKSk6Fq.jpg",
"width": 900,
"height": 600
}, {
"name": "https://i.imgur.com/np1wmxw.jpg",
"width": 600,
"height": 600
}]
}, {
"id": 11,
"name": "product 11",
"image": [{
"name": "https://i.imgur.com/jlFgGpe.jpg",
"width": 639,
"height": 700
}, {
"name": "https://i.imgur.com/B0D4iRk.jpg",
"width": 1280,
"height": 960
}, {
"name": "https://i.imgur.com/4AiXzf8.jpg",
"width": 540,
"height": 405
}]
}]
I would like to group input to a new slice based on the same ID,
so the result output would be new slice of new struct with grouped image with same ID.
H̶o̶w̶ ̶w̶o̶u̶l̶d̶ ̶I̶ ̶a̶c̶h̶i̶e̶v̶e̶d̶ ̶t̶h̶e̶ ̶̶o̶u̶t̶p̶u̶t̶̶ ̶r̶e̶s̶u̶l̶t̶ ̶u̶s̶i̶n̶g̶ ̶G̶O̶? update: got the answer from Peter Eichelsheim
Also, if I had to ge image size in the input with http.get and want to use goroutine, how would I achieve the result? since my last code here playground not achieving the correct output (always get the last input)
note: I don't know why I get null in go playground, but in my laptop the result is: [{"id":11,"name":"product 11","image":[{"name":"https://i.imgur.com/B0D4iRk.jpg","width":1280,"height":960}]}]
In PHP, I would do something below to achieve the intended output.
foreach ($input as $key => $value) {
if (!isset($output[$value["id"]])) {
$output[$value["id"]] = [
"id" => $value["id"],
"name" => $value["name"],
"image" => [],
];
}
$get = getimagesize($value["image"]["name"]);
if ($get) {
$width = isset($get[0]) ? $get[0] : 0;
$height = isset($get[1]) ? $get[1] : 0;
}
$output[$value["id"]]["image"][$key] = [
"name" => $value["image"]["name"],
"width" => $width,
"height" => $height,
];
$output[$value["id"]]["image"] = array_values($output[$value["id"]]["image"]);
}
$output = array_values($output);
$json = json_encode($output, true);
echo $json;
Thanks
Here a little sample with sample json input, using map[int]output to club images into the same product ID.
package main
import (
"encoding/json"
"fmt"
"log"
)
type input struct {
ID int `json:"id"`
Name string `json:"name"`
Image img `json:"image"`
}
type output struct {
ID int `json:"id"`
Name string `json:"name"`
Image []img `json:"image"`
}
type img struct {
Name string `json:"name"`
}
func main() {
var jsoninput = []byte(`
[{
"id": 10,
"name": "product 10",
"image": {"name": "image 10a"}
}, {
"id": 10,
"name": "product 10",
"image": {"name": "image 10b"}
}, {
"id": 11,
"name": "product 11",
"image": {"name": "image 11a"}
}, {
"id": 11,
"name": "product 11",
"image": {"name": "image 11b"}
}, {
"id": 11,
"name": "product 11",
"image": {"name": "image 11c"}
}]`)
var inputs []input
err := json.Unmarshal(jsoninput, &inputs)
if err != nil {
log.Fatalln("could not Unmarshal:", err)
}
var outputlist = make(map[int]output)
for _, inp := range inputs {
outputlist[inp.ID] = output{inp.ID, inp.Name, append(outputlist[inp.ID].Image, inp.Image)}
}
var outputs []output
for _, outp := range outputlist{
outputs = append(outputs,outp)
}
jsonoutput, err := json.Marshal(outputs)
fmt.Println(string(jsonoutput))
}
var inputs []input // assuming the json has been unmarshalled correctly
outs := make(map[int]output)
// this will create a map, keyed by id, values are groups of inputs
for _, input := range inputs {
out, exists := outs[input.ID]
if !exists {
out = output{
ID: input.ID,
Name: input.Name,
}
}
out.Image = append(out.Image, img{Name: input.Name})
}
output := make([]output, len(outs))
var idx int
for key, out := range outs {
output[idx] = out
idx++
}

Resources