How to get double nested data with gorm? - go

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
}

Related

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

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

golang, creating relation between 2 model and retrieve them with Preload using gorm

I am learning golang with gqlgen and gorm as orm, I am creating an app using with 2 models user and messages where the user has a list of messages,
and the messages have sender and recipient.
I have made them like the following
type User struct {
ID string `json:"id" gorm:"primary_key;type:uuid;default:uuid_generate_v4()"`
Username string `json:"username"`
Email string `json:"email"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Messages []*Message `json:"messages"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at" sql:"index"`
}
type Message struct {
ID string `json:"id" gorm:"primary_key;type:serial"`
Title string `json:"title"`
Body string `json:"body"`
DueDate time.Time `json:"dueDate"`
IsViewed bool `json:"isViewed" gorm:"default:false"`
SenderID string `json:"senderId" gorm:"type:uuid"`
Sender *User `json:"sender" gorm:"foreignkey:SenderID"`
RecipientID string `json:"recipientId" gorm:"type:uuid"`
Recipient *User `json:"recipient" gorm:"foreignkey:RecipientID"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at" sql:"index"`
}
when I retrieve the messages data using Preload
var messages []*models.Message
err := db.
Preload("Sender").
Preload("Recipient").
Find(&messages).Error
if err != nil {
return nil, err
}
return messages, err
it works perfectly but my problem is when trying to retrieve the user with the messages preloaded.
var users []*models.User
err := db.
Preload("Messages").
Find(&users).Error
if err != nil {
return nil, err
}
return users, err
this one gives me the following error can't preload field Messages for models.User
I know I might design my schema wrong if there's a better way to organize it I would appreciate it so much, thanks in advance.
I think you should separate messages in User to SentMessages and ReceivedMessages. Then you can specify foreign keys in User like that:
SentMessages []*Message `gorm:"foreignkey:SenderID" json:"sentMessages"`
ReceivedMessages []*Message `gorm:"foreignkey:RecipientID" json:"receivedMessages"`
then use it as the following:
var users []*models.User
err := db.
Preload("SentMessages").
Preload("ReceivedMessages").
Find(&users).Error
if err != nil {
return nil, err
}
return users, err
that should work as you want
I think you've to take a closer look on how the association are made(actually create from the official gorm package) like (&Model{}).Create(target).Association("Column").Append(&related_instance)
So when you'll retry it using Preload everything gone be Ok. And BTW it work with your current design. Hope it helps.

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
}

Respond only few fields from the model

Below is my Advertiser Model -
type Advertiser struct {
ID int `json:"id" db:"id"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
Name string `json:"name" db:"name"`
Email string `json:"email" db:"email"`
ContactNumber string `json:"contact_number" db:"contact_number"`
}
I have generated the Advertiser Resource and by default, it had brought-in the actions.
Now, in AdvertiserList action, I need all these fields to do some or the other calculation. But, finally, I would like to only respond with Name, Email and ContactNumber fields.
Remember, this is a List action, which means, we have an array of Advertiser.
Right now, my action does below-
func (v AdvertisersResource) List(c buffalo.Context) error {
tx, ok := c.Value("tx").(*pop.Connection)
if !ok {
return errors.WithStack(errors.New("no transaction found"))
}
advertisers := &models.Advertisers{}
q := tx.PaginateFromParams(c.Params())
if err := q.All(advertisers); err != nil {
return errors.WithStack(err)
}
c.Set("pagination", q.Paginator)
return c.Render(200, r.JSON(advertisers))
}
I'm not sure if I understood your question correctly. However when marshaling to json if you have fields with tags json:"-" then that value is not returned.

Resources