I have different types for a REST API which have some things in common:
All types have Id
All types have Type
All types have Attributes, but Attributes differ
As I would not like to have a big count of unnecessary types I came up with a type format but which does not seem to be scannable by sqlx.
type Pool struct {
Id *string `json:"id" db:"pool_id"`
Type *string `json:"type"`
Attributes struct {
Name *string `json:"name" db:"name"`
Description *string `json:"description" db:"description"`
} `json:"attributes"`
}
type User struct {
Id *string `json:"id" db:"user_id"`
Type *string `json:"type"`
Attributes struct {
FirstName *string `json:"first_name" db:"first_name"`
LastName *string `json:"last_name" db:"last_name"`
} `json:"attributes"`
}
When trying to load the SQL result into the variable I get an error: missing destination name name in *main.Pool
package main
import (
_ "github.com/jmoiron/sqlx"
)
func selectOne() interface{} {
pa := Pool{}
err := dbx.Get(&pa, "SELECT * FROM pools LIMIT 1")
if err != nil {
panic(err)
}
return pa
}
As name is present I do not see why this does not work. Any advice how to solve this is highly appreciated!
package main
import (
_ "github.com/jmoiron/sqlx"
)
func selectOne() interface{} {
pa := Pool{}
err := dbx.Get(&pa, `SELECT p.id, p.type, a.name AS "attributes.name" FROM pools p left join attributes a on a.p LIMIT 1`)
if err != nil {
panic(err)
}
return pa
}
Related
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!
I am trying to get extra columns from a many2many relationships on Gorm. Example
Part
type Part struct {
Id unit
Name string
}
Diagram
type Diagram struct {
Id unit
Name string
Parts []Part `gorm:"many2many:diagram_parts;"`
}
DiagramPart
type DiagramPart struct{
DiagramId uint `gorm:"primaryKey"`
PartId uint `gorm:"primaryKey"`
PartDiagramNumber int
PartNumber string
PartDescription string
}
This is what I have done trying to retrieve PartNumber and PartDescription in Parts.
diagram := &Diagram{}
db := s.db.Where("id = ?", 1).
Preload("Parts", func(db *gorm.DB) *gorm.DB {
return db.Select("parts.*, diagram_parts.part_number, diagram_parts.part_description").
Joins("left join diagram_parts on diagram_parts.part_id = parts.id")
}).
First(diagram)
Unfortunately, I am not able to retrieve part_number, part_description. How should I go about it?
You can add field PartNumber and PartDescription on struct Part OR Diagram, then add tag gorm:"-:migration;->" on than fields to ignore migration and to readonly mode. But on your situation, you can add it in struct Part because you already preload it.
source: https://gorm.io/docs/models.html#Field-Level-Permission
here's the example:
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Part struct {
Id uint `gorm:"primaryKey"`
Name string
PartNumber string `gorm:"-:migration;->"`
PartDescription string `gorm:"-:migration;->"`
}
type Diagram struct {
Id uint `gorm:"primaryKey"`
Name string
Parts []Part `gorm:"many2many:diagram_parts;"`
// PartNumber string `gorm:"-:migration;->"`
// PartDescription string `gorm:"-:migration;->"`
}
type DiagramPart struct {
DiagramId uint `gorm:"primaryKey"`
PartId uint `gorm:"primaryKey"`
PartDiagramNumber int
PartNumber string
PartDescription string
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&Diagram{}, &Part{}, &DiagramPart{})
diagram := &Diagram{}
err = db.Debug().Where("id = ?", 1).
// Select("diagrams.*, diagram_parts.part_number, diagram_parts.part_description").
Preload("Parts", func(db *gorm.DB) *gorm.DB {
return db.Select("parts.*, diagram_parts.part_number, diagram_parts.part_description").
Joins("left join diagram_parts on diagram_parts.part_id = parts.id")
}).
// Joins("left join diagram_parts on diagram_parts.diagram_id = diagrams.id").
First(diagram).Error
if err != nil {
panic(err)
}
fmt.Printf("diagram: %v\n", diagram)
fmt.Println("part number:", diagram.Parts[0].PartNumber)
}
I have 2 items, collections and accounts represented by 2 structs that I would like to merge into a single response.
collections, accounts, err := h.Service.Many(ctx, params)
The collection struct is defined as follows:
type Collection struct {
ID int64 `json:"id"`
Name *string `json:"name"`
Description *string `json:"description"`
Total *int64 `json:"total"`
}
And accounts is defined as a map as such accounts := make(map[int64][]string) and the data looks like this map[1:[19565 21423] 7:[]]
What I would like to do is merge these 2 something like the following:
// merge into single struct
type CollectionWithAccounts struct {
Collections []*collection.Collection
AccountIDs []string
}
// initialize struct
collectionsWithAccounts := make([]CollectionWithAccounts, 0)
// merge strucst in loop
for _, collection := range collections {
for _, account := range accounts {
collectionsWithAccounts.Collections = append(collectionsWithAccounts, collection)
collectionsWithAccounts.Accounts = append(collectionsWithAccounts, account)
}
}
How can I accomplish this merge?
You can do this even without any loops:
package main
import "fmt"
type Collection struct {
ID int64 `json:"id"`
Name *string `json:"name"`
Description *string `json:"description"`
Total *int64 `json:"total"`
}
type AccountID map[int64][]string
// merge into single struct
type CollectionWithAccounts struct {
Collections []*Collection
AccountIDs []AccountID
}
func main() {
// get the data
// []*Collections, []AccountID, err
collections, accounts, err := h.Service.Many(ctx, params)
// handle error
if err != nil {
fmt.Println(err.Error())
// more logic
}
collectionsWithAccounts := CollectionWithAccounts{
Collections: collections,
AccountIDs: accounts,
}
}
I have a table that represents my user data. There is a field that represents a phone number, and I want to store the hash of that automatically in the database at either update or insert.
so my model is :
type Users struct {
gorm.Model
ID uint `gorm:"autoIncrement;unique" json:"id"`
PhoneNumber string `json:"phone_number"`
HashID string `gorm:"primaryKey" json:"hash_id"`
Name string `gorm:"default:dear user" json:"name"`
Rank uint `json:"rank"`
Score uint `json:"score"`
Image string `json:"image"`
Email string `json:"email"`
Address string `json:"address"`
Birthday string `json:"birthday"`
Biography string `json:"biography"
}
How can I tell the GORM to fill the HashID column with the sha256 hash code of the PhoneNumber column while inserting or updating data?
You need something like this:
package main
import (
"crypto/sha256"
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Users struct {
gorm.Model
Key string `json:"phone_number"`
Hash string `gorm:"primaryKey" json:"hash_id"`
}
func (u *Users) BeforeCreate(tx *gorm.DB) (err error) {
h := sha256.Sum256([]byte(u.Key))
u.Hash = fmt.Sprintf("%x", h[:])
return nil
}
func (u *Users) BeforeSave(tx *gorm.DB) (err error) {
h := sha256.Sum256([]byte(u.Key))
u.Hash = fmt.Sprintf("%x", h[:])
return nil
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&Users{})
u := Users{Key: "123"}
db.Create(&u)
}
Check https://gorm.io/docs/index.html
I have struct called User:
type User struct {
Email string
Name string
}
and struct called UserDALModel:
type UserDALModel struct {
gorm.Model
models.User
}
gorm Model looks like this:
type Model struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
this is possible to make UserDALModel nested with gorm model and user model so the output will be:
{
ID
CreatedAt
UpdatedAt
DeletedAt
Email
Name
}
now the output is:
{
Model: {
ID
CreatedAt
UpdatedAt
DeletedAt
}
User: {
Name
Email
}
}
According to this test in gorm, I think you need to add an embedded tag to the struct.
type UserDALModel struct {
gorm.Model `gorm:"embedded"`
models.User `gorm:"embedded"`
}
You can also specify a prefix if you want with embedded_prefix.
I found the answer:
type UserModel struct {
Email string
Name string
}
type UserDALModel struct {
gorm.Model
*UserModal
}
------------------------------
user := UserModel{"name", "email#email.com"}
userDALModel := UserDALModel{}
userDal.UserModal = &user
be careful embedding two structs with the same column:
package tests
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"testing"
)
type A struct {
X string
Y string
}
type B struct {
X string
Y string
}
type AB struct {
B B `gorm:"embedded"` // Embedded struct B before struct A
A A `gorm:"embedded"`
}
var DB *gorm.DB
func connectDB() error {
var err error
spec := "slumberuser:password#tcp(localhost:3306)/slumber"
DB, err = gorm.Open("mysql", spec+"?parseTime=true&loc=UTC&charset=utf8")
DB.LogMode(true) // Print SQL statements
//defer DB.Close()
if err != nil {
return err
}
return nil
}
// cd tests
// go test -run TestGormEmbed
func TestGormEmbed(t *testing.T) {
if err := connectDB(); err != nil {
t.Errorf("error connecting to db %v", err)
}
values := []interface{}{&A{}, &B{}}
for _, value := range values {
DB.DropTable(value)
}
if err := DB.AutoMigrate(values...).Error; err != nil {
panic(fmt.Sprintf("No error should happen when create table, but got %+v", err))
}
DB.Save(&A{X: "AX1", Y: "AY1"})
DB.Save(&A{X: "AX2", Y: "AY2"})
DB.Save(&B{X: "BX1", Y: "BY1"})
DB.Save(&B{X: "BX2", Y: "BY2"})
//select * from `as` join `bs`;
// # x,y,x,y
// # AX1,AY1,BX1,BY1
// # AX2,AY2,BX1,BY1
// # AX1,AY1,BX2,BY2
// # AX2,AY2,BX2,BY2
var abs []AB
DB.Select("*").
Table("as").
Joins("join bs").
Find(&abs)
for _, ab := range abs {
fmt.Println(ab.A, ab.B)
}
// if it worked it should print
//{AX1 AY1} {BX1 BY1}
//{AX2 AY2} {BX1 BY1}
//{AX1 AY1} {BX2 BY2}
//{AX2 AY2} {BX2 BY2}
// but actually prints
//{BX1 BY1} {AX1 AY1}
//{BX1 BY1} {AX2 AY2}
//{BX2 BY2} {AX1 AY1}
//{BX2 BY2} {AX2 AY2}
}