GORM: Preloading not working with nested associations? - go

I'm trying to preload association ProfileData on Profile, where Profile is a collection type in ProfileCollection model. It loads the profiles / members of the collection, although not the inner associations of Profile such as ProfileData?
Code:
var collections []ProfileCollection
var error = database.
Model(ProfileCollection{}).
Preload(clause.Associations).
Find(&collections).
Error
if error != nil {
log.Fatalln(error)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(collections)
I've also tried loading them like so but nothing changed:
.Preload("Members.ProfileData")
JSON Output:
{
"id": 1,
"name": "Test 1",
"description": "Just one from the seeder.",
"members": [
{
"id": 10,
"collection_id": 1,
"path": "/",
"created_at": "2021-09-05T01:40:06Z",
"updated_at": "2021-09-05T01:40:06Z",
"collection": {
"id": 0,
"name": "",
"description": "",
"members": null
},
"profile_data": {
"id": 0,
"profile_id": 0,
"name": "",
"username": "",
"picture": "",
"bio": "",
"is_private": false,
"other_data": null,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
"media_items": null,
"origin": null,
},
]
}
Structs:
type ProfileCollection struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Members []Profile `gorm:"foreignKey:collection_id" json:"members"`
}
type Profile struct {
ID int64 `json:"id" gorm:"primary_key"`
CollectionId int64 `json:"collection_id"`
Path string `json:"path"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ProfileData ProfileData `gorm:"foreignKey:profile_id" json:"profile_data"`
MediaItems []ProfileMediaItem `gorm:"foreignKey:profile_id" json:"media_items"`
Origin *Profile `gorm:"foreignKey:origin_id" json:"origin"`
}
type ProfileData struct {
ID int64 `json:"id" gorm:"primary_key"`
ProfileId int64 `json:"profile_id"`
Name string `json:"name"`
Username string `json:"username"`
Picture string `json:"picture"`
Bio string `json:"bio"`
IsPrivate bool `json:"is_private"`
OtherData datatypes.JSON `json:"other_data"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

Related

When the first stage is array, How to handle with go-simplejson

JSON struct like below:
[
{
"sha": "eb08dc1940e073a5c40d8b53a5fd58760fde8f27",
"node_id": "C_kwDOHb9FrtoAKGViMDhkYzE5NDBlMDczYTVjNDBkOGI1M2E1ZmQ1ODc2MGZkZThmMjc",
"commit": {
"author": {
"name": "xxxx"
},
"committer": {
"name": "xxxxx"
},
"message": "update DownLoad_Stitch_ACM.py",
"tree": {
"sha": "a30aab98319846f0e86da4a39ec05786e04c0a4f",
"url": "xxxxx"
},
"url": "xxxxx",
"comment_count": 0,
"verification": {
"verified": false,
"reason": "unsigned",
"signature": null,
"payload": null
}
},
"url": "xxxxx",
"html_url": "xxxxx",
"comments_url": "xxxxx",
"author": {
"login": "xxxxx",
"id": "xxxxx",
"node_id": "U_kgDOBkuicQ",
"avatar_url": "https://avatars.githubusercontent.com/u/105620081?v=4",
"gravatar_id": "",
"type": "User",
"site_admin": false
},
"committer": {
"login": "xxxxx",
"id": "xxxxx"
},
"parents": [
{
"sha": "cf867ec9dc4b904c466d9ad4b9338616d1213a06",
"url": "xxxxx",
"html_url": "xxxxx"
}
]
}
]
I don't know how to get the location 0's data.
content, _ := simplejson.NewJson(body)
arr, _ := content.Array() // Here can get the all data, It's []interface{} type.
I cannot get the next data with arr[0]["sha"]. How to handle it?
It is not clear to the compiler that arr is an array of map[string]interface{} at compile time, as arr[0] is of type interface{}. This basically means that the compiler knows nothing about this type, which is why you can't do a map lookup operation here.
You can add a type assertion to make sure you can use it as a map like this:
asMap := arr[0].(map[string]interface{})
fmt.Println(asMap["sha"])
To get the SHA as string, you can again add a type assertion behind it as well:
asString := asMap["sha"].(string)
This is also shown in this working example. The downside of this is that your program will panic in case the given data is not of the specified type. You could instead use a type assertion with a check if it worked (asString, ok := ...), but it gets cumbersome with more complex data.
This does work, but isn't really nice. I would recommend using a tool like this to generate Go structs and then use them in a type-safe way. First define a struct with all the info you need:
type ArrayElement struct {
Sha string `json:"sha"`
// Add more fields if you need them
}
Then you can just use the standard-library json package to unmarshal your data:
// This should have the same structure as the data you want to parse
var result []ArrayElement
err := json.Unmarshal([]byte(str), &result)
if err != nil {
panic(err)
}
fmt.Println(result[0].Sha)
Here is an example for that -- this is a more Go-like approach for converting JSON data.
Your json data is wrong formatted. First of all, remove , after "id": "xxxxx", line:
...
"id": "xxxxx"
...
You should check errors after NewJson to prevent find out if there is a problem:
content, err := simplejson.NewJson(body)
if err != nil {
// log err
}
For getting sha from first index, you simply can use simplejson built-in methods:
shaVal := content.GetIndex(0).Get("sha").String()
Here is how you can get the desired value.
This worked for me.
package main
import (
"encoding/json"
"fmt"
)
type MyData []struct {
Sha string `json:"sha"`
NodeID string `json:"node_id"`
Commit struct {
Author struct {
Name string `json:"name"`
} `json:"author"`
Committer struct {
Name string `json:"name"`
} `json:"committer"`
Message string `json:"message"`
Tree struct {
Sha string `json:"sha"`
URL string `json:"url"`
} `json:"tree"`
URL string `json:"url"`
CommentCount int `json:"comment_count"`
Verification struct {
Verified bool `json:"verified"`
Reason string `json:"reason"`
Signature interface{} `json:"signature"`
Payload interface{} `json:"payload"`
} `json:"verification"`
} `json:"commit"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
CommentsURL string `json:"comments_url"`
Author struct {
Login string `json:"login"`
ID string `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
} `json:"author"`
Committer struct {
Login string `json:"login"`
ID string `json:"id"`
} `json:"committer"`
Parents []struct {
Sha string `json:"sha"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
} `json:"parents"`
}
func main() {
my_json_data := `[
{
"sha": "eb08dc1940e073a5c40d8b53a5fd58760fde8f27",
"node_id": "C_kwDOHb9FrtoAKGViMDhkYzE5NDBlMDczYTVjNDBkOGI1M2E1ZmQ1ODc2MGZkZThmMjc",
"commit": {
"author": {
"name": "xxxx"
},
"committer": {
"name": "xxxxx"
},
"message": "update DownLoad_Stitch_ACM.py",
"tree": {
"sha": "a30aab98319846f0e86da4a39ec05786e04c0a4f",
"url": "xxxxx"
},
"url": "xxxxx",
"comment_count": 0,
"verification": {
"verified": false,
"reason": "unsigned",
"signature": null,
"payload": null
}
},
"url": "xxxxx",
"html_url": "xxxxx",
"comments_url": "xxxxx",
"author": {
"login": "xxxxx",
"id": "xxxxx",
"node_id": "U_kgDOBkuicQ",
"avatar_url": "https://avatars.githubusercontent.com/u/105620081?v=4",
"gravatar_id": "",
"type": "User",
"site_admin": false
},
"committer": {
"login": "xxxxx",
"id": "xxxxx"
},
"parents": [
{
"sha": "cf867ec9dc4b904c466d9ad4b9338616d1213a06",
"url": "xxxxx",
"html_url": "xxxxx"
}
]
}]`
var data MyData
err := json.Unmarshal([]byte(my_json_data), &data)
if err != nil {
panic(err)
}
fmt.Println("data --> sha: ", data[0].Sha)
}

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.

Relational Database Resulting in Loop

I have the following hierarchy, Users -> Maps -> Elements -> Posts
A user can have a bunch of maps, each map will have a number of elements and each element will have a number of posts.
type User struct {
UserID uint `gorm:"primarykey;autoIncrement;not_null"`
UserName string `json: user_name`
FirstName string `json: first_name`
LastName string `json: last_name`
Email string `json:email`
Password []byte `json:"-"`
Phone string `json:"phone"`
Maps []Map `gorm:"-"`
}
type Map struct {
MapID uint `gorm:"primarykey;autoIncrement;not_null"`
UserID uint `json:userid`
User User `json:"user"; gorm:"foreignkey:UserID`
Title string `json:title`
Desc string `json: "desc"`
Elements []Element `gorm:"foreignKey:MapID"`
Date time.Time `json: date`
}
type Element struct {
ElementID uint `gorm:"primarykey;autoIncrement;not_null"`
ElementName string `json: element_name`
Desc string `json: desc`
MapID uint `json:mapid`
Map Map `json:"map"; gorm:"foreignkey:MapID`
Posts []Post `gorm:"foreignKey:ElementID"`
Date time.Time `json: date`
UserID uint `json:userid`
User User `json:"user"; gorm:"foreignkey:UserID`
}
type Post struct {
PostId uint `gorm:"primarykey;autoIncrement;not_null"`
Title string `json: p_title`
Subject string `json: subject`
Date time.Time `json: date`
Entry string `json: entry_text`
ElementID uint `json:elementid`
Element Element `json:"element"; gorm:"foreignkey:ElementID`
UserID uint `json:userid`
User User `json:"user"; gorm:"foreignkey:UserID`
}
This all seems to work fine, but now when I send the JSON response from the backend there seems to be potential for an infinite loop.
When I retrieve all of a user's maps, it then lists the user object relating to the user that created the map, but the map then also includes a list of elements and within the element object it is going to list the map it belongs to and that map object will again list all of it's elements.
So should I be handling this by just preloading the hierarchy in one direction?
var getmaps []models.Map
database.DB.Preload("User").Preload("Map").Preload("Elements").Offset(offset).Limit(limit).Find(&getmaps)
Or should I be fixing the struct and gorm settings to only get relationships in one direction? Since returning a map will return it's elements and each element will return the map it belongs to which loops back to its elements etc.
This loop would also happen with Elements and posts where an element will have many posts, those post objects would display their element and that element object would display its posts.
I'm sure there is an ideal or optimum way to implement this so just curious what people would recommend.
Example calling one map with the following preloads
func DetailMap(c *fiber.Ctx) error {
id,_ := strconv.Atoi(c.Params("id"))
fmt.Println(id)
var smap models.Map
database.DB.Where("map_id=?", id).Preload("User").Preload("Map").Preload("Elements.Posts").First(&smap)
return c.JSON(fiber.Map{
"data":smap,
})
}
"data": {
"MapID": 1,
"UserID": 1,
"user": {
"UserID": 1,
"UserName": "Chris",
"FirstName": "Chris",
"LastName": "XxxXxxxx",
"Email": "xxxxx#gmail.com",
"phone": "123-456-6789",
"Maps": null
},
"Title": "My Map",
"Desc": "This is the subject",
"Elements": [
{
"ElementID": 1,
"ElementType": "BASE",
"ElementName": "Identity",
"BriefDesc": "This is the identity ",
"Desc": "In publishing and graphic design
"ParentId": "",
"NumElements": 0,
"NumEntries": 0,
"MapID": 1,
"map": {
"MapID": 0,
"UserID": 0,
"user": {
"UserID": 0,
"UserName": "",
"FirstName": "",
"LastName": "",
"Email": "",
"phone": "",
"Maps": null
},
"Title": "",
"Desc": "",
"Elements": null,
"Date": "0001-01-01T00:00:00Z"
},
"Notes": null,
"Questions": null,
"Posts": [
{
"PostId": 1,
"Title": "First Post",
"Subject": "This is the subject",
"Date": "2022-04-11T12:35:55.267-03:00",
"Entry": "This is the Entry",
"ElementID": 1,
"element": {
"ElementID": 0,
"ElementType": "",
"ElementName": "",
"BriefDesc": "",
"Desc": "",
"ParentId": "",
"NumElements": 0,
"NumEntries": 0,
"MapID": 0,
"map": {
"MapID": 0,
"UserID": 0,
"user": {
"UserID": 0,
"UserName": "",
"FirstName": "",
"LastName": "",
"Email": "",
"phone": "",
"Maps": null
},
"Title": "",
"Desc": "",
"Elements": null,
"Date": "0001-01-01T00:00:00Z"
},
"Notes": null,
"Questions": null,
"Posts": null,
"Date": "0001-01-01T00:00:00Z",
"UserID": 0,
"user": {
"UserID": 0,
"UserName": "",
"FirstName": "",
"LastName": "",
"Email": "",
"phone": "",
"Maps": null
}
},
"UserID": 1,
"user": {
"UserID": 0,
"UserName": "",
"FirstName": "",
"LastName": "",
"Email": "",
"phone": "",
"Maps": null
}
}
],
"Date": "2022-04-11T11:31:01.72-03:00",
"UserID": 1,
"user": {
"UserID": 0,
"UserName": "",
"FirstName": "",
"LastName": "",
"Email": "",
"phone": "",
"Maps": null
}
},`
In your Post, Map and Element structs you have the fields:
UserID uint `json:userid`
User User `json:"user"; gorm:"foreignkey:UserID`
You should remove the User field from your content structs because you already have a UserID. A "reference" (ID) in this case is more sensible than including the whole user object. The client can call a /users/{id} endpoint and find more info if needed.
Also limit the content of the User struct by removing Maps []Map (responsible for the loop you mentioned). You would then need to set up endpoints like /user/{id}/maps so the client can get the user's content.
The same applies for Post and Element. You could go all-out and store only IDs, or you can store an array of only "child" models. (Map embeds Element, Element DOES NOT embed Map). So to find the associated map of an element, you would call endpoint /maps/{your element's map ID}. same for Element > Post
type Map struct {
gorm.Model // this takes care of the ID field
UserID uint `json:userid`
Title string `json:title`
Desc string `json: "desc"`
Elements []Element // gorm will handle the relationship automatically
Date time.Time `json: date`
}
type Element struct {
gorm.Model // includes ID
ElementName string `json: element_name`
Desc string `json: desc`
MapID uint `json:mapid`
// Map Map ... This relationship is described by another endpoint - /elements/{elementId}/map to get the related map
Posts []Post // gorm handles this
Date time.Time `json: date`
UserID uint `json:userid`
}
type Post struct {
gorm.Model
Title string `json: p_title`
Subject string `json: subject`
Date time.Time `json: date`
Entry string `json: entry_text`
ElementID uint `json:elementid` // gorm will use this as fk
UserID uint `json:userid`
}
To avoid loops you will need to make the relationships one-directional at a struct level, and set up more http routes to go in the other direction (see commented code).
What I described is a simple REST api. Microsoft has a nice overview: https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design#organize-the-api-design-around-resources - specifically the customer/order relationship will interest you.
On the gorm side you would be using one-to-many associations: https://gorm.io/docs/has_many.html

Trying to format response for API but i don't know how

hello developers i recently hit a wall trying to create slightly advance query with many 2 many relationship and belongs to relationship and i was successfully fetching them all with GORM , but i want to format the response to my won structure and i can't do it here my query i gorm
var p []modules.Posts
tm := modules.Tags{}
db.First(&tm , tagID)
//tag and posts has many 2 many rel
//post belongs to user
tagMaps := db.Model(&tm).Preload("TagMaps").Preload("User").Related(&p,"Posts")
i'm trying to format the response to something like this
[
{
"id": "post_id",
"title": "post_title",
"image" :" post_image",
"tags":[
{"name" : "tag_name" , "id" : "tag_id"},
{"name" : "tag_name" , "id" : "tag_id"},
{"name" : "tag_name" , "id" : "tag_id"}
],
"user":{
"id": "user_id",
"avatar": " user_avatar"
}
}
]
this what the gorm response look like right know
{
"id": 2,
"title": "thos lore for other users lorem 2",
"image": "",
"User": {
"ID": 1,
"avatar": "url",
"user_name": "user 5",
"email": "newadmin#test.com",
"password": "password",
"status": "pea",
"google_id": "google",
"facebook_id": "face",
"account_type": "fb",
"CreatedAt": "2020-04-02T20:35:38+02:00",
"UpdatedAt": "2020-04-02T20:35:38+02:00",
"DeletedAt": null,
"Posts": null
},
"UserRefer": 1,
"created_at": "2020-04-02T20:36:05+02:00",
"updated_at": "2020-04-02T20:36:05+02:00",
"deleted_at": null,
"TagMaps": [{
"id": 1,
"tag_name": "asda",
"CreatedAt": "2020-04-15T23:25:05+02:00",
"UpdatedAt": "2020-04-17T21:39:26+02:00",
"DeletedAt": null,
"Posts": null
},
{
"id": 2,
"tag_name": "name",
"CreatedAt": "2020-04-15T23:25:05+02:00",
"UpdatedAt": "2020-04-17T21:39:26+02:00",
"DeletedAt": null,
"Posts": null
}
]
},
this my current structs
type Posts struct {
ID uint `json: "id"`
Title string `json: "title"`
Image string `json: "image"`
User *User `gorm: "foreignkey:UserRefer"` // use UserRefer as foreign key
UserRefer uint
CreatedAt time.Time `json: "created_at"`
UpdatedAt time.Time `json: "updated_at"`
DeletedAt *time.Time `sql: "index" json: "deleted_at"`
TagMaps []Tags `gorm: "many2many:tag_maps;association_autoupdate:false;association_autocreate:false;"`
}
type Tags struct {
ID uint `gorm: "primary_key" json: "id"`
Name string `json: "tag_name"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql: "index"`
Posts []*Posts `gorm: "many2many:tag_maps"`
}
type User struct {
ID uint `gorm: "primary_key"`
Avatar string `json: "avatar" `
UserName string `json: "user_name"`
Password string `json: "password"`
Bio string `json: "bio"`
GoogleID string `json: "google_id"`
FacebookID string `json: "facebook_id"`
AccountType string `json: "account_type"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql: "index"`
// Relationship
Posts *Posts `gorm: "PRELOAD:false"`
}
any advice will be appropriated and my level in go isn't that good i started playing with like month ago
Not sure what you trying to achieve but GORM response is not so far from the one you need.
Can you try to add more encoding json tag to your structs, ie:
to ignore the "CreatedAt" field
CreatedAt time.Time `json:"-"`
to transform "User" into "user" (works for TagMaps too)
User *User `gorm: "foreignkey:UserRefer" json:"user"`
By the way, there is also json:",omitempty" to ignore empty field
https://golang.org/pkg/encoding/json/#Marshal

How to make query result structure match to what i've been declared on GORM Select

I want to make the structure of the query results match what I have stated in GORM Select because right now it only matches the Struct structure. How do i make it work? Thank you in advance
i've tried to make new Struct and it works, but i dont know is it a best practice or not
type User struct {
User_Id uint `json:"user_id" gorm:"column:user_id; PRIMARY_KEY"`
Email string `json:"email"`
Password string `json:"password"`
Token string `json:"token" gorm:"-"`
}
func GetUsers() map[string]interface{} {
users := []User{}
GetDB().Table("app_user").Select("user_id, email").Find(&users)
resp := u.Message(true, "All users")
resp["users"] = users
return resp
}
//actual result
{
"message": "All users",
"status": true,
"users": [
{
"user_id": 1732,
"email": "aaaaaaa#gmail.com",
"password": "",
"token": ""
},
{
"user_id": 1733,
"email": "bbbbbbb#gmail.com",
"password": "",
"token": ""
},
]
}
//Expected result
{
"message": "All users",
"status": true,
"users": [
{
"user_id": 1732,
"email": "aaaaaaa#gmail.com"
},
{
"user_id": 1733,
"email": "bbbbbbb#gmail.com"
}
]
}
It looks like all you need to do is to omit empty fields. You can do that by adding omitempty to json tags:
Password string `json:"password,omitempty"`
Token string `json:"token,omitempty" gorm:"-"`

Resources