Many to many association with structs - go

I have this data structure here :
And I am having an issue linking the many-to-many association between Historique and Medicament. (History is all the time a patient went doctor, treatment are the treatment he received, medicament the medicament prescribed)
I have this code here and I can't find a way to link them with all these foreign keys !
type Traitement struct {
Histo_id int `gorm:"primary_key"`
Histo_patient_id int `gorm:"primary_key"`
Histo_medecin_id int `gorm:"primary_key"`
Medicament_id int `gorm:"primary_key"`
Debut_traitement string
Fin_traitement string
}
type Medicament struct {
ID int `gorm:"primary_key"`
Nom string `gorm:"column:medicament_nom"`
Description string `gorm:"column:medicament_desc"`
Historique []Historique `gorm:"many2many:tt_traitement"`
}
type Historique struct {
Fk_patient_id string `json:"-" gorm:"primary_key"`
Date_consultation string
Fk_maladie_id uint `json:"-" gorm:"primary_key"`
Fk_compte_medecin_id uint
Maladie Maladie `gorm:"ForeignKey:ID;AssociationForeignKey:Fk_maladie_id"`
Medicament []Medicament `gorm:"many2many:tt_traitement"`
}
func GetPatientWithDiseases(id uint) (*Patient, error) {
patient := &Patient{}
err := GetDB().Preload("Historique.Maladie").Preload("Historique.Traitement.Medicament").Find(&patient, id).Error
if err != nil {
return nil, err
}
return patient, nil
}
Thanks in advance for your help !

Ok, so after a lucky try, I had an error that led me to the good path.
I removed the Traitement struct, and the many_to_many and []Historique in the Medicament struct. I only kept:
type Historique struct {
ID int `gorm:"primary_key"`
Fk_patient_id string `json:"-" gorm:"primary_key"`
Date_consultation string
Fk_maladie_id uint `json:"-"`
Fk_compte_medecin_id uint `gorm:"primary_key"`
Maladie Maladie `gorm:"ForeignKey:ID;AssociationForeignKey:Fk_maladie_id"`
Medicament []Medicament `gorm:"many2many:tt_traitement"`
}
type Medicament struct {
ID int `gorm:"primary_key"`
Nom string `gorm:"column:medicament_nom"`
Description string `gorm:"column:medicament_desc"`
}
func GetPatientWithDiseases(id uint) (*Patient, error) {
patient := &Patient{}
err := GetDB().Preload("Historique.Maladie").Preload("Historique.Medicament").Find(&patient, id).Error
if err != nil {
return nil, err
}
return patient, nil
}
And things worked ! I just had to rename some of my columns, but as it is a dummy database, it wasn't an issue :) !
I hope this can help someone out there !

Related

gorm [error] unsupported data type: &map[]

I want to create an API using map, but when in models I define map type, it error with message not support
This is Model
type Movie struct {
ID int `json:"id" validate:"number"`
Title string `json:"title"`
Description string `json:"description"`
Year int `json:"year"`
ReleaseDate time.Time `json:"release_date"`
Runtime int `json:"runtime"`
Rating int `json:"rating"`
MPAARating string `json:"mpaa_rating"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
MovieGenres map[int]string `json:"-" gorm:"many2many:movie_genres"`
}
type Genre struct {
ID int `json:"-"`
GenreName string `json:"genre_name"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
}
type MovieGenre struct {
ID int `json:"id"`
MovieID int `json:"movie_id"`
GenreID int `json:"genre_id"`
Genre Genre `gorm:"foreignKey:GenreID"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
}
and this is the code to retrieve data by ID
func (MovieRepositoryImpl *MovieRepositoryImpl) GetMovieById(id int) (*Movie, error) {
var movie Movie
err := MovieRepositoryImpl.DB.First(&movie, id).Error
if err != nil {
return nil, err
}
var movie_genres MovieGenre
rows, err := MovieRepositoryImpl.DB.Model(&movie_genres).Preload(movie_genres.Genre.GenreName).Rows()
defer rows.Close()
genres := make(map[int]string)
for rows.Next() {
err := MovieRepositoryImpl.DB.ScanRows(rows, &movie_genres).Error
if err != nil {
panic(err)
}
genres[movie_genres.ID] = movie_genres.Genre.GenreName
}
movie.MovieGenres = genres
return &movie, nil
}
This is my Expect
You can change MovieGenres map[int]string to MovieGenres datatypes.JSONMap
https://gorm.io/docs/data_types.html
https://github.com/go-gorm/datatypes/blob/master/json_map.go

How to save data with association in gorm golang?

So, I want to save the movies and movie_genres data, but it always fails, which is stored in the database is the movies table as much as looping movie_genres, and the movie_genres column with the wrong movies ID. This is the result
movies table
movie_genres table
This the JSON result from frontend
{
"title":"Qui aut consectetur",
"release_date":"1974-11-25",
"runtime":"91",
"mpaa_rating":"PG",
"rating":"2",
"description":
"Anim dolor molestias",
"genre_id":["1","2"]
}
Below is my code on go
models
type Movie struct {
ID int `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Year int `json:"year"`
ReleaseDate time.Time `json:"release_date"`
Runtime int `json:"runtime"`
Rating int `json:"rating"`
MPAARating string `json:"mpaa_rating"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Genres []Genre `json:"genres" gorm:"many2many:movie_genres"`
MovieGenre []MovieGenre `json:"movie_genres" gorm:"many2many:movie_genres"`
}
type Genre struct {
ID int `json:"id"`
GenreName string `json:"name"`
Movies []Movie `json:"movies" gorm:"many2many:movie_genres"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
}
type MovieGenre struct {
ID int `json:"id"`
MovieID int `json:"movie_id"`
Movie Movie `gorm:"foreignKey:MovieID"`
GenreID int `json:"genre_id"`
Genre Genre `gorm:"foreignKey:GenreID"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
}
handler.go
func (app *Application) CreateMovies(ctx *gin.Context) {
var payload web.MoviePayloadResponse
if err := ctx.BindJSON(&payload); err != nil {
log.Println(err)
return
}
var movieGenre models.MovieGenre
movieGenre.Movie.ID, _ = strconv.Atoi(payload.ID)
movieGenre.Movie.Title = payload.Title
movieGenre.Movie.Description = payload.Description
movieGenre.Movie.ReleaseDate, _ = time.Parse("2006-01-02", payload.ReleaseDate)
movieGenre.Movie.Year = movieGenre.Movie.ReleaseDate.Year()
movieGenre.Movie.Runtime, _ = strconv.Atoi(payload.Runtime)
movieGenre.Movie.Rating, _ = strconv.Atoi(payload.Rating)
movieGenre.Movie.MPAARating = payload.MPAARating
movieGenre.Movie.CreatedAt = time.Now()
movieGenre.Movie.UpdatedAt = time.Now()
movieGenre.ID, _ = strconv.Atoi(payload.ID)
movieGenre.MovieID = movieGenre.Movie.ID
movieGenre.CreatedAt = time.Now()
movieGenre.UpdatedAt = time.Now()
err := app.models.Repository.CreateMovie(movieGenre.Movie)
if err != nil {
panic(err)
}
for _, v := range payload.GenreID {
movieGenre.GenreID, _ = strconv.Atoi(v)
err := app.models.Repository.CreateMovieGenres(movieGenre)
if err != nil {
panic(err)
}
}
ctx.JSON(http.StatusOK, &gin.H{
"ok": "response",
})
return
}
repository.go
func (MovieRepositoryImpl *MovieRepositoryImpl) CreateMovie(movie Movie) error {
MovieRepositoryImpl.DB.Create(&movie)
return nil
}
func (MovieRepositoryImpl *MovieRepositoryImpl) CreateMovieGenres(movieGenre MovieGenre) error {
MovieRepositoryImpl.DB.Create(&movieGenre)
return nil
}
payload
type MoviePayloadResponse struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Year string `json:"year"`
ReleaseDate string `json:"release_date"`
Runtime string `json:"runtime"`
Rating string `json:"rating"`
MPAARating string `json:"mpaa_rating"`
GenreID []string `json:"genre_id"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
The results that I expect are in the movies table, only 1 column is made, and in the movie_genres table store data movies as much as the genre inputted with the new movies ID. How to solve the problem?

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)
}

Golang GORM Cascade Delete to Nested Fields

Main Model:
type Page struct {
ID int `gorm:"primarykey" json:"id"`
Title string `gorm:"unique;not null" json:"title"`
LocalizedPageTitles []LocalizedPageTitle `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"localizedPageTitles"`
Paragraphs []Paragraph `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"paragraphs"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
First Child:
type Paragraph struct {
ID uint `gorm:"primarykey" json:"id"`
Text string `gorm:"unique;not null" json:"text"`
PageID uint `json:"pageId"`
LocalizedParagraphs []LocalizedParagraph `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"localizedParagraphs"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
Second Child:
type LocalizedParagraph struct {
Localized
ID uint `gorm:"primarykey" json:"id"`
ParagraphID uint `json:"paragraphId"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
This is how I delete my page entity:
func (p PageRepositoryImpl) Delete(id int) error {
return p.db.Unscoped().Select(clause.Associations).Delete(&entity.Page{ID: id}).Error
}
Above function deletes Page and Paragraphs but how can I remove LocalizedParagraphs automatically?
By the way I'm using Sqlite.
dbmanager.go
func InitSQLite(filePath string) *gorm.DB {
database, err := gorm.Open(sqlite.Open(filePath), &gorm.Config{})
if err != nil {
fmt.Printf("Error:%v", err)
panic("Failed to connect database")
}
autoMigrateDB(database)
return database
}
func autoMigrateDB(db *gorm.DB) {
db.AutoMigrate(
&entity.Page{},
&entity.Paragraph{},
&entity.LocalizedPageTitle{},
&entity.LocalizedParagraph{},
)
}
I couldn't perform this action without delete hooks.
Select(clause.Associations) statement already take care of the one level associations:
func (p PageRepositoryImpl) Delete(id int) error {
return p.db.Unscoped().Select(clause.Associations).Delete(&entity.Page{ID: id}).Error
}
For nested associations, I used delete hook,
here is my solution:
func (page *Page) BeforeDelete(tx *gorm.DB) error {
paragraphs := make([]Paragraph, 0)
err := tx.Where("page_id = ?", page.ID).Find(&paragraphs).Error
if err != nil{
return err
}
ids := make([]int, 0, len(paragraphs))
for _, element := range paragraphs{
ids = append(ids, int(element.ID))
}
lps := make([]LocalizedParagraph,0)
err = tx.Where("paragraph_id IN ?", ids).Unscoped().Delete(&lps).Error
return err;
}

Can't set up has-many association in GORM

I'm trying to set up an association between Users and PredictionsBags. My problem is that everything works OK if I use GORM's assumed names for referring objects, but I'd like to change the names a bit.
type User struct {
gorm.Model
// We’ll try not using usernames for now
Email string `gorm:"not null;unique_index"`
Password string `gorm:"-"`
PasswordHash string `gorm:"not null"`
Remember string `gorm:"-"` // A user’s remember token.
RememberHash string `gorm:"not null;unique_index"`
Bags []PredictionsBag
}
Every user, of course, owns zero or more PredictionsBags:
type PredictionsBag struct {
gorm.Model
UserID uint // I want this to be "OwnerID"
Title string
NotesPublic string `gorm:"not null"` // Markdown field. May be published.
NotesPrivate string `gorm:"not null"` // Markdown field. Only for (private) viewing and export.
Predictions []Prediction
}
And I'd like to have .Related() work in the usual way:
func (ug *userGorm) ByEmail(email string) (*User, error) {
var ret User
matchingEmail := ug.db.Where("email = ?", email)
err := first(matchingEmail, &ret)
if err != nil {
return nil, err
}
var bags []PredictionsBag
if err := ug.db.Model(&ret).Related(&bags).Error; err != nil {
return nil, err
}
ret.Bags = bags
return &ret, nil
}
My problem is that I can't find a way to change PredictionsBag.UserID to anything else and still have GORM figure out the relationships involved. I've been reading http://gorm.io/docs/has_many.html#Foreign-Key and if I change the relevant lines to
type User struct {
// …
Bags []PredictionsBag `gorm:"foreignkey:OwnerID"`
}
and
type PredictionsBag struct {
// …
OwnerID uint
// …
}
I get this error:
[2019-07-28 14:23:49] invalid association []
What am I doing wrong? I've also been reading http://gorm.io/docs/belongs_to.html, but I'm not sure which page to follow more closely.
I'll have to Check Related() when I get home, but I think what you're looking for is Preload() This is my example that works with what you want.
package main
import (
"errors"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"log"
)
var DB *gorm.DB
func init() {
var err error
DB, err = gorm.Open("mysql", fmt.Sprintf("%s:%s#tcp(%s:3306)/%s?&parseTime=True&loc=Local", "root", "root", "localhost", "testing"))
if err != nil {
log.Fatal(err)
}
DB.DropTableIfExists(&User{}, &PredictionsBag{})
DB.AutoMigrate(&User{}, &PredictionsBag{})
user := User{Email:"dave#example.com"}
user.Bags = append(user.Bags, PredictionsBag{OwnerID: user.ID, NotesPrivate: "1", NotesPublic: "1"})
DB.Create(&user)
}
func main() {
user := User{Email:"dave#example.com"}
err := user.ByEmail()
if err != nil {
log.Println(err)
}
fmt.Println(user.ID, user.Email, "Bags:", len(user.Bags))
DB.Close()
}
type User struct {
gorm.Model
// We’ll try not using usernames for now
Email string `gorm:"not null;unique_index"`
Password string `gorm:"-"`
PasswordHash string `gorm:"not null"`
Remember string `gorm:"-"` // A user’s remember token.
RememberHash string `gorm:"not null;unique_index"`
Bags []PredictionsBag `gorm:"foreignkey:OwnerID"`
}
type PredictionsBag struct {
gorm.Model
OwnerID uint
Title string
NotesPublic string `gorm:"not null"` // Markdown field. May be published.
NotesPrivate string `gorm:"not null"` // Markdown field. Only for (private) viewing and export.
}
func (ug *User) ByEmail() error {
DB.Where("email = ?", ug.Email).Preload("Bags").Limit(1).Find(&ug)
if ug.ID == 0 {
return errors.New("no user found")
}
return nil
}
Using this might work with related, but I'm not sure what else needs to be changed:
Bags []PredictionsBag `gorm:"foreignkey:OwnerID;association_foreignkey:ID"`
Update:
I can get the Related() method to work, if you state the ForeignKey like the following:
DB.Where("email = ?", ug.Email).Limit(1).Find(&ug)
if ug.ID == 0 {
return errors.New("no user found")
}
if err := DB.Model(&ug).Related(&ug.Bags, "owner_id").Error; err != nil {
return err
}

Resources