GORM Association not Updating - go

With the following Code
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Person struct {
gorm.Model
FirstName string `json:"firstname"`
LastName string `json:"lastname"`
Addresses []PersonAddressLines `gorm:"many2many:person_addresses;" json:"addresses"`
}
type PersonAddressLines struct {
gorm.Model
AddressLine1 string `json:"address_line1"`
AddressLine2 string `json:"address_line2"`
AddressLine3 string `json:"address_line3"`
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.Migrator().DropTable("people")
db.Migrator().DropTable("person_addresses")
db.Migrator().DropTable("person_address_lines")
// Migrate the schema
err = db.AutoMigrate(&PersonAddressLines{}, &Person{})
if err != nil {
panic("Cannot migrate")
}
fmt.Println(">> Create Person")
// Create
var OriginalPerson = &Person{FirstName: "Sample", LastName: "Person", Addresses: []PersonAddressLines{
{AddressLine1: "1 Some Street", AddressLine2: "Somewhere"},
}}
fmt.Println(">> Reload Person")
db.Debug().Create(&OriginalPerson)
// Load the person again and update
var person Person
db.Preload("Addresses").First(&person, 1) //get the first person..
person.Addresses[0].AddressLine2 = "CHANGED!!!"
fmt.Println(">> Update with New Address")
db.Debug().Save(&person)
fmt.Println (">> New value " + person.Addresses[0].AddressLine2)
// Reload from database is it changed??
fmt.Println(">> Reloading Person again...")
var newPerson Person
db.Preload("Addresses").First(&newPerson, 1) //get the first person..
fmt.Println("Changed Person Value: Expect 'CHANGED!!!' == " + person.Addresses[0].AddressLine2)
fmt.Println("New Person Value: Expect 'CHANGED!!!' == " + newPerson.Addresses[0].AddressLine2)
}
The change to
person.Addresses[0].AddressLine2 = "CHANGED!!!"
Is not being updated in the database. Is this a bug or am I doing something wrong?
I have the same issue with postgres also.

Related

How to return nested entities after creating a new object?

Model Account contains nested structures - Currency and User
When I create a new instance of Account in DB, and then return it in my response, nested entities are empty:
type Account struct {
BaseModel
Name string `gorm:"size:64;not null" json:"name"`
Balance decimal.Decimal `gorm:"type:decimal(16, 2);default:0;not null;" json:"balance"`
UserID int `gorm:"not null" json:"-"`
User User `gorm:"foreignKey:UserID" json:"user"`
CurrencyID int `gorm:"not null" json:"-"`
Currency Currency `gorm:"foreignKey:CurrencyID" json:"currency"`
}
type CreateAccountBody struct {
Name string `json:"name" binding:"required"`
Balance decimal.Decimal `json:"balance"`
CurrencyID int `json:"currency_id" binding:"required"`
}
func CreateAccount(ctx *gin.Context) {
body := CreateAccountBody{}
if err := ctx.Bind(&body); err != nil {
log.Println("Error while binding body:", err)
ctx.JSON(
http.StatusBadRequest,
gin.H{"error": "Wrong request parameters"},
)
return
}
account := Account {
Name: body.Name,
Balance: body.Balance,
CurrencyID: body.CurrencyID,
UserID: 1,
}
if result := db.DB.Create(&account); result.Error != nil {
log.Println("Unable to create an account:", result.Error)
}
ctx.JSON(http.StatusCreated, gin.H{"data": account})
}
To avoid this problem, I refresh account variable with separate query:
db.DB.Create(&account)
db.DB.Preload("User").Preload("Currency").Find(&account, account.ID)
ctx.JSON(http.StatusCreated, gin.H{"data": account})
Is this the most effective and correct way to achieve the desired result?
I'm gonna share you how usually I managed this scenario. First, let me share the code.
main.go file
package main
import (
"context"
"gogindemo/handlers"
"github.com/gin-gonic/gin"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var (
db *gorm.DB
ctx *gin.Context
)
func init() {
dsn := "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable"
var err error
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&handlers.Currency{})
db.AutoMigrate(&handlers.User{})
db.AutoMigrate(&handlers.Account{})
}
func AddDb() gin.HandlerFunc {
return func(ctx *gin.Context) {
ctx.Request = ctx.Request.WithContext(context.WithValue(ctx.Request.Context(), "DB", db))
ctx.Next()
}
}
func main() {
db.Create(&handlers.User{Id: 1, Name: "john doe"})
db.Create(&handlers.User{Id: 2, Name: "mary hut"})
db.Create(&handlers.Currency{Id: 1, Name: "EUR"})
db.Create(&handlers.Currency{Id: 2, Name: "USD"})
r := gin.Default()
r.POST("/account", AddDb(), handlers.CreateAccount)
r.Run()
}
Here, I've just added the code for bootstrapping the database objects and add some dummy data to it.
handlers/handlers.go file
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
type User struct {
Id int
Name string
}
type Currency struct {
Id int
Name string
}
type Account struct {
Id int
Name string `gorm:"size:64;not null" json:"name"`
Balance decimal.Decimal `gorm:"type:decimal(16, 2);default:0;not null;" json:"balance"`
UserID int `gorm:"not null" json:"-"`
User User `gorm:"foreignKey:UserID" json:"user"`
CurrencyID int `gorm:"not null" json:"-"`
Currency Currency `gorm:"foreignKey:CurrencyID" json:"currency"`
}
type CreateAccountBody struct {
Name string `json:"name" binding:"required"`
Balance decimal.Decimal `json:"balance"`
CurrencyID int `json:"currency_id" binding:"required"`
}
func CreateAccount(c *gin.Context) {
db, ok := c.Request.Context().Value("DB").(*gorm.DB)
if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
return
}
var accountReq CreateAccountBody
if err := c.BindJSON(&accountReq); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "wrong request body payload"})
return
}
// create Account & update the "account" variable
account := Account{Name: accountReq.Name, Balance: accountReq.Balance, CurrencyID: accountReq.CurrencyID, UserID: 1}
db.Create(&account).Preload("Currency").Preload("User").Find(&account, account.Id)
c.IndentedJSON(http.StatusCreated, account)
}
Within this file, I actually talk with the database through the DB passed in the context. Now, back to your question.
If the relationship between the Currency/Account and User/Account is of type 1:1, then, you should rely on the Preload clause. This will load the related entity in a separate query instead of adding it in an INNER JOIN clause.
Let me know if this solves your issue, thanks!

Replace incoming post request data before bind with Go Gin?

I made a simple post API to store articles in database using Gorm And Go Gin.
problem with API showing when I tried to post the category name instead of the category id because the struct declaring it as int32 type
I made a simple function to getting the id of the category instead of the name but i don't know how to inject it within the incoming request
The article model
package models
import (
"gorm.io/gorm"
)
type News struct {
Id int `gorm:"primary_key;auto_increment;not_null" json:"id"`
Category int32 `gorm:"category" json:"category" binding:"required"`
CountryID int32 `gorm:"country_id" json:"country_id" binding:"required"`
LangID int32 `gorm:"lang_id" json:"lang_id" binding:"required"`
SourceId int32 `gorm:"source_id" json:"source_id" binding:"required"`
HashtagId int32 `gorm:"hashtag_id" json:"hashtag_id" binding:"required"`
Title string `gorm:"title" json:"title" binding:"required"`
Content string `gorm:"content" json:"content" binding:"required"`
Description string `gorm:"description" json:"description" binding:"required"`
Summery string `gorm:"summery" json:"summery" binding:"required"`
ImageUrl string `gorm:"image_url" json:"image_url" binding:"required"`
SourceUrl string `gorm:"source_url" json:"source_url" binding:"required"`
Author string `gorm:"author" json:"author" `
}
func CreateArticle(db *gorm.DB, article *News) (err error) {
err = db.Create(article).Error
if err != nil {
return err
}
return nil
}
Category Model
package models
import "gorm.io/gorm"
type Category struct {
Id int32 `gorm:"primary_key;auto_increment;not_null" json:"id"`
Name string `gorm:"category_name" json:"category" binding:"required"`
Parent int32 `gorm:"parent" json:"parent"`
}
func GetCategoryByName(db *gorm.DB, category *Category, name string) (err error) {
err = db.Where("LOWER(category_name) Like LOWER(?)", "%"+name+"%").First(category).Error
if err != nil {
return err
}
return nil
}
News Controller which implement the models
package controllers
import (
"errors"
"net/http"
"github.com/fouad82/news-app-go/models"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"gorm.io/gorm"
)
type NewsRepo struct {
db *gorm.DB
}
func NewArticle() *NewsRepo {
db := models.InitDb()
return &NewsRepo{db: db}
}
// CreateArticle Create Article
func (repository *NewsRepo) CreateArticle(c *gin.Context) {
var Category models.Category
if err := c.ShouldBindBodyWith(&Category, binding.JSON); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": &Category})
return
}
err := models.GetCategoryByName(repository.db, &Category, Category.Name)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.AbortWithStatus(http.StatusNotFound)
return
}
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"status": "error", "error": err})
return
}
var CategoryId = Category.Id
var Article models.News
Article.Category = CategoryId
if err := c.ShouldBindBodyWith(&Article, binding.JSON); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": err})
return
}
err = models.GetArticleByTitle(repository.db, &Article, Article.Title)
if err == nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.AbortWithStatus(http.StatusNotFound)
return
}
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"status": "error", "error": "Title Created Before"})
return
}
createArticle := models.CreateArticle(repository.db, &Article)
if createArticle != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"status": "error", "error": createArticle})
return
}
c.JSON(http.StatusOK, gin.H{"status": "ok", "result": "Article Created"})
}
in this line i tried to inject the int value within the article struct abut it still getting error that im passing string value not int and this because it still read from the request json not the injected parameter
var CategoryId = Category.Id
var Article models.News
Article.Category = CategoryId
I believe the function you are using is out of request scope, so it's basically can not affect the gin.Context.
What I suggest is to copy the context in order to modify it with your changed data. Article.Category in specific.
...
c.Copy
...
The other thing is about the function - it's evaluating itself - I would suggest to exclude it from CreateArticle function
createArticle := models.CreateArticle(repository.db, &Article)
What I also suggest is to return changed struct and work with it separately from request payload - probably you need to create another function for that:
func (repository *NewsRepo) ChangedArticle(c *gin.Context)News{
changedNews := News{
Id: 0,
Category: 0, // Here is where you change it
CountryID: 0,
LangID: 0,
SourceId: 0,
HashtagId: 0,
Title: "",
Content: "",
Description: "",
Summery: "",
ImageUrl: "",
SourceUrl: "",
Author: "",
}
return changedNews
}
and than in your main function - just use it:
...
createArticle := models.ChangedArticle
...
I could miss some details, but I hope this top level explanation helps.

How to get the form data by postman in short way using golang?

I am retrieving form data using postman, but the code is too long. Is there is any method for getting the data in short form? Here's the code I am using:
Customer struct:
type Customer struct {
FirstName string `json:"first_name" bson:"first_name"`
LastName string `json:"last_name" bson:"last_name"`
Email string `json:"email" bson:"email"`
}
type Customers []Customer
type new_user struct {
first_name string
last_name string
email string
}
Function for retrieving the form data called by the route:
function GetData(c *gin.Context){
first_name := c.PostForm("first_name")
last_name := c.PostForm("last_name")
email := c.PostForm("email")
reqBody := new(new_user)
err := c.Bind(reqBody)
if err != nil {
fmt.Println(err)
}
customer.FirstName = first_name
customer.LastName = last_name
customer.Email = email
}
I'm getting 3 form values. Suppose I need to get 50 values, then the function will be much bigger.
You can parse HTTP request body yourself, like to following
option 1:
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/json"
"log"
)
type Customer struct {
FirstName string `json:"first_name" bson:"first_name"`
LastName string `json:"last_name" bson:"last_name"`
Email string `json:"email" bson:"email"`
}
func process(context *gin.Context) {
var customer = &Customer{}
req := context.Request
err := json.NewDecoder(req.Body).Decode(customer)
if err != nil {
log.Fatal()
}
}
option 2:
Encoding to map to decode to struct (not recommended)
import (
"github.com/gin-gonic/gin"
"encoding/json"
"bytes"
"log"
)
type Customer struct {
FirstName string `json:"first_name" bson:"first_name"`
LastName string `json:"last_name" bson:"last_name"`
Email string `json:"email" bson:"email"`
}
func Process(context *gin.Context) {
req := context.Request
var aMap = map[string]interface{}{}
for key, values := range req.PostForm {
aMap[key]=values[0]
}
var buf = new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(aMap)
if err != nil {
log.Fatal(err)
}
var customer = &Customer{}
json.NewDecoder(buf).Decode(customer)
if err != nil {
log.Fatal(err)
}
}
As the mkopriva tells me a short way to do this. I got the answer for it. It can be shorter by doing the following code.
type Customer struct {
FirstName string `form:"first_name" json:"first_name" bson:"first_name"`
LastName string `form:"last_name" json:"last_name" bson:"last_name"`
Email string `form:"email" json:"email" bson:"email"`
}
In the function the code is:-
customer := new(Customer)
if err := c.Bind(customer); err != nil {
return nil, err
}
fmt.Println(customer)
It will print the data from the form-data of the postman.

Query Document with different struct for results

I have a collection of documents that were inserted into Mongo looking something like this:
type Stats struct {
UserStatus string `json:"userStatus" bson:"userStatus"`
... a bunch more fields
}
type User struct {
ID bson.ObjectId `json:"-" bson:"_id"`
LastName string `json:"lastName" bson:"lastName"`
FirstName string `json:"firstName" bson:"firstName"`
Role string `json:"role" bson:"role"`
Tags []string `json:"tags" bson:"tags"`
... (a bunch more fields)
Stats UserStats `json:"stats" bson:"stats"`
}
I want to query it to get a specific report, so I tried this:
func UserNameReport() {
... get mongo session, etc.
// create struct of just the data I want returned
type UserNames struct {
LastName string `json:"lastName" bson:"lastName"`
FirstName string `json:"firstName" bson:"firstName"`
... etc
UserStats Stats `json:"stats" bson:"stats"`
}
projection := bson.M{"lastName":1, "firstName":1, etc}
result := []UserNames{}
err := x.Find({query user collection}).Select(projection).All(&result)
...
}
This works - my question is, how can I include just ONE field from the 'Stats' struct? In other words,
I essentially want the "projection" to be this:
projection := bson.M{"lastName":1, ..., "stats.userStatus":1} <-- stats.userStatus doesn't work
...
err := x.Find({query user collection}).Select(projection).All(&result)
I get the entire "Stats" embedded struct in the results - how can I filter out just one field from the sub-document in and put it into the result set?
Thanks!
It works perfectly for me, with MongoDB 2.6.5
The following code:
package main
import (
"fmt"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"log"
)
type Statistics struct {
Url string
Hits int
}
type Person struct {
Num int
Uuid string
Name string
Stats []Statistics
}
func main() {
// Connect to the database
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
// Remove people collection if any
c := session.DB("test").C("people")
c.DropCollection()
// Add some data
err = c.Insert(
&Person{1, "UUID1", "Joe", []Statistics{Statistics{"a", 1}, Statistics{"b", 2}}},
&Person{2, "UUID2", "Jane", []Statistics{Statistics{"c", 3}, Statistics{"d", 4}}},
&Person{3, "UUID3", "Didier", []Statistics{Statistics{"e", 5}, Statistics{"f", 6}}})
if err != nil {
log.Fatal(err)
}
result := []Person{}
err = c.Find(bson.M{"$or": []bson.M{bson.M{"uuid": "UUID3"}, bson.M{"name": "Joe"}}}).Select(bson.M{"num": 1, "name": 1, "stats.hits": 1}).All(&result)
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
results in:
[{1 Joe [{ 1} { 2}]} {3 Didier [{ 5} { 6}]}]
... which is precisely what I would expect.
Maybe this will help others - essentially I was trying to take a document with an embedded document and return a result set like I would do in SQL with a select a.LastName + ', ' + a.FirstName as Name, b.OtherData and in essence have a different 'table' / 'document'.
So here is my current solution - love to get better ones (more performant?) though!
I created a new struct and I'm using the 'mapstructure' library
import "github.com/goinggo/mapstructure"
type Stats struct {
UserStatus string `json:"userStatus" bson:"userStatus"`
... a bunch more fields
}
type User struct {
ID bson.ObjectId `json:"-" bson:"_id"`
LastName string `json:"lastName" bson:"lastName"`
FirstName string `json:"firstName" bson:"firstName"`
Role string `json:"role" bson:"role"`
Tags []string `json:"tags" bson:"tags"`
... (a bunch more fields)
Stats UserStats `json:"stats" bson:"stats"`
}
type MyReportItem struct {
FirstName string `json:"firstName" jpath:"firstName"`
LastName string `json:"lastName" jpath:"lastName"`
Status string `json:"status" jpath:"stats.userStatus"`
}
type MyReport struct {
Results []MyReportItem `json:"results"`
}
func xxxx(w http.ResponseWriter, r *http.Request) {
var users MyReportItem
// the results will come back as a slice of map[string]interface{}
mgoResult := []map[string]interface{}{}
// execute the query
err := c.Find(finder).Select(projection).All(&mgoResult)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
user := MyReportItem{}
// iterate through the results and decode them into the MyReport struct
for _, x := range mgoResult {
docScript, _ := json.Marshal(x)
docMap := map[string]interface{}{}
json.Unmarshal(docScript, &docMap)
err := mapstructure.DecodePath(docMap, &user)
if err == nil {
users.Results = append(users.Results, user)
}
}
... send back the results ...
_ := json.NewEncoder(w).Encode(&users)
}
Now I get a slice of objects in the form:
results: [
{
firstName: "John",
lastName: "Doe",
status: "active"
}
...
]
Instead of:
{
firstName: "John",
lastName: "Doe",
stats: {
status: "active"
}
}

Golang declaring variables as NULLs

As per the example below, I need to get email and firstname and dateofbirth and so on as NullStrings and NullTimes as required, as my User struct is using them. How do I declare variables as NULLs
package entities
import (
"database/sql"
"github.com/go-sql-driver/mysql"
"testing"
"time"
)
var (
email = sql.NullString("mail#gmail.com") << Does not work
hashedPassword = "password"
firstName = "Lee"
lastName = "Brooks"
dateOfBirth = time.Now
height = 1.85
weight = 101.3
)
func privacyConcernedUser() *User {
return &User{
Email: email, << These all complain eg: cannot use Email (type string) as type sql.NullString in field value
HashedPassword: hashedPassword,
FirstName: firstName,
LastName: lastName,
}
}
sql.NullString isn't a drop-in replacement for the string type, you have to some work with it.
package main
import "database/sql"
import "fmt"
type User struct {
Name string
}
func main() {
var nsName sql.NullString
if err := nsName.Scan("User's Name"); err != nil {
panic(err)
}
user := &User{Name: nsName.String}
fmt.Println(user)
}
You can check if the NullString is valid with nsName.Valid.
http://golang.org/pkg/database/sql/#NullString
sql.NullString("mail#gmail.com") << Does not work
Try:
sql.NullString{"mail#gmail.com", true}
see http://golang.org/pkg/database/sql/#NullString

Resources