Golang Gorm retrive one to many relation with limit - go

Hello I have Room and Messages tables where where room has many messages. I want to get user rooms with limit 20 rooms and each room has only 20 messages only.
type Room struct {
gorm.Model
ID uint `json:"id" gorm:"primary_key"`
Hash string `json:"hash" binding:"required" gorm:"not null:true"`
Users []User `json:"users" gorm:"many2many:room_users"`
Messages []Message `json:"messages"`
Blocked bool `json:"blocked" gorm:"default:false"`
BlockerId uint `json:"blocker_id" gorm:"nullable:true"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
RoomBotData shared.RoomBotData `json:"bot_data"`
RoomBotDataID uint `json:"room_bot_data_id"`
}
type Message struct {
ID uint `json:"id" gorm:"primary_key"`
Text string `json:"text" binding:"required" gorm:"not null:true"`
UserID uint `json:"user_id" gorm:"not null:true"`
RoomID uint `json:"room_id" gorm:"not null:true"`
IsRead bool `json:"is_read" binding:"required" gorm:"default:false"`
Type string `json:"type" gorm:"default:text"`
CreatedAt time.Time `json:"created_at"`
}
I have tried preload function and set limit to 20 for message
db.Preload("Messages", func(db *gorm.DB) *gorm.DB {
return db.Limit(20)
}).Find(&rooms)
but this only get 20 messages in total for all rooms.
I also tried to get 20 messages for each room
err = db.DB.Model(&Room{}).
Order("updated_at DESC").
Limit(20).
Preload(RoomSchema.Users).
Where("id in ?", roomIds).
Find(&rooms).Error
roomMessagesChannel := make(chan map[int][]Message)
for i, room := range rooms {
go r.getRoomMessages(roomMessagesChannel, room.ID, i)
}
for i := 0; i < len(rooms); i++ {
messagesMap := <-roomMessagesChannel
firstItemOfMap := reflect.ValueOf(messagesMap).MapKeys()[0].Int()
rooms[int(firstItemOfMap)].Messages = messagesMap[int(firstItemOfMap)]
}
close(roomMessagesChannel)
I used channels to speed up the process but it cause memory leak and consume a lot of memory. I want to do that in a better way

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

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

using smart select in gorm with many 2 many relation

I want to use smart select when querying rooms table. When use the original struct it works fine but when use smart select it fails.
Here is my schema
type Room struct {
gorm.Model
ID uint `json:"id" gorm:"primary_key"`
Hash string `json:"hash" binding:"required" gorm:"not null:true"`
Users []User `json:"users" gorm:"many2many:room_users"`
Messages []Message `json:"messages"`
}
type RoomAPI struct {
ID uint `json:"id" gorm:"primary_key"`
Hash string `json:"hash" binding:"required" gorm:"not null:true"`
Users []User `json:"users" gorm:"many2many:room_users"`
//Messages []Message `json:"messages" gorm:"foreignKey:RoomID"`
}
func (RoomAPI) TableName() string {
return "rooms"
}
type User struct {
gorm.Model
ID uint `json:"id" gorm:"primary_key"`
Name string `json:"name" binding:"required" gorm:"not null:true"`
Phone string `json:"phone"`
Email string `json:"email" binding:"required,email" gorm:"not null:true"`
Password string `json:"password" binding:"required,min=8" gorm:"not null:true"`
Gender string `json:"gender" binding:"Enum=male_female" gorm:"type:gender;not null:true;default:male"`
Rooms []Room `json:"rooms" gorm:"many2many:room_users"`
}
type Message struct {
gorm.Model
ID uint `json:"id" gorm:"primary_key"`
Text string `json:"text" binding:"required" gorm:"not null:true"`
UserID uint `json:"user_id" gorm:"not null:true"`
RoomID uint `json:"room_id" gorm:"not null:true"`
}
here is my query
var rooms []RoomAPI
user := User{ID: userId}
err := db.DB.Model(&user).Preload("Users").
Association("Rooms").
Find(&rooms)
if err != nil {
log.Printf("err -> %+v", err)
return err, nil
}
return nil, rooms
I want to get rooms with selected fields with smart select but it fails

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

Resources