How to load structs with multiple nested association - postgresql

I am trying to load one to one and one to many associations for one of my object using GORM. I keep getting the following error when I am trying to run.
{
"errors": [
{
"message": "can't preload field UserProfiles for models.Renter",
"path": [
"users"
]
}
],
"data": null
}
Here is my code
type User struct {
BaseModelSoftDelete // We don't to actually delete the users, audit
Email string `gorm:"not null;unique_index"`
Password string
FirstName *string
LastName *string
Renter Renter `gorm:"auto_preload"`
Rentee Rentee `gorm:"auto_preload"`
UserProfiles []UserProfile `gorm:"association_autocreate:false;association_autoupdate:false"`
Roles []Role `gorm:"many2many:user_roles;association_autocreate:false;association_autoupdate:false"`
Permissions []Permission `gorm:"many2many:user_permissions;association_autocreate:false;association_autoupdate:false"`
}
// UserProfile saves all the related OAuth Profiles
type UserProfile struct {
BaseModelSeq
Email string `gorm:"unique_index:idx_email_provider_external_user_id"`
UserID uuid.UUID `gorm:"not null;index"`
User User `gorm:"association_autocreate:false;association_autoupdate:false"`
Provider string `gorm:"not null;index;unique_index:idx_email_provider_external_user_id;default:'DB'"` // DB means database or no ExternalUserID
ExternalUserID string `gorm:"not null;index;unique_index:idx_email_provider_external_user_id"` // User ID
Name string
FirstName string
LastName string
AvatarURL string `gorm:"size:1024"`
Description string `gorm:"size:1024"`
}
type Renter struct {
BaseModelSoftDelete // We don't to actually delete the users, audit
UserID uuid.UUID `gorm:"unique;not null;index"`
Verified bool
Properties []Property `gorm:"association_autocreate:false;association_autoupdate:false"`
Listings []Listing `gorm:"association_autocreate:false;association_autoupdate:false"`
}
type Rentee struct {
BaseModelSoftDelete // We don't to actually delete the users, audit
UserID uuid.UUID `gorm:"unique;not null;index"`
Verified bool
Bookings []Booking `gorm:"association_autocreate:false;association_autoupdate:false"`
}
then I call this function
func userList(r *queryResolver, id *string) (*gql.Users, error) {
entity := consts.GetTableName(consts.EntityNames.Users)
whereID := "id = ?"
record := &gql.Users{}
dbRecords := []*dbm.User{}
tx := r.ORM.DB.Begin().Preload(consts.EntityNames.UserProfiles)
defer tx.RollbackUnlessCommitted()
if id != nil {
tx = tx.Where(whereID, *id)
}
tx = tx.Find(&dbRecords).Count(&record.Count)
for _, dbRec := range dbRecords {
renter := dbm.Renter{}
tx = tx.Model(&dbRec).Related(&renter)
logger.Infof("%+v", dbRec.Renter)
// rentee := dbm.Rentee{}
// tx.Related(&rentee)
// logger.Info(rentee)
if rec, err := tf.DBUserToGQLUser(dbRec); err != nil {
logger.Errorfn(entity, err)
} else {
record.List = append(record.List, rec)
}
}
return record, tx.Error
}
If I get rid of tx = tx.Model(&dbRec).Related(&renter) the query runs, the profile object loads but my renter and rentee object doesn't have the data from database. And I notice it doesn't run the query SELECT * FROM "renters" WHERE "renters"."deleted_at" IS NULL AND (("user_id" = 'my-user-uuid'))
I also tried to this this:
tx = tx.Preload(consts.EntityNames.Renters).Preload(consts.EntityNames.Rentees).Preload(consts.EntityNames.UserProfiles).Find(&dbRecords).Count(&record.Count)
but get thise error: can't preload field Renters for models.User

Related

Retrieving object form Many 2 Many Relationship in Gorm (posgtgres)

I have two models
type Role struct {
models.BaseModel
UID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();uniqueIndex:idx_uniqueGroupId" form:"id" xml:"id" json:"id"`
Name string `gorm:"size:150;uniqueIndex:idx_uniqueGroup" form:"name" xml:"name" json:"name"`
Permission []Permission `gorm:"many2many:roles_permissions;constraint:OnDelete:CASCADE;" form:"rolePermissions" xml:"rolePermissions" json:"rolePermissions"`
}
and
type Permission struct {
models.BaseModel
UID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();uniqueIndex:idx_uniquePermId" form:"id" xml:"id" json:"id"`
Model string `gorm:"uniqueIndex:idx_uniquePerm" form:"entity" xml:"entity" json:"entity"`
Name string `gromr:"uniqueIndex:idx_uniquePerm" form:"name" xml:"name" json:"name"`
}
I created a function which will return role with related permissions as
func GetRoleByUid(uid uuid.UUID, db *gorm.DB) (*Role, error) {
var role Role
if err := db.Model(&Role{}).Preload("Permissions").Find(&role, "uid = ?", uid).Error ; err != nil {
return nil, err
}
if role.UID == uuid.Nil {
return nil, errors.New("Role not Found")
}
return &role, nil
}
But i got an error "Permissions: unsupported relations for schema Role"
How can I resolve this ?

Skip binding ID but return it as a response

I have a Person struct as follows:
type Person struct {
ID primitive.ObjectID `bson:"_id,omitempty" json:"id"`
HomeAddress Address `bson:"home_address" json:"home_address"`
Pets []*struct {
ID string `json:"id"`
Name string `json:"name"`
Species string `json:"species"`
} `json:"items"`
}
The person's ID is generated automatically in mongodb. A person may have pets with ID that is generated using UUID in the backend. So the JSON sent by user shouldn't include the ID of Person and ID of every pet.
I use this struct when receiving the json sent by the user, saving the data to my mongodb, and return it as a response in my handler as follows:
PersonPostResponse struct {
models.Person
ServerTime time.Time `json:"server_time"`
EditFlag bool `json:"edit_locked"`
}
// handler of POST /person
return func(c *gin.Context) {
newPerson := models.Person{}
if err := c.shouldBindJSON(&newPerson); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if _, err := (*repo).SavePersonData(c, &newPerson); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, PersonPostResponse{newPerson, time.Now(), false})
}
The problem is, when I try to send the JSON with Person ID of 24 chars, it is accepted and saved to the mongodb. I tried putting the suggestions from this link https://github.com/gin-gonic/gin/pull/1733
ID primitive.ObjectID `bson:"id,omitempty" json:"-" binding:"-"`
but the POST response doesn't include the ID of the newly created Person as a result.
How do I skip ID of Person and ID of pets when binding but return it as a response?
Is it not a good practice to use the same struct for 3 different purposes? In reality I have 17 JSON fields and I'm not sure if its good that I rewrite them 3 times.
This can be handled in several ways. One way is to split the structure to support different views of it:
// Use this variant as the Person without ID
type Person struct {
// All person fields, except the ID
}
// Use this variant for DB operations and when you need ID
type DBPerson struct {
ID primitive.ObjectId
Person `bson:",inline"`
}
Another and sometimes simpler way to deal with it is that after binding the user, manually clear the ID you received:
newPerson := models.Person{}
if err := c.shouldBindJSON(&newPerson); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
newPerson.ID=primitive.NilObjectID
Or, you can also given an error if it is nonempty.

how to make request body in gorm for Association belongs to and has one

I have People and Data , where People has one Data and Data belongs to People
how to make a request body JSON for that Association in go gin? I am using gorm for this case,
the documentation of gorm is not clear for me for this case,
i was supposed like
func CreateTodo(db *gorm.DB) func(c *gin.Context) {
var person Person
var data Data
c.bindJSON(&Person)
c.bindJSON(&Data)
db.create(&Person)
db.create(&Data)
c.JSON(200, gin.H{ result : []interface{person, data})
}
type (
Data struct {
ID uint `gorm:"auto_increment"`
PersonID uint
Person *Person `gorm:"foreignkey:PersonID;association_foreignkey:id"`
Address string
Languange string
}
Person struct {
gorm.Model
Email string `gorm:"type:varchar(100);unique_index;not null"`
Password string `gorm:"type:varchar(100);not null"`
Role string `gorm:"size:30;not null"`
DataID uint
Data *Data `gorm:""foreignkey:DataID;association_foreignkey:id"`
}
)
I am sure it will not make the person_id and data_id for FK
what I ask, how I can make the request body for that Association until those request created with FK itself ? should I create then update again for person_id and data_id after it created ??
Gorm will do almost everything for an association link. It seems that "DataID" in your Person struct is useless. See the code below for an example:
package main
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
type (
Data struct {
ID uint `gorm:"auto_increment"`
PersonID uint
Person *Person `gorm:"foreignkey:PersonID;association_foreignkey:id"`
Address string
Languange string
}
Person struct {
gorm.Model
Email string `gorm:"type:varchar(100);unique_index;not null"`
Password string `gorm:"type:varchar(100);not null"`
Role string `gorm:"size:30;not null"`
Data *Data `gorm:""foreignkey:PersonID;association_foreignkey:id"`
}
)
func main() {
db, err := gorm.Open("sqlite3", "test.db")
if err != nil {
panic("failed to connect database")
}
db.LogMode(true)
defer db.Close()
// Migrate the schema
db.AutoMigrate(&Person{}, &Data{})
data := &Data{
Address: "Shanghai,China",
Languange: "Chinese",
}
person := &Person{
Email: "zhjw43#163.com",
Data: data,
}
db.Save(person)
db.DropTable("data", "people")
}

GORM database colum with json data

I am trying to write an email service, where I want to store some data related to email into a Postgres DB using gorm. I have a field which needs to be stored as a JSON blob, which in request passed as a JSON object. When I am trying to migrate, it errors keep saying unsupported type map. When manually add the DB, then run gorm, it doesn't write the row to the database.
I have seen some example where it's using postgres.Json as field types, but I want the field loaded from the request as map[string]string.
// Email : Base with injected fields `UUID`, `CreatedAt`, `UpdatedAt`
type Email struct {
gorm.Model
Status string `grom:"type:varchar(32);not null"`
TemplateID string `grom:"type:varchar(256)"`
ToEmai string `grom:"type:varchar(256);not null"`
FromEmail string `grom:"type:varchar(256);not null"`
ToName string `grom:"type:varchar(256);not null"`
FromName string `grom:"type:varchar(256);not null"`
Subject string `grom:"type:varchar(256)"`
Message string `grom:"type:varchar"`
DynamicData *map[string]string `grom:"type:json"`
}
this is my model.
then I do a gin request:
// SendEmail : sends an email
func SendEmail(c *gin.Context) {
body, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
log.Error("Error reading request body to get rate estimates")
}
var newEmail = models.Email{
Status: "PENDING",
}
jsonErr := json.Unmarshal(body, &newEmail)
if jsonErr != nil {
log.Error(jsonErr)
}
database.DB.Create(&newEmail)
defer c.Request.Body.Close()
err = newEmail.SendSendgridEmail()
if err != nil {
c.JSON(http.StatusBadRequest, err)
} else {
c.JSON(http.StatusOK, "Successfully sent email")
}
}
which then looks into this function
func (e Email) dynamicTemplateEmailBody() []byte {
newMail := mail.NewV3Mail()
emailFrom := mail.NewEmail(e.FromName, e.FromEmail)
newMail.SetFrom(emailFrom)
newMail.SetTemplateID(e.TemplateID)
p := mail.NewPersonalization()
tos := []*mail.Email{
mail.NewEmail(e.ToName, e.ToEmai),
}
p.AddTos(tos...)
if e.DynamicData != nil {
for key, value := range *e.DynamicData {
log.Infof("%s %s", key, value)
p.SetDynamicTemplateData(key, value)
}
}
newMail.AddPersonalizations(p)
return mail.GetRequestBody(newMail)
}
I would like to be able to run DB.AutoMigrate(&models.Email{}) and automatically migrate the objects, or and when I make a request to the endpoint, the row gets added to my email table.

gobuffalo tx.Eager().Create causing error “could not set '%!s(int64=15)' to '<invalid reflect.Value>' ”

I'm trying to write data to PostgreSQL database with relationships using this code.
req := c.Request()
tx := c.Value("tx").(*pop.Connection)
user := &models.User{}
if req.FormValue("user_type") == "0" {
salt, _ := strconv.Atoi(os.Getenv("SALT"))
userType, _ := strconv.Atoi(req.FormValue("user_type"))
user.UserType = int16(userType)
user.Phone, _ = strconv.ParseInt(req.FormValue("phone"), 10, 64)
hash, _ := bcrypt.GenerateFromPassword([]byte(req.FormValue("password")), salt)
user.PassHash = string(hash)
user.Balance = 0
user.Validated = false
user.Person = &models.Person{
Name: req.FormValue("name"),
City: req.FormValue("city"),
}
} else {
}
err := tx.Eager().Create(user)
fmt.Println(errors.WithStack(err))
return c.Render(200, r.JSON(map[string]string{"message": "User successfully created"}))
User model:
type User struct {
ID int64 `db:"id" rw:"w" form:"-"`
UserType int16 `db:"user_type" form:"-"`
Phone int64 `db:"phone"`
PassHash string `db:"pass_hash" form:"-"`
Balance int64 `db:"balance" form:"-"`
Avatar nulls.String `db:"avatar" form:"-"`
Validated bool `db:"validated" form:"-"`
//Company *Company `has_one:"company" fk_id:"user_id"`
Person *Person `has_one:"person" fk_id:"user_id"`
}
Person model:
type Person struct {
ID int64 `db:"id"`
Name string `db:"name"`
Email nulls.String `db:"email"`
City string `db:"city"`
UserId int64 `db:"user_id"`
User *User `belongs_to:"user"`
}
tx.Eager().Create is only creating a row for User model. Row for Person model is not being created. When trying to print, the following error occurs without a stack trace.
could not set '%!s(int64=15)' to '<invalid reflect.Value>'
Any help will be appreciated.