Gorm: DeletedAt with alias type of time.Time

Created on 21 Oct 2016  ·  8Comments  ·  Source: go-gorm/gorm

Considering I define a JSONTime type to control the output format of time

type JSONTime time.Time

func (t JSONTime) MarshalJSON() ([]byte, error) {
    stamp := fmt.Sprintf(`"%s"`, time.Time(t).Format("2006-01-02 15:04:05"))
    return []byte(stamp), nil
}

In the model definition, I cannot set the DeletedAt field type to JSONTime, or soft delete won't work. Since this field won't show to user, the code below is still reasonable. But if you can solve it, it makes OCDers feel better :)

type Role struct {
    Id        uint `gorm:"primary_key"`
    CreatedAt JSONTime
    UpdatedAt JSONTime
    DeletedAt *time.Time `sql:"index"` //cannot use JSONTime, or soft delete won't work.
    Name        string
}

Most helpful comment

@BertrandGouny @mehdy use callbacks

add callbacks

// ... init gorm
DB, err = gorm.Open("mysql", "DSN")
// ... handle error

// add callbacks refer: http://gorm.io/docs/write_plugins.html
DB.Callback().Create().After("gorm:update_time_stamp").Register("mark_timestamp_normal", markTimestampNormal)
DB.Callback().Update().After("gorm:update_time_stamp").Register("mark_timestamp_normal", markTimestampNormal)
// ....
func markTimestampNormal(scope *gorm.Scope) {
    if createdAtField, ok := scope.FieldByName("CreatedAt"); ok {
        createdAtField.IsNormal = true
    }

    if updatedAtField, ok := scope.FieldByName("UpdatedAt"); ok {
        updatedAtField.IsNormal = true
    }
}

alias

// alias
type Timestamp time.Time

// UnmarshalParam echo api @see https://echo.labstack.com/guide/request
func (t *Timestamp) UnmarshalParam(src string) error {
    if src != "" {
        m, err := strconv.ParseInt(src, 10, 64)
        if err != nil {
            return err
        }

        ts := time.Unix(0, m*int64(time.Millisecond))
        *t = Timestamp(ts)
    }
    return nil
}

// MarshalJSON echo api json response
func (t *Timestamp) MarshalJSON() ([]byte, error) {
    if t != nil {
        ts := time.Time(*t)
        return []byte(fmt.Sprintf(`"%d"`, ts.UnixNano()/int64(time.Millisecond))), nil
    }
    return nil, nil
}

// for sql log, print readable format
func (t Timestamp) String() string {
    ts := time.Time(t)
    return ts.Format(time.RFC3339)
}

// insert into database conversion
func (t Timestamp) Value() (driver.Value, error) {
    return time.Time(t), nil
}

// read from database conversion
func (t *Timestamp) Scan(src interface{}) error {
    if val, ok := src.(time.Time); ok {
        *t = Timestamp(val)
    }
    return nil
}

define model, should work

type App struct {
    Id        uint32     `json:"id,string"`
    AppKey    string     `json:"appKey" validate:"required"`
    AppSecret string     `json:"appSecret" validate:"required"`
    AppName   string     `json:"appName" validate:"required"`
    CreatedAt Timestamp  `json:"createdAt"`
    UpdatedAt *Timestamp `json:"updatedAt"`
}

All 8 comments

Further, with JSONTime, the CreatedAt and UpdatedAt won't save to db, but reading them is ok.

@qjebbs did you solve this? I have the same issue

@mehdy @qjebbs same here :S any workaround or something ?

@BertrandGouny @mehdy use callbacks

add callbacks

// ... init gorm
DB, err = gorm.Open("mysql", "DSN")
// ... handle error

// add callbacks refer: http://gorm.io/docs/write_plugins.html
DB.Callback().Create().After("gorm:update_time_stamp").Register("mark_timestamp_normal", markTimestampNormal)
DB.Callback().Update().After("gorm:update_time_stamp").Register("mark_timestamp_normal", markTimestampNormal)
// ....
func markTimestampNormal(scope *gorm.Scope) {
    if createdAtField, ok := scope.FieldByName("CreatedAt"); ok {
        createdAtField.IsNormal = true
    }

    if updatedAtField, ok := scope.FieldByName("UpdatedAt"); ok {
        updatedAtField.IsNormal = true
    }
}

alias

// alias
type Timestamp time.Time

// UnmarshalParam echo api @see https://echo.labstack.com/guide/request
func (t *Timestamp) UnmarshalParam(src string) error {
    if src != "" {
        m, err := strconv.ParseInt(src, 10, 64)
        if err != nil {
            return err
        }

        ts := time.Unix(0, m*int64(time.Millisecond))
        *t = Timestamp(ts)
    }
    return nil
}

// MarshalJSON echo api json response
func (t *Timestamp) MarshalJSON() ([]byte, error) {
    if t != nil {
        ts := time.Time(*t)
        return []byte(fmt.Sprintf(`"%d"`, ts.UnixNano()/int64(time.Millisecond))), nil
    }
    return nil, nil
}

// for sql log, print readable format
func (t Timestamp) String() string {
    ts := time.Time(t)
    return ts.Format(time.RFC3339)
}

// insert into database conversion
func (t Timestamp) Value() (driver.Value, error) {
    return time.Time(t), nil
}

// read from database conversion
func (t *Timestamp) Scan(src interface{}) error {
    if val, ok := src.(time.Time); ok {
        *t = Timestamp(val)
    }
    return nil
}

define model, should work

type App struct {
    Id        uint32     `json:"id,string"`
    AppKey    string     `json:"appKey" validate:"required"`
    AppSecret string     `json:"appSecret" validate:"required"`
    AppName   string     `json:"appName" validate:"required"`
    CreatedAt Timestamp  `json:"createdAt"`
    UpdatedAt *Timestamp `json:"updatedAt"`
}

@yinheli thanks!

@yinheli

你好,根据你的方法,在我的 mysql 中,创建出来的字段为 bigint 类型。但现在我需要为 time.Time 类型取别名,同时保证字段在数据库中为 datetime 类型。我可以这样做吗?我应该怎么做?

@yinheli

你好,根据你的方法,在我的 mysql 中,创建出来的字段为 bigint 类型。但现在我需要为 time.Time 类型取别名,同时保证字段在数据库中为 datetime 类型。我可以这样做吗?我应该怎么做?

一样的思路, 关键是实现 func Value() (driver.Value, error) func Scan(src interface{}) error 方法

@yinheli 您好,我也是用你这个办法,但是创建出来的数据库类型和 @youguanxinqing 一样,是bigint类型,请问是哪里搞错了吗?除非手动加上gorm:"type:datetime"

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kumarsiva07 picture kumarsiva07  ·  3Comments

bramp picture bramp  ·  3Comments

koalacxr picture koalacxr  ·  3Comments

easonlin404 picture easonlin404  ·  3Comments

Quentin-M picture Quentin-M  ·  3Comments