Golang GORM Cascade Delete to Nested Fields - go

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

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 Update Multiple Records Using gorm?

So I want to update movie_genre data with different genre_id values, each movie has more than 1 genre_id automatically there are multiple records in movie_genre with the same movie_id and different genre_id,
when I start trying to update with gorm by selecting only 1 genre_id, the old genre has changed, but the newly changed genre is created again in a new row with all the same column values ​​except created_at, whereas if I try to update by selecting 2 genres, the data the changed only has the value of the first genre_id, the second genre_id is not read
this is a erd database
payload json from frontend
{
"title":"Autaaa sint nihil quis ",
"release_date":"2001-01-01",
"runtime":"22",
"mpaa_rating":"G",
"rating":"4",
"description":"Eius enim distinctio1",
"id":"82",
"genre_id":["3","8"]
}:
handler
func (app *Application) UpdateMovie(ctx *gin.Context) {
var payload web.MoviePayloadResponse
if err := ctx.BindJSON(&payload); err != nil {
log.Println(err)
return
}
id, _ := strconv.Atoi(payload.ID)
movie, err := app.models.Repository.GetMovieById(id)
if err != nil {
helper.NotFound(*ctx, err)
return
}
movie.ID, _ = strconv.Atoi(payload.ID)
movie.Title = payload.Title
movie.Description = payload.Description
movie.ReleaseDate, _ = time.Parse("2006-01-02", payload.ReleaseDate)
movie.Year = movie.ReleaseDate.Year()
movie.Runtime, _ = strconv.Atoi(payload.Runtime)
movie.Rating, _ = strconv.Atoi(payload.Rating)
movie.MPAARating = payload.MPAARating
movie.UpdatedAt = time.Now()
err = app.models.Repository.UpdateMovie(movie, id)
if err != nil {
panic(err)
}
movieGenre, _ := app.models.Repository.GetMovieGenresById(id)
for _, mg := range *movieGenre {
mg.MovieID = movie.ID
mg.UpdatedAt = time.Now()
for _, v := range payload.GenreID {
mg.GenreID, _ = strconv.Atoi(v)
err := app.models.Repository.UpdateMovieGenre(&mg, id)
if err != nil {
panic(err)
}
}
}
ctx.JSON(http.StatusOK, &gin.H{
"ok": "response",
})
return
}
repository
func (MovieRepositoryImpl *MovieRepositoryImpl) UpdateMovie(movie *Movie, id int) error {
err := MovieRepositoryImpl.DB.Where("id = ?", id).Save(&movie).Error
if err != nil {
return err
}
return nil
}
func (MovieRepositoryImpl *MovieRepositoryImpl) UpdateMovieGenre(movieGenre *MovieGenre, id int) error {
err := MovieRepositoryImpl.DB.Where("movie_id = ?", id).Save(&movieGenre).Error
if err != nil {
return err
}
return nil
}
func (MovieRepositoryImpl *MovieRepositoryImpl) GetMovieGenresById(id int) (*[]MovieGenre, error) {
var movie []MovieGenre
err := MovieRepositoryImpl.DB.Where("movie_id = ?", id).Find(&movie).Error
if err != nil {
return nil, err
}
return &movie, nil
}
Model
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:"-"`
}
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"`
}
My expectation is that the data genre_id that changes is only data whose value is not the same as the value in the payload, if it is the same then the data does not change, if it is different then it changes, and if the data in the payload exceeds the original genre data, then new data is automatically created. That's roughly what it looks like. Please help!!

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?

Cannot bind JSON in Golang Gin to struct with ManyToMany field

I have the following structs as relational models:
type Product struct {
ID uint32 `gorm:"primary_key;auto_increment" json:"id"`
Name string `gorm:"size:100;not null" json:"name"`
Description string `gorm:"size:512" json:"description"`
Price float64 `gorm:"default:0" json:"price"`
Shop Shop `json:"shop"`
ShopID uint32 `gorm:"not null" json:"shop_id"`
Categories []Category `gorm:"many2many:product_categories;" json:"categories"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
}
type Category struct {
ID uint32 `gorm:"primary_key;auto_increment" json:"id"`
Name string `gorm:"size:100;not null" json:"name"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
}
Now, on my controller I want to create a Product with multiple categories from a JSON object but I can't find a way on how to do it; thus, it returns the message Unable to bind data to product. Here is the code:
func (s *Server) createProduct(c *gin.Context) {
uid, err := auth.ExtractTokenID(c.Request)
if err != nil {
c.IndentedJSON(http.StatusUnauthorized, gin.H{"message": "Unauthorized"})
return
}
var product = models.Product{}
var shop = models.Shop{}
if err := c.BindJSON(&product); err != nil {
fmt.Println(err)
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": "Unable to bind data to product"})
return
}
Is there a solution or a simple way on how to achieve this? As I haven't been able to find anything on gorm documentation.
Edit; Here is the JSON object I'm sending:
{
"name": "Soap",
"description": "Smelly Soap",
"Price": 12.99,
"categories": [1]
}

How to get double nested data with gorm?

I am developing an application that manages quotes using go and gorm. Users create quote, quotes have multiple tags, and users can add quotes to their favorites.
I want to get a list of quotes that a user has added to their favorites. And at that time, I want to get the tag attached to the quotes.
Here is my data models.
type Quote struct {
ID int `gorm:"primary_key" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Text string `json:"text"`
Page int `json:"page"`
Published bool `gorm:"default:false" json:"published"`
Tags []Tag `gorm:"many2many:quotes_tags;" json:"tags"`
User User `json:"user"`
UserID string `json:"user_id"`
FavoriteUsers []User `gorm:"many2many:users_quotes;" json:"favorite_users"`
}
type User struct {
ID string `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Username string `json:"username"`
Quotes []Quote `json:"quotes"`
FavoriteQuotes []Quote `gorm:"many2many:users_quotes;" json:"favorite_quotes"`
}
type Tag struct {
ID int `gorm:"primary_key" json:"id"`
CreatedAt time.Time `json:"created_at"`
Name string `json:"name"`
}
I tried the following and got the favorite quote, but the tags was null. Is there a way to do this with gorm? Thank you in advance.
func (service *Service) GetUser(uid string) (models.User, error) {
fmt.Println(uid)
user := models.User{}
if result := service.db.Preload(clause.Associations).First(&user, "id = ?", uid); result.Error != nil {
return models.User{}, result.Error
}
return user, nil
}
func (service *Service) GetFavoriteQuotes(uid string) ([]models.Quote, error) {
user, err := service.GetUser(uid)
if err != nil {
return []models.Quote{}, err
}
return user.FavoriteQuotes, nil
}
If you check the Preload All documentation, it says:
clause.Associations won’t preload nested associations, but you can use it with Nested Preloading
In your case, this would be something like this:
if result := service.db.Preload("FavoriteQuotes.Tags").Preload(clause.Associations).First(&user, "id = ?", uid); result.Error != nil {
return models.User{}, result.Error
}

Resources