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
}