Gorm Associations BeforeCreate Callback not working as expected - go

I have Customer struct
type Customer struct {
Model
Email string `json:"email,omitempty"`
Addresses []Address
}
func (c *Customer) BeforeCreate() (err error) {
if err := c.GenerateID(); err != nil {
return err
}
return c.Marshal()
}
And Address struct
type Address struct {
Model
CustomerID string
Address1 string
}
func (a *Address) BeforeCreate() error {
// The ID is still generated, but the insert query has no `id` in it
if err := a.GenerateID(); err != nil {
return err
}
return nil
}
Model struct
type Model struct {
ID string `gorm:"primary_key;type:varchar(100)"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
func (model *Model) GenerateID() error {
uv4, err := uuid.NewV4()
if err != nil {
return err
}
model.ID = uv4.String()
return nil
}
A Customer:
customer := &model.Customer{
Email: "a",
Addresses: []model.Address{
{
Address1: "abc street",
},
},
}
if err := gormDb.Create(customer).Error; err != nil {
return nil, err
}
I got an error: Error 1364: Field 'id' doesn't have a default value for the Address object
But if I remove the Address associations, things work. And the customer's Id is generated.
customer := & model.Customer{
Email: "a",
//Addresses: []model.Address{
//{
//Address1: "abc street",
//},
//},
}
How can I keep the Address associations and insert both successfully, and still using this ID-generation mechanism?

You can use scope.SetColumn to set a field’s value in BeforeCreate hook
func (a *Address) BeforeCreate(scope *gorm.Scope) error {
scope.SetColumn("ID", uuid.New())
return nil
}
Ref: https://v1.gorm.io/docs/create.html#Setting-Field-Values-In-Hooks

You should use tx.Statement.SetColumn to set a field value in GORM hooks e.g. BeforeCreate. Following is the sample implementation.
func (s Product3) BeforeCreate(tx *gorm.DB) (err error) {
tx.Statement.SetColumn("Price", s.Price+100)
return nil
}
Reference implementation link for GORM Github repo
https://github.com/go-gorm/gorm/blob/ac722c16f90e0e0dffc600c7f69e791c110d788c/tests/hooks_test.go#L306-L309
Reference link from GORM docs
https://gorm.io/docs/update.html#Change-Updating-Values

Related

ValidateCreate function not called on ValidateAndCreate

I'm usign buffalo to build a simple backend to store values.
I have a Target struct defined as follow:
type Target struct {
ID uuid.UUID `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Description string `json:"description" db:"description"`
Observations []Observation `json:"values,omitempty" has_many:"observations" order_by:"updated_at desc"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// ...
func (t *Target) Validate(tx *pop.Connection) (*validate.Errors, error) {
return validate.Validate(
&validators.StringIsPresent{Field: t.Name, Name: "Name"},
&validators.StringLengthInRange{Field: t.Name, Name: "Name", Min: 2, Max: 15},
), nil
}
func (t *Target) ValidateCreate(tx *pop.Connection) (*validate.Errors, error) {
return t.Validate(tx)
}
func (t *Target) ValidateUpdate(tx *pop.Connection) (*validate.Errors, error) {
return t.Validate(tx)
}
A new Target is created using the following action:
func TargetAdd(c buffalo.Context) error {
body, err := io.ReadAll(c.Request().Body)
if err != nil {
log.Get().Error("Error reading body: %v", err)
return err
}
target := &models.Target{}
json.Unmarshal([]byte(body), target)
vErr, err := models.DB.ValidateAndCreate(target)
if err != nil {
log.Get().Error("entity not valid: %s", err)
return response.SendGeneralError(c, err)
}
if vErr.HasAny() {
log.Get().Error("entity not valid: %s", vErr)
return response.SendGeneralError(c, err)
}
return response.SendOKResponse(c, target)
}
The problem is that the ValidateAndCreate function does not call any of the validation functions defined for the Target model, even if it should be so (link to documentation).
Debugging the issue, I found that in the validation.go file, in this function
func (m *Model) validateCreate(c *Connection) (*validate.Errors, error) {
return m.iterateAndValidate(func(model *Model) (*validate.Errors, error) {
verrs, err := model.validate(c)
if err != nil {
return verrs, err
}
if x, ok := model.Value.(validateCreateable); ok {
vs, err := x.ValidateCreate(c)
if vs != nil {
verrs.Append(vs)
}
if err != nil {
return verrs, err
}
}
return verrs, err
})
}
the call to model.Value.(validateCreateable) seems to return ok to false.
Can someone please explain me where's the problem and how it's possibile to validate a model? Thanks.
EDIT:
Changing the import from
"github.com/gobuffalo/validate"
"github.com/gobuffalo/validate/validators"
to
"github.com/gobuffalo/validate/v3"
"github.com/gobuffalo/validate/v3/validators"
seems to fix the problem I have

How to receive a json to insert

I am receiving some values per post and I have a json type field but it arrives empty and if I enter a normal text it works and I do not see the error in the field
the model was updated so that it receives the fields and allows inserting in mysql
POSTman
{
"Code":"1234",//it works
"Desc":"desc",//it works
"Config":{"link":"https://stackoverflow.com/" }, //not works
"Dev":[ {"item":1},{"item":2}]//not works
}
type User struct {
gorm.Model
Code string `gorm:"type:varchar(100);unique_index"`
Desc string `gorm:"type:varchar(255);"`
Config JSON `json:"currencies" gorm:"type:varchar(255);"`
Dev JSON `json:"currencies" gorm:"type:varchar(255);"`
}
func CreateUser(c *gin.Context) {
var usuario models.User
var bodyBytes []byte
if c.Request.Body != nil {
bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
}
data := bytes.NewBuffer(bodyBytes)
fmt.Println(data.Config)
c.BindJSON(&usuario)
db.DB.Create(&usuario)
c.JSON(200, usuario)
}
Model update. receive post form with json fields and insert in mysql
package models
import (
"bytes"
"database/sql/driver"
"errors"
)
type JSON []byte
func (j JSON) Value() (driver.Value, error) {
if j.IsNull() {
return nil, nil
}
return string(j), nil
}
func (j *JSON) Scan(value interface{}) error {
if value == nil {
*j = nil
return nil
}
s, ok := value.([]byte)
if !ok {
errors.New("error")
}
*j = append((*j)[0:0], s...)
return nil
}
func (m JSON) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return m, nil
}
func (m *JSON) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("error")
}
*m = append((*m)[0:0], data...)
return nil
}
func (j JSON) IsNull() bool {
return len(j) == 0 || string(j) == "null"
}
func (j JSON) Equals(j1 JSON) bool {
return bytes.Equal([]byte(j), []byte(j1))
}
Thank you very much to everyone who helped me, I consider that the functionality of receiving a json and saving it in mysql is very common and this can be useful to many people
You can change the JSON like below or You can change the Struct like below (I prefer struct approach)
{
"Code": "1234",
"Desc": "desc",
"Config": {
"Link": "https://stackoverflow.com/"
},
"Dev": [
{
"Item": 1
},
{
"Item": 2
}
]
}
Struct:
type User struct {
gorm.Model
Code string `json:"Code" gorm:"type:varchar(100);unique_index"`
Desc string `json:"Desc" gorm:"type:varchar(255);"`
Config []struct {
Link string `json:"link" gorm:"type:varchar(255);"`
Title string `json:"title" gorm:"type:varchar(255);"`
}
Dev []struct {
Item string `json:"item" gorm:"type:varchar(255);"`
}
}
You have made two kind of mistakes
Your json decoding cannot work because your struct does not match your json. Config is defined as a array of something but in your json you have an object not array, and in Dev the property Item is a int not a string
Your model may not be well defined as you have not defined you joined table. Well I never seen a working example with this kind of definition. I suggest you to declare your nested struct as independent struct.
Here a full working example :
package main
import (
"database/sql"
"encoding/json"
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
const data = `{
"Code":"1234",
"Desc":"desc",
"Config":{"link":"https://stackoverflow.com/" },
"Dev":[ {"item":1},{"item":2}]
}`
type Config struct {
Id int `gorm:"primaryKey"`
Link string `json:"link"`
Title string
UserId int
}
type Item struct {
Id int `gorm:"primaryKey"`
Item int `json:"item"`
UserId int
}
type User struct {
Id int `gorm:"primaryKey"`
Code string
Desc string
Config Config `gorm:"foreignkey:UserId"`
Dev []Item `gorm:"foreignkey:UserId"`
}
func initDb(url string) (*gorm.DB, *sql.DB, error) {
connexion := sqlite.Open(url)
db, err := gorm.Open(connexion, &gorm.Config{})
if err != nil {
return nil, nil, err
}
sql, err := db.DB()
if err != nil {
return nil, nil, err
}
err = db.AutoMigrate(&User{})
if err != nil {
return nil, nil, err
}
err = db.AutoMigrate(&Item{})
if err != nil {
return nil, nil, err
}
err = db.AutoMigrate(&Config{})
if err != nil {
return nil, nil, err
}
return db, sql, nil
}
func run() error {
db, sql, err := initDb("file::memory:?cache=shared")
if err != nil {
return err
}
defer sql.Close()
var user User
err = json.Unmarshal([]byte(data), &user)
fmt.Printf("%#v\n", user)
err = db.Create(&user).Error
if err != nil {
return err
}
var loaded User
db.Preload("Config").Preload("Dev").First(&loaded)
fmt.Printf("%#v\n", loaded)
return nil
}
func main() {
if err := run(); err != nil {
fmt.Println("failed", err)
}
}
try adding this JSON Field in your model
import (
"errors"
"database/sql/driver"
"encoding/json"
)
// JSON Interface for JSON Field of yourTableName Table
type JSON interface{}
// Value Marshal
func (a JSON) Value() (driver.Value, error) {
return json.Marshal(a)
}
// Scan Unmarshal
func (a *JSON) Scan(value interface{}) error {
b, ok := value.([]byte)
if !ok {
return errors.New("type assertion to byte failed")
}
return json.Unmarshal(b,&a)
}
All these answers didn't work for me, but this will work for everyone
Model
// This is the max Thing you need
import "gorm.io/datatypes"
import "encoding/json"
type CMSGenericModel struct {
gorm.Model
//... Other Posts
ExtraData datatypes.JSON `json:"data"`
}
In Handler Function
type CmsReqBody struct {
// ============= RAW ========
Data json.RawMessage `json:"data"`
// other props...
}
cmsBodyRecord := new(models.CMSGenericModel)
cmsBodyPayload := new(CmsReqBody)
if err := c.BodyParser(cmsBodyPayload); err != nil {
return c.Status(503).SendString(err.Error())
}
cmsBodyRecord.ExtraData = datatypes.JSON(cmsBodyPayload.Data)
My Sample Data
{
"title": "Blog Post 1",
"subtitle": "first",
"description": "Updated",
"type": "blog",
"isActive": true,
"uuid": "new",
"data": {
"complex1": ["kkkk", "yyyy"],
"complex2": [
{
"name": "sourav"
},
{
"name": "yahooo"
},
{
"yahoo": "name",
"kjk": ["abbsb", {"data": "abcd"}]
}
]
}
}

How To Use Embedding To Get Data From A Table

I am a PHP programmer and I have just learned Golang for weeks. I am writing REST API to get post information from my 'posts' table. I have the FindPostByID method in posts_controller. This controller uses Post struct which is model
func (p *Post) FindPostByID(db *gorm.DB, pid uint64) (*Post, error) {
var err error
err = db.Debug().Model(&Post{}).Where("id = ?", pid).Take(&p).Error
if err != nil {
return &Post{}, err
}
if p.ID != 0 {
err = db.Debug().Model(&User{}).Where("id = ?", p.AuthorID).Take(&p.Author).Error
if err != nil {
return &Post{}, err
}
}
return p, nil
}
But now I want to add method FindByID into parent struct, such as ParentModel because when I have articles_controller I can use FindByID method in parent struct to get an article info. So I have the following code in parent struct
type ParentModel struct {
}
func (m *ParentModel) FindByID(db *gorm.DB, uid uint64) (*ParentModel, error) {
var err error
err = db.Debug().Model(ParentModel{}).Where("id = ?", uid).Take(&m).Error
if err != nil {
return &ParentModel{}, err
}
if gorm.IsRecordNotFoundError(err) {
return &ParentModel{}, errors.New("User Not Found")
}
return m, err
}
And I change in the Post struct like following:
type Post struct {
ParentModel
ID uint64 `gorm:"primary_key;auto_increment" json:"id"`
Title string `gorm:"size:255;not null;unique" json:"title"`
Content string `gorm:"size:255;not null;" json:"content"`
Author User `json:"author"`
AuthorID uint32 `sql:"type:int REFERENCES users(id)" json:"author_id"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
}
I change FindPostByID method in posts_controller as well:
func (server *Server) GetPost(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pid, err := strconv.ParseUint(vars["id"], 10, 64)
if err != nil {
responses.ERROR(w, http.StatusBadRequest, err)
return
}
post := models.Post{}
postReceived, err := post.FindByID(server.DB, pid)
if err != nil {
responses.ERROR(w, http.StatusInternalServerError, err)
return
}
responses.JSON(w, http.StatusOK, postReceived)
}
When I run my program, have the err: Table 'golang_rest_api.parent_models' doesn't exist.
How I can do to use inheritance method like PHP language
The error Table 'golang_rest_api.parent_models' doesn't exist means that no table named parent_models exists in your database.
This is because gorm by default uses the pluralised camelcase name of your struct, which you supplied in gorm's Model() method, as its table's name.
Gorm provides in-built support for many kinds of associations which you can find here. I would also like to ask you to complete your ParentModel struct's definition. It's empty right now.

Get Object returns empty string as values Golang

I am trying to get users from the DB with gorm and I get a 200 response but the user object got has empty values
{
"id": "",
"firstName": "",
"lastName": "",
"email": "",
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"deletedAt": null
}
here is my model and my reg method
func (user *User) Get(db *gorm.DB, uid string) *errors.Error {
fmt.Println("This is user ID %s", uid)
var err error
err = db.Debug().Model(User{}).Where("id = ?", uid).Take(&user).Error
if err != nil {
return errors.NewBadRequestError(fmt.Sprintf("error when trying to get user: %s", err.Error()))
}
if gorm.IsRecordNotFoundError(err) {
return errors.NewBadRequestError(fmt.Sprintf("user not found: %s", err.Error()))
}
return nil
}
the model is
type User struct {
ID string `gorm:"primary_key;" json:"id"`
FirstName string `gorm:"size:255;not null;column:firstName" json:"firstName"`
LastName string `gorm:"size:255;not null;column:lastName" json:"lastName"`
Email string `gorm:"size:100;not null;unique;column:email" json:"email"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP;column:createdAt" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP;column:updatedAt" json:"updatedAt"`
DeletedAt *time.Time `sql:"index;column:deletedAt" json:"deletedAt"`
}
my services logic
func GetUser(userId string) (*users.User, *errors.Error) {
// result := &users.User{Base: {
// }, }
fmt.Println("This is user ID 2 %s", userId)
result := &users.User{}
if err := result.Get(database.DB, userId); err != nil {
return nil, err
}
return &users.User{}, nil
}
my controller
func GetUser(c *gin.Context) {
userId := c.Param("user_id")
fmt.Println(userId)
user, getErr := services.GetUser(userId)
if getErr != nil {
// TODO: handle user createing error
c.JSON(getErr.Status, getErr)
return
}
c.JSON(http.StatusOK, user)
}
In your service logic, you are returning a pointer and not a value.
func GetUser(userId string) (*users.User, *errors.Error) {
fmt.Println("This is user ID 2 %s", userId)
result := &users.User{}
if err := result.Get(database.DB, userId); err != nil {
return nil, err
}
return &users.User{}, nil
}
It should be:
func GetUser(userId string) (*users.User, *errors.Error) {
fmt.Println("This is user ID 2 %s", userId)
result := &users.User{ID: userId}
if err := result.Get(database.DB, userId); err != nil {
return nil, err
}
return result, nil
}

Read flattened entity from cloud datastore in golang

func (db *dataStore) AddAcceptance(ctx context.Context, req *acceptance.PolicyAcceptance) (uint64, error) {
accpKey := datastore.IncompleteKey("Acceptance", nil)
key, err := db.Put(context.Background(), accpKey, req);
if err != nil {
log.Fatalf("Failed to save Acceptance: %v", err)
}
accpKey = key
val := uint64(accpKey.ID)
return val, err
}
type PolicyAcceptance struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
PolicyNumber int64 `protobuf:"varint,2,opt,name=policyNumber" json:"policyNumber,omitempty"`
Version string `protobuf:"bytes,3,opt,name=version" json:"version,omitempty"`
SignerData *SignerData `protobuf:"bytes,4,opt,name=signerData" json:"signerData,omitempty" datastore:",flatten"`
GroupID int64 `protobuf:"varint,5,opt,name=groupID" json:"groupID,omitempty"`
LocationID int64 `protobuf:"varint,6,opt,name=locationID" json:"locationID,omitempty"`
BusinessId int64 `protobuf:"varint,7,opt,name=businessId" json:"businessId,omitempty"`
AcceptedDate *google_protobuf.Timestamp `protobuf:"bytes,8,opt,name=acceptedDate" json:"acceptedDate,omitempty" datastore:",flatten"`
IssuerName string `protobuf:"bytes,9,opt,name=issuerName" json:"issuerName,omitempty"`
Place string `protobuf:"bytes,10,opt,name=place" json:"place,omitempty"`
}
type SignerData struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Email string `protobuf:"bytes,2,opt,name=email" json:"email,omitempty"`
Type string `protobuf:"bytes,3,opt,name=type" json:"type,omitempty"`
Id int64 `protobuf:"varint,4,opt,name=id" json:"id,omitempty"`
}
datastore:",flatten" saves data as flattened in data store. The property names becomes flattened with . like SignerData.Id as property name but when it's read from data store, how can I map it back to struct? It fails throwing an error like:
SignerData.Id could not be found as a key in struct. Error: No such
struct field.
func (db *dataStore) GetAcceptanceBySignerData(ctx context.Context, req *acceptance.SignerData) (*acceptance.ListOfPolicyAcceptance, error) {
query := datastore.NewQuery("Acceptance").Filter("SignerData.Id =", req.Id)
var accpArr acceptance.ListOfPolicyAcceptance
var err error
it := db.Run(ctx, query)
for {
var accept acceptance.PolicyAcceptance
_, err := it.Next(&accept)
if err == iterator.Done {
break
}
if err != nil {
log.Fatalf("Error fetching : %v", err)
}
accpArr.AcceptanceList = append(accpArr.AcceptanceList, &accept)
}
return &accpArr, err
}

Resources