Cannot bind JSON in Golang Gin to struct with ManyToMany field - algorithm

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

Related

(Gorm Golang) How to select field without ignore json in struct?

So, I want to create an API, but when I try to query with a select field, I always fail, I want the data for my API only for the ID and Name fields, I want to remove movies field without ignoring the json because these fields are also needed in other urls, how to solve it?
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"`
Genres []Genre `json:"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"`
GenreID int `json:"genre_id"`
Genre Genre `gorm:"foreignKey:GenreID"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
}
This the code for query
func (MovieRepositoryImpl *MovieRepositoryImpl) GetGenres() (*[]Genre, error) {
var genres []Genre
err := MovieRepositoryImpl.DB.Find(&genres).Error
if err != nil {
return nil, err
}
return &genres, nil
}
This the result
I want to remove field movies without ignore the json
if you do not want send param: movies to backend and response without moives, it would work, json tag add omitempty.
type Genre struct {
ID int `json:"id"`
GenreName string `json:"name"`
Movies []Movie `json:"movies,omitempty" gorm:"many2many:movie_genres"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
}

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?

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

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