Your Question
Hi ! I defined a base model to reuse for all tables. The id was generated automatically using https://github.com/rs/xid
type Model struct {
ID string `gorm:"primary_key" json:"id"`
CreatedAt int64 `gorm:"autoCreateTime" json:"-"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"-"`
DeletedAt DeletedAt `sql:"index" json:"-"`
}
I declared a custom hook to generate the id like this
db.Callback().Create().Before("gorm:save_before_associations").Register("app:update_xid_when_create", generateXID)
func generateXID(db *gorm.DB) {
if db.Error == nil && db.Statement.Schema != nil {
var field = db.Statement.Schema.LookUpField("ID")
if field != nil {
if v, isZero := field.ValueOf(db.Statement.ReflectValue); isZero {
if id, ok := v.(string); ok {
if id == "" {
field.Set(db.Statement.ReflectValue, xid.New().String())
}
}
}
}
}
}
It's panic when creating with the association. In v1 I can make this is work correctly, but in v2 I don't how to do this.
Please help.
Thanks.
The document you expected this should be explained
Expected answer
Comment From: jinzhu
looks correct, can you post the panic information?
Comment From: thaitanloi365
type Lot struct {
Model
Name string `json:"name"`
Spots []*Spot `gorm:"foreignKey:LotID;references:ID" json:"spots"`
}
type Spot struct {
Model
LotID string `json:"-"`
Name string `json:"name"`
}
{"time":"2020-09-03T10:45:03.670518261Z","level":"-","prefix":"echo","file":"recover.go","line":"73","message":"[PANIC RECOVER] reflect: call of reflect.Value.Field on slice Value goroutine 40 [running]:\ngithub.com/labstack/echo/v4/middleware.RecoverWithConfig.func1.1.1(0x1c92de0, 0x1000, 0x0, 0x20e58c0, 0xc00050a400)\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.1.16/middleware/recover.go:71 +0xee\npanic(0x1804380, 0xc00078b620)\n\t/usr/local/go/src/runtime/panic.go:969 +0x166\nreflect.Value.Field(0x17257a0, 0xc00078b600, 0x97, 0x0, 0x1, 0xff00000000000000, 0xc0007688f0)\n\t/usr/local/go/src/reflect/value.go:827 +0x123\ngorm.io/gorm/schema.(*Field).setupValuerAndSetter.func2(0x17257a0, 0xc00078b600, 0x97, 0x2, 0xc0012c0088, 0x1a3c001)\n\t/go/pkg/mod/gorm.io/gorm@v1.20.0/schema/field.go:377 +0x83\ngithub.com/calibratedev/legend-lot-backend/pkg/db.generateXID(0xc0003c4150)\n\t/app/pkg/db/callback.go:12 +0xfe\ngorm.io/gorm.(*processor).Execute(0xc000759f00, 0xc0003c4150)\n\t/go/pkg/mod/gorm.io/gorm@v1.20.0/callbacks.go:101 +0x21d\ngorm.io/gorm.(*DB).Create(0xc0003c4150, 0x17257a0, 0xc00078b600, 0x1)\n\t/go/pkg/mod/gorm.io/gorm@v1.20.0/finisher_api.go:19 +0xa7\ngorm.io/gorm/callbacks.SaveAfterAssociations(0xc000094060)\n\t/go/pkg/mod/gorm.io/gorm@v1.20.0/callbacks/associations.go:235 +0x17d3\ngorm.io/gorm.(*processor).Execute(0xc000759f00, 0xc000094060)\n\t/go/pkg/mod/gorm.io/gorm@v1.20.0/callbacks.go:101 +0x21d\ngorm.io/gorm.(*DB).Create(0xc000094060, 0x16f59e0, 0xc0013b60e0, 0x1)\n\t/go/pkg/mod/gorm.io/gorm@v1.20.0/finisher_api.go:19 +0xa7\ngithub.com/calibratedev/legend-lot-backend/services/backend/controllers/admin.CreateLot(0x20e58c0, 0xc00050a400, 0x5, 0xc0000ca401)\n\t/app/services/backend/controllers/admin/lot.go:253 +0x357\ngithub.com/calibratedev/legend-lot-backend/pkg/middlewares.(*Middleware).RequireRoles.func1(0x20e58c0, 0xc00050a400, 0xa, 0x183ebc0)\n\t/app/pkg/middlewares/auth.go:103 +0x356\ngithub.com/labstack/echo/v4/middleware.JWTWithConfig.func2.1(0x20e58c0, 0xc00050a400, 0xffffffffffffffff, 0xc0001c68f0)\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.1.16/middleware/jwt.go:207 +0x3c0\ngithub.com/labstack/echo/v4/middleware.GzipWithConfig.func1.1(0x20e58c0, 0xc00050a400, 0x0, 0x0)\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.1.16/middleware/compress.go:92 +0x20c\ngithub.com/labstack/echo/v4.(*Echo).add.func1(0x20e58c0, 0xc00050a400, 0x37452a0, 0x180)\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.1.16/echo.go:512 +0x8a\ngithub.com/calibratedev/legend-lot-backend/pkg/middlewares.(*Middleware).Logger.func1.1(0x20e58c0, 0xc00050a400, 0xc000134640, 0x3fa8cd17)\n\t/app/pkg/middlewares/logger.go:37 +0xf7\ngithub.com/labstack/echo/v4/middleware.BodyLimitWithConfig.func1.1(0x20e58c0, 0xc00050a400, 0x0, 0x0)\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.1.16/middleware/body_limit.go:87 +0x17e\ngithub.com/labstack/echo/v4/middleware.CORSWithConfig.func1.1(0x20e58c0, 0xc00050a400, 0x414f00, 0x160)\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.1.16/middleware/cors.go:121 +0x477\ngithub.com/labstack/echo/v4/middleware.RecoverWithConfig.func1.1(0x20e58c0, 0xc00050a400, 0x0, 0x0)\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.1.16/middleware/recover.go:78 +0x12a\ngithub.com/labstack/echo/v4/middleware.RequestIDWithConfig.func1.1(0x20e58c0, 0xc00050a400, 0xc0013f2ac0, 0xc0013f2a20)\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.1.16/middleware/request_id.go:57 +0x1f0\ngithub.com/calibratedev/legend-lot-backend/pkg/middlewares.(*Middleware).RegisterCustomContext.func1.1(0x20e5aa0, 0xc00009e820, 0x6, 0x8)\n\t/app/pkg/middlewares/context.go:51 +0xf3\ngithub.com/labstack/echo/v4.(*Echo).ServeHTTP(0xc00000c5a0, 0x20acae0, 0xc0013b6000, 0xc0001b4800)\n\t/go/pkg/mod/github.com/labstack/echo/v4@v4.1.16/echo.go:623 +0x16c\nnet/http.serverHandler.ServeHTTP(0xc0013b62a0, 0x20acae0, 0xc0013b6000, 0xc0001b4800)\n\t/usr/local/go/src/net/http/server.go:2807 +0xa3\nnet/http.(*conn).serve(0xc00009e320, 0x20ba1a0, 0xc00050a040)\n\t/usr/local/go/src/net/http/server.go:1895 +0x86c\ncreated by net/http.(*Server).Serve\n\t/usr/local/go/src/net/http/server.go:2933 +0x35c\n\ngoroutine 1 [chan receive, 11 minutes]:\ngithub.com/calibratedev/legend-lot-backend/services/backend/routes.(*Router).gracefulShutdown(0xc0013f2d00)\n\t/app/services/backend/routes/routes.go:100 +0xf5\ngithub.com/calibratedev/legen\n"}
Comment From: jinzhu
db.Statement.ReflectValue is a slice data, refer https://github.com/go-gorm/gorm/blob/master/callbacks/callmethod.go#L9
Comment From: thaitanloi365
I tried a few hours, but still getting same error
func generateXID(db *gorm.DB) {
if db.Error == nil && db.Statement.Schema != nil {
var field = db.Statement.Schema.LookUpField("ID")
if field != nil {
if v, isZero := field.ValueOf(db.Statement.ReflectValue); isZero {
if _, ok := v.(string); ok {
var xid = xid.New().String()
switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
field.Set(db.Statement.ReflectValue.Index(db.Statement.CurDestIndex), xid)
case reflect.Struct:
field.Set(db.Statement.ReflectValue, xid)
}
}
}
}
}
}
@jinzhu can you help me look, thanks you ?
Comment From: jinzhu
You need to write like this
for i := 0; db.Statement.ReflectValue.Len(); i++ {
db.Statement.ReflectValue.Index(i)
}
Comment From: thaitanloi365
@jinzhu thanks for your response, I followed your suggestion , but don't know how to apply that. I created an example to demonstrate this question. Can you please look that ? https://github.com/thaitanloi365/gorm-v2-xid
Comment From: jinzhu
db.Callback().Create().Before("gorm:save_before_associations").Register("app:update_xid_when_create", func(db *gorm.DB) {
var field = db.Statement.Schema.LookUpField("ID")
if field != nil {
if v, isZero := field.ValueOf(db.Statement.ReflectValue); isZero {
if _, ok := v.(string); ok {
fmt.Println("****** kind", db.Statement.ReflectValue.Kind())
switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; db.Statement.ReflectValue.Len(); i++ {
var xid = xid.New().String()
field.Set(db.Statement.ReflectValue.Index(i), xid)
}
case reflect.Struct:
var xid = xid.New().String()
field.Set(db.Statement.ReflectValue, xid)
}
}
}
}
})
Comment From: thaitanloi365
the logs show ****** kind struct and sill getting panic error
could you take your time to run my sample code ?
Comment From: thaitanloi365
I fixed the issue, thank you
Comment From: snowdream
field.ValueOf(db.Statement.ReflectValue)
not enough arguments in call to field.ValueOf have (reflect.Value) want (context.Context, reflect.Value)
// Field is the representation of model schema's field
type Field struct {
Name string
DBName string
BindNames []string
DataType DataType
GORMDataType DataType
PrimaryKey bool
AutoIncrement bool
AutoIncrementIncrement int64
Creatable bool
Updatable bool
Readable bool
AutoCreateTime TimeType
AutoUpdateTime TimeType
HasDefaultValue bool
DefaultValue string
DefaultValueInterface interface{}
NotNull bool
Unique bool
Comment string
Size int
Precision int
Scale int
IgnoreMigration bool
FieldType reflect.Type
IndirectFieldType reflect.Type
StructField reflect.StructField
Tag reflect.StructTag
TagSettings map[string]string
Schema *Schema
EmbeddedSchema *Schema
OwnerSchema *Schema
ReflectValueOf func(context.Context, reflect.Value) reflect.Value
ValueOf func(context.Context, reflect.Value) (value interface{}, zero bool)
Set func(context.Context, reflect.Value, interface{}) error
Serializer SerializerInterface
NewValuePool FieldNewValuePool
}
Comment From: snowdream
field.ValueOf(db.Statement.ReflectValue)not enough arguments in call to field.ValueOf have (reflect.Value) want (context.Context, reflect.Value)
// Field is the representation of model schema's field type Field struct { Name string DBName string BindNames []string DataType DataType GORMDataType DataType PrimaryKey bool AutoIncrement bool AutoIncrementIncrement int64 Creatable bool Updatable bool Readable bool AutoCreateTime TimeType AutoUpdateTime TimeType HasDefaultValue bool DefaultValue string DefaultValueInterface interface{} NotNull bool Unique bool Comment string Size int Precision int Scale int IgnoreMigration bool FieldType reflect.Type IndirectFieldType reflect.Type StructField reflect.StructField Tag reflect.StructTag TagSettings map[string]string Schema *Schema EmbeddedSchema *Schema OwnerSchema *Schema ReflectValueOf func(context.Context, reflect.Value) reflect.Value ValueOf func(context.Context, reflect.Value) (value interface{}, zero bool) Set func(context.Context, reflect.Value, interface{}) error Serializer SerializerInterface NewValuePool FieldNewValuePool }
Fixed: Hooks/Callbacks 中的 Context 您可以从当前 Statement中访问 Context 对象,例如︰
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
ctx := tx.Statement.Context
// ...
return
}