datastore doesn't put nested struct in Go - go

The structs look like this:
type Account struct {
Username string // NameKey
Password []byte `datastore:",noindex"`
RegistrationTime time.Time `datastore:",noindex"`
AppUser
}
type AppUser struct {
LoginEntries []LoginEntry `datastore:",noindex"`
}
type LoginEntry struct {
Timestamp time.Time `datastore:",noindex"`
UserAgent string `datastore:",noindex"`
IP string `datastore:",noindex"`
}
I'm also sure I put the data correctly, because other data has no problem being updated, and I tried to fmt.Println the content of account Account right before saving it in datastore (Put(ctx, key, &account) and when I print it then I can see all the AppUser information.. but when I later Get the user then the AppUser info doesn't exist (just shows up as {[]}).
I'm quite certain I have stored nested struct slices before in datastore without any problems, so I'm quite confused as to what might be causing it..
The Put func:
func PutAccount(ctx context.Context, acc Account) (*datastore.Key, error) {
if isValidUsername(acc.Username) != true {
return nil, errors.New("Invalid username.")
}
var hashedPassword []byte
if acc.RegistrationTime.IsZero() {
var err error
hashedPassword, err = bcrypt.GenerateFromPassword(acc.Password, 12)
if err != nil {
return nil, err
}
} else {
hashedPassword = acc.Password
}
account := Account{
Username: strings.ToLower(acc.Username),
Password: hashedPassword,
RegistrationTime: time.Now(),
AppUser: acc.AppUser}
fmt.Println("PutAccount, account:", account) // before saving it prints the AppUser without problems
key := datastore.NameKey("Account", account.Username, nil)
return database.DatastoreClient().Put(ctx, key, &account)
}
the Get func:
func GetAccount(ctx context.Context, key *datastore.Key) (Account, error) {
var account Account
err := database.DatastoreClient().Get(ctx, key, &account)
if err != nil {
return account, err
}
return account, nil
}

Using named struct works. eg:
type Account struct {
Username string // NameKey
Password []byte `datastore:",noindex"`
RegistrationTime time.Time `datastore:",noindex"`
AppUser AppUser
}
As to why anonymous embedded struct do not, this is probably worthy of an issue.

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!

Error:interface must be a pointer to struct Error Returned in fiber (golang), how to solve this?

I'm new in Golang Programming
I'm Faceing an issue..
I'm trying to acces my sent body data by "BodyParser"functtion
But I got an error
schema: interface must be a pointer to struct
I'm Giving the Function Bellow
func CreateService(c *fiber.Ctx) error {
if c.Locals("user_type") != "1" {
return c.SendString("Wrong One")
}
file, err := c.FormFile("image")
// Check for errors:
if err != nil {
fmt.Println(err.Error())
return c.JSON("Something error")
}
// 👷 Save file to root directory:
c.SaveFile(file, fmt.Sprintf("./%s", file.Filename))
// 👷 Save file inside uploads folder under current working directory:
c.SaveFile(file, fmt.Sprintf("./uploads/%s", file.Filename))
// 👷 Save file using a relative path:
c.SaveFile(file, fmt.Sprintf("/tmp/uploads_relative/%s", file.Filename))
var data map[string]string
if err := c.BodyParser(&data); err != nil {
return err
}
service := models.Services{
Title: data["title"],
Src: PORT + "/" + file.Filename,
}
database.DB.Create(&service)
return c.JSON(service)
}
model.Services is
type Services struct {
Id uint `json:"id"`
Title string `json:"title"`
Src string `json:"src"`
}
Please Help me out. Thanks a lot in advance!!!
We need to provide pointer to a struct in GoFiber_v2 not *map[string]string. This is causing the issue.
Below Function for getting data from GoFiber_v2 BodyParser:
func Register(c *fiber.Ctx) error {
var user models.User
err := c.BodyParser(&user)
if err != nil {
return err
}
fmt.Printf("%#+v", user)
}
Model Package
package models
type User struct {
Id uint `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
Password string `json:"password"`
}

Preloading 'belongs to' associations both ways

I'd like to use GORM's 'belongs to' association in a way similar to Django's one-to-one relationships. Consider the following example in which each User is associated with one Profile:
package main
import (
"fmt"
"os"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/sirupsen/logrus"
)
type User struct {
gorm.Model
Name string
}
func (user User) String() string {
return fmt.Sprintf("User(Name=%s)", user.Name)
}
type Profile struct {
gorm.Model
UserID uint
User User
Name string
}
func (profile Profile) String() string {
return fmt.Sprintf("Profile(Name=%s, User=%d)", profile.Name, profile.UserID)
}
func (user *User) AfterCreate(scope *gorm.Scope) error {
profile := Profile{
UserID: user.ID,
Name: user.Name,
}
return scope.DB().Create(&profile).Error
}
const dbName = "examplegorm.db"
func main() {
db, err := gorm.Open("sqlite3", dbName)
if err != nil {
logrus.Fatalf("open db: %v", err)
}
defer func() {
db.Close()
os.Remove(dbName)
}()
db.LogMode(true)
db.AutoMigrate(&User{})
db.AutoMigrate(&Profile{})
user := User{Name: "jinzhu"}
if err := db.Create(&user).Error; err != nil {
logrus.Fatalf("create user: %v", err)
}
var profile Profile
if err := db.Where(Profile{UserID: user.ID}).Preload("User").First(&profile).Error; err != nil {
logrus.Fatalf("get profile: %v", err)
}
logrus.Infof("profile: %v", profile)
logrus.Infof("user: %v", profile.User)
}
In this example, I query for a Profile and preload its User. I would actually like to do this the other way, however: query a User and preload its Profile.
As I understand it, in Django you would be able to access both the profile.user and the user.profile, but if I try to add Profile and ProfileID fields to the User model,
type User struct {
gorm.Model
Name string
Profile
ProfileID uint
}
I get an 'invalid recursive type' error:
# command-line-arguments
./gorm_belongs_to.go:23:6: invalid recursive type Profile
Is there any way to get a user.Profile in this GORM example?
I think that the problem in your case is using the name Profile which is the same as the type.
If your User struct will look something like this it should work:
type User struct {
gorm.Model
Name string
UserProfile Profile
UserProfileID uint
}

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
}

Reuse or cast structs for validation, API results, and DB saves in Go

I'm trying to write an API that will do different things with the data depending on its purpose.
API results - The API should expose certain fields to the user
Validation - Validation should handle different schemas e.g. login form doesn't require Name, but register does
Database - The database should save everything, including password etc. - if I try using the same struct for the API and DB with json:"-" or un-exporting the field the database save also ignores the field
Some sample code is below with a couple of comments in capitals to show where I ideally need to type cast to change the data. As they are different structs, they cannot be type cast, so I get an error. How can I fix this?
Alternatively, what is a better way of doing different things with the data without having lots of similar structs?
// IDValidation for uuids
type IDValidation struct {
ID string `json:"id" validate:"required,uuid"`
}
// RegisterValidation for register form
type RegisterValidation struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
}
// UserModel to save in DB
type UserModel struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password,omitempty"`
Active bool `json:"active,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"`
jwt.StandardClaims
}
// UserAPI data to display to user
type UserAPI struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Email string `json:"email"`
Active bool `json:"active,omitempty"`
}
// register a user
func register(c echo.Context) error {
u := new(UserModel)
if err := c.Bind(u); err != nil {
return err
}
// NEED TO CAST TO RegisterValidation HERE?
if err := c.Validate(u); err != nil {
return err
}
token, err := u.Register()
if err != nil {
return err
}
return c.JSON(http.StatusOK, lib.JSON{"token": token})
}
// retrieve a user
func retrieve(c echo.Context) error {
u := IDValidation{
ID: c.Param("id"),
}
if err := c.Validate(u); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// NEED TO CAST USER TO UserAPI?
user, err := userModel.GetByID(u.ID)
if err != nil {
return err
}
return c.JSON(200, lib.JSON{"message": "User found", "user": user})
}

Resources