Golang Gorm reverse hasMany relation - go

We can Easy to get the child of hasMany relation in Golang Gorm with Preload.
But how to get reverse of relation.
type Owner struct {
ID int `gorm:"column:id" json:"id"`
Name string `gorm:"column:name" json:"name"`
Projects []Project `gorm:"foreignkey:OwnerID" json:"projects"`
}
type Project struct {
ID int `gorm:"column:id" json:"id"`
Name string `gorm:"column:name" json:"name"`
OwnerID int `gorm:"column:owner_id" json:"owner_id"`
Gallery []Gallery `gorm:"foreignkey:ProjectID" json:"gallery"`
}
type Gallery struct {
ID int `gorm:"column:id" json:"id"`
ProjectID int `gorm:"column:project_id" json:"project_id"`
Url string `gorm:"column:url" json:"url"`
Title string `gorm:"column:title" json:"title"`
Description string `gorm:"column:description" json:"description"`
}
we can populate Gallery inside Project with Preload, like this:
db.Preload("Gallery").Find(&project)
How to get reverse, we want load project from gallery or owner from project?
the result I want some thing like this, when get project in form of json:
{
"id": 1,
"name": "Name Of Project",
"owner": {
"id":1,
"name": "Owner 1"
},
"gallery":[]
}

I am a bit late to the game with this answer, but you can define inverse relationships (belongs to) on the child models by doing this:
type ParentModel struct {
Children []Child // This is a 'has many'
}
type Child struct {
ParentModelID int // These behave as a 'belongs to'
ParentModel ParentModel // You need both the model and the modelID
}
Full working example
This uses the models from your question. A note on performance, the preloading in gorm isn't done via joins. It's done via additional SQL queries, the more nesting the more queries. If you have commonly executed preloading, it could be worth writing a raw query and a customer scanner to use an optimised query.
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Owner struct {
gorm.Model
Projects []Project
}
type Project struct {
gorm.Model
OwnerID int
Owner Owner
Gallery []Gallery
}
type Gallery struct {
gorm.Model
ProjectID int
Project Project
}
func main() {
db, err := gorm.Open(sqlite.Open("many2many.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
err = db.AutoMigrate(&Owner{}, &Project{}, &Gallery{})
if err != nil {
return
}
ownerOne := Owner{}
db.Create(&ownerOne)
projectOne := Project{Owner: ownerOne}
projectTwo := Project{Owner: ownerOne}
db.Create(&projectOne)
db.Create(&projectTwo)
galleryOne := Gallery{Project: projectOne}
galleryTwo := Gallery{Project: projectOne}
galleryThree := Gallery{Project: projectTwo}
galleryFour := Gallery{Project: projectTwo}
db.Create(&galleryOne)
db.Create(&galleryTwo)
db.Create(&galleryThree)
db.Create(&galleryFour)
// Find by project and preload owners
fetchedProject := Project{}
db.Preload("Owner").Find(&fetchedProject, projectOne.ID)
fmt.Println(fetchedProject.Owner)
// Find by a gallery and preload project and owner
fetchedGallery := Gallery{}
db.Preload("Project.Owner").Find(&fetchedGallery, galleryOne.ID)
fmt.Printf("Gallery.id = %d --> Project.id = %d --> Owner.id = %d\n", fetchedGallery.ID,
fetchedGallery.Project.ID, fetchedGallery.Project.Owner.ID)
}

Related

Get data from two different struct

Have this struct of User and Post and I try to make Name from User to be included within Post Struct when a user create a new post.
type User struct {
ID int
Name string
Created time.Time
}
type Post struct {
ID int
PostTitle string
PostDesc string
Created time.Time
}
How can I create something connected between this two struct such as Author of the Post ?
The goal is try to get the name of the post author which from User struct with the code below:
post, err := app.Models.Posts.GetPost(id)
GetPost() just run SELECT query and scan row
This approach is without any ORM.
It's a simple query that can return multiple rows. You've to scan the whole resultset and map each column on the struct's fields.
Keep in mind to always check for errors.
Below you can find the solution:
package main
import (
"database/sql"
"fmt"
"time"
_ "github.com/lib/pq"
)
type Post struct {
ID int
PostTitle string
PostDesc string
Created time.Time
UserID int
User User
}
type User struct {
ID int
Name string
Created time.Time
}
func main() {
conn, err := sql.Open("postgres", "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable")
if err != nil {
panic(err)
}
defer conn.Close()
// get MAX id
var id int
conn.QueryRow(`SELECT MAX(id) FROM posts`).Scan(&id)
// insert
sqlInsertStmt := `INSERT INTO posts (id, post_title, post_desc, created, user_id) VALUES ($1,$2,$3,$4,$5)`
if _, err = conn.Exec(sqlInsertStmt, id+1, "TDD", "Introduction to Test-Driven-Development", time.Now(), 1); err != nil {
panic(err)
}
// read
rows, err := conn.Query(`SELECT posts.id, post_title, post_desc, posts.created, users.id, users.name, users.created FROM posts INNER JOIN users ON posts.user_id=users.id`)
if err != nil {
panic(err)
}
var posts []Post
for rows.Next() {
var post Post
if err = rows.Scan(&post.ID, &post.PostTitle, &post.PostDesc, &post.Created, &post.User.ID, &post.User.Name, &post.User.Created); err != nil {
panic(err)
}
posts = append(posts, post)
}
if err = rows.Err(); err != nil {
panic(err)
}
for _, v := range posts {
fmt.Printf("author name: %q\n", v.User.Name)
}
}
Let me know if this helps.
Edit
I've also included an example of INSERT in Postgres. To achieve it, we've to use the db.Exec() function, and provide the parameters.
Pay attention to how you construct the query as you can get a SQL-Injection vulnerability.
Lastly, in a real-world scenario, you shouldn't lookup for the MAX id in the posts table but should be automatically generated.
Give it a try to this solution, maybe it resolves your issue.
First, I defined the structs in this way:
// "Post" belongs to "User", "UserID" is the foreign key
type Post struct {
gorm.Model
ID int
PostTitle string
PostDesc string
Created time.Time
UserID int
User User
}
type User struct {
ID int
Name string
Created time.Time
}
In this way, you can say that Post belongs to User and access the User's information within the Post struct.
To query the records, you've to use Preload("User") to be sure to eager load the User records from the separate table.
Keep attention to the name you pass in as the argument in Preload, as it can be tricky.
Lastly, you'll be able to access data in the embedded struct (with the dot notation).
Below, you can find a complete working example (implemented with the use of Docker):
package main
import (
"fmt"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// "Post" belongs to "User", "UserID" is the foreign key
type Post struct {
gorm.Model
ID int
PostTitle string
PostDesc string
Created time.Time
UserID int
User User
}
type User struct {
ID int
Name string
Created time.Time
}
func main() {
dsn := "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Post{})
newPost := &Post{ID: 1, PostTitle: "Golang", PostDesc: "Introduction to Golang", Created: time.Now(), UserID: 1, User: User{ID: 1, Name: "John Doe", Created: time.Now()}}
db.Create(newPost)
var post Post
db.Preload("User").Find(&post, 1)
fmt.Printf("author name: %q\n", post.User.Name)
}
Let me know if I answered your question!

Relation does not exist GORM

Everything worked, but when I cleared the database and started the application again, I get this error: ERROR: relation "orders" does not exist (SQLSTATE 42P01)
My code:
type Cart struct {
gorm.Model
Products JSONB `gorm:"type:jsonb" json:"products"`
OrderID uint
}
type Jurik struct {
gorm.Model
Inn string `json:"inn" gorm:column:"inn"`
...
OrderID uint
}
type Phyz struct {
gorm.Model
Name string `json:"name" gorm:column:"name"`
...
OrderID uint
}
type Order struct {
gorm.Model
Cart Cart `json:"cart"`
User_id string `json:"user_id"`
Jurik Jurik `json:"jurik"`
Phyz Phyz `json:"phyz"`
}
I really don't understand what could be wrong, because my Cart Jurik Phyz tables are related to Order
I don't know how it works, but my code before looked like this:
func Connect() {
db, err := gorm.Open(postgres.Open("postgres://postgres:qwerty#localhost:5432/shop"), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&models.Categories{}, &models.Products{}, &models.Pagination{}, &models.Feedback{}, &models.Cart{}, &models.Jurik{}, &models.Phyz{}, &models.Order{})
DB = db
}
Then I tried to make a separate migration for Order:
func Connect() {
db, err := gorm.Open(postgres.Open("postgres://postgres:qwerty#localhost:5432/shop"), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&models.Categories{}, &models.Products{}, &models.Pagination{}, &models.Feedback{}, &models.Cart{}, &models.Jurik{}, &models.Phyz{})
db.AutoMigrate(&models.Order{})
DB = db
}
Hope this helps someone!

Gorm Query Customized Join Extra Column

I am trying to get extra columns from a many2many relationships on Gorm. Example
Part
type Part struct {
Id unit
Name string
}
Diagram
type Diagram struct {
Id unit
Name string
Parts []Part `gorm:"many2many:diagram_parts;"`
}
DiagramPart
type DiagramPart struct{
DiagramId uint `gorm:"primaryKey"`
PartId uint `gorm:"primaryKey"`
PartDiagramNumber int
PartNumber string
PartDescription string
}
This is what I have done trying to retrieve PartNumber and PartDescription in Parts.
diagram := &Diagram{}
db := s.db.Where("id = ?", 1).
Preload("Parts", func(db *gorm.DB) *gorm.DB {
return db.Select("parts.*, diagram_parts.part_number, diagram_parts.part_description").
Joins("left join diagram_parts on diagram_parts.part_id = parts.id")
}).
First(diagram)
Unfortunately, I am not able to retrieve part_number, part_description. How should I go about it?
You can add field PartNumber and PartDescription on struct Part OR Diagram, then add tag gorm:"-:migration;->" on than fields to ignore migration and to readonly mode. But on your situation, you can add it in struct Part because you already preload it.
source: https://gorm.io/docs/models.html#Field-Level-Permission
here's the example:
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Part struct {
Id uint `gorm:"primaryKey"`
Name string
PartNumber string `gorm:"-:migration;->"`
PartDescription string `gorm:"-:migration;->"`
}
type Diagram struct {
Id uint `gorm:"primaryKey"`
Name string
Parts []Part `gorm:"many2many:diagram_parts;"`
// PartNumber string `gorm:"-:migration;->"`
// PartDescription string `gorm:"-:migration;->"`
}
type DiagramPart struct {
DiagramId uint `gorm:"primaryKey"`
PartId uint `gorm:"primaryKey"`
PartDiagramNumber int
PartNumber string
PartDescription string
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&Diagram{}, &Part{}, &DiagramPart{})
diagram := &Diagram{}
err = db.Debug().Where("id = ?", 1).
// Select("diagrams.*, diagram_parts.part_number, diagram_parts.part_description").
Preload("Parts", func(db *gorm.DB) *gorm.DB {
return db.Select("parts.*, diagram_parts.part_number, diagram_parts.part_description").
Joins("left join diagram_parts on diagram_parts.part_id = parts.id")
}).
// Joins("left join diagram_parts on diagram_parts.diagram_id = diagrams.id").
First(diagram).Error
if err != nil {
panic(err)
}
fmt.Printf("diagram: %v\n", diagram)
fmt.Println("part number:", diagram.Parts[0].PartNumber)
}

Merging of map and struct golang

I have 2 items, collections and accounts represented by 2 structs that I would like to merge into a single response.
collections, accounts, err := h.Service.Many(ctx, params)
The collection struct is defined as follows:
type Collection struct {
ID int64 `json:"id"`
Name *string `json:"name"`
Description *string `json:"description"`
Total *int64 `json:"total"`
}
And accounts is defined as a map as such accounts := make(map[int64][]string) and the data looks like this map[1:[19565 21423] 7:[]]
What I would like to do is merge these 2 something like the following:
// merge into single struct
type CollectionWithAccounts struct {
Collections []*collection.Collection
AccountIDs []string
}
// initialize struct
collectionsWithAccounts := make([]CollectionWithAccounts, 0)
// merge strucst in loop
for _, collection := range collections {
for _, account := range accounts {
collectionsWithAccounts.Collections = append(collectionsWithAccounts, collection)
collectionsWithAccounts.Accounts = append(collectionsWithAccounts, account)
}
}
How can I accomplish this merge?
You can do this even without any loops:
package main
import "fmt"
type Collection struct {
ID int64 `json:"id"`
Name *string `json:"name"`
Description *string `json:"description"`
Total *int64 `json:"total"`
}
type AccountID map[int64][]string
// merge into single struct
type CollectionWithAccounts struct {
Collections []*Collection
AccountIDs []AccountID
}
func main() {
// get the data
// []*Collections, []AccountID, err
collections, accounts, err := h.Service.Many(ctx, params)
// handle error
if err != nil {
fmt.Println(err.Error())
// more logic
}
collectionsWithAccounts := CollectionWithAccounts{
Collections: collections,
AccountIDs: accounts,
}
}

Can't get GORM associations to work as expected

My two models are
package models
// Business ...
type Business struct {
ID uint
Name string `gorm:"not null"`
Tables Tables `gorm:"ForeignKey:BusinessID"`
}
// Businesses ...
type Businesses []Business
and
package models
// Table ...
type Table struct {
ID uint
Ref string `gorm:"not null"`
Business Business
BusinessID uint
}
// Tables ...
type Tables []Table
It may be obvious from the code, but the association should be that one 'business' has many 'tables' and a 'table' belong to a 'business'. However, when the database is created there are no foreign keys created (I am using sqlite3) and when I return the business which has been created with
bus := models.Business{
Name: "Test",
Tables: models.Tables{
models.Table{Ref: "A1"},
},
}
db.Create(&bus)
the businesses array is empty, and when the table is returned although the business_id is correct, there is business struct is empty also.
I could not reproduce your problem. I have a working solution here. I suspected that it would not work with the entities in a separate models package, but that worked as well.
package main
import (
"log"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
_ "github.com/mattn/go-sqlite3"
)
type Business struct {
ID uint
Name string `gorm:"not null"`
Tables Tables `gorm:"ForeignKey:BusinessID"`
}
type Table struct {
ID uint
Ref string `gorm:"not null"`
Business Business
BusinessID uint
}
type Tables []Table
type Businesses []Business
func main() {
var err error
var db *gorm.DB
db, err = gorm.Open("sqlite3", "test.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
db.LogMode(true)
db.AutoMigrate(&Business{})
db.AutoMigrate(&Table{})
bus := Business{
Name: "Test",
Tables: Tables{
Table{Ref: "A1"},
},
}
db.Create(&bus)
var businesses Businesses
db.Preload("Tables").Find(&businesses)
log.Println(businesses)
}

Resources