I am working with GORM V1. I have a requirement where we would want to add an archived_at
column similar to GORMs deleted_at
. The entities could be archived and unarchived and by default we would want to query records with archived_at
is NULL.
My current thought is to leverage GORM callbacks, to register a callback
This sounds good until here but how do I efficiently replicate the Unscoped() equivalent of archived.
archived_at
column should be added inside the necessary callback?archived_at
IS NOT NULL).EDIT - my use case is to use both deleted_at
and archived_at
fields and not use one in place of another. I want to retain the ability of soft deleting along with adding the ability to archive an entity. A user could be just archived, and then may be deleted (soft delete).
Inviting feedback. This is what I have currently come up with.
callback.go
package db
import (
"fmt"
"reflect"
"gorm.io/gorm"
)
const (
GormSettingKeyUnscopeArchive = "unscope_archive"
StructFieldNameArchivedAt = "ArchivedAt"
)
// ArchivedQueryCallback - conditionally adds "WHERE archived_at IS NULL" if the Model being queried has the following
// 1. Struct field represented by StructFieldNameArchivedAt
// 2. GORM instance setting GormSettingKeyUnscopeArchive, See UnscopeArchive
func ArchivedQueryCallback(db *gorm.DB) {
// Check if Model is a pointer and has an indirect struct type
if db.Statement.Model != nil &&
reflect.TypeOf(db.Statement.Model).Kind() == reflect.Ptr &&
reflect.Indirect(reflect.ValueOf(db.Statement.Model)).Kind() == reflect.Struct {
stmt := &gorm.Statement{DB: db}
parseErr := stmt.Parse(db.Statement.Model)
if parseErr != nil {
panic(parseErr)
}
if _, archivedAtExists := stmt.Schema.FieldsByName[StructFieldNameArchivedAt]; archivedAtExists {
v, ok := db.InstanceGet(GormSettingKeyUnscopeArchive)
if ok {
if v == true {
return
}
}
db.Where(fmt.Sprintf("%s IS NULL", stmt.Schema.FieldsByName[StructFieldNameArchivedAt].DBName))
}
}
}
scopes.go
// UnscopeArchive - sets a true value for the key GormSettingKeyUnscopeArchive
func UnscopeArchive(db *gorm.DB) *gorm.DB {
db = db.InstanceSet(GormSettingKeyUnscopeArchive, true)
return db
}
main.go
type User {
ID string `gorm:"primary_key" json:"id" valid:"uuidv4, optional"`
CreatedAt time.Time `valid:"-" json:"created_at"`
UpdatedAt time.Time `valid:"-" json:"-"`
DeletedAt gorm.DeletedAt `sql:"index" valid:"-" json:"-"`
ArchivedAt time.Time
}
var user []User
ctx := context.Background()
dbClient := InitializeGORM() //helper
_ := dbClient.WithContext(ctx).Find(&user).Error // SELECT * FROM users WHERE deleted_at IS NULL AND archived_at IS NULL;
_ := dbClient.WithContext(ctx).Scopes(UnscopeArchive).Find(&user).Error // SELECT * FROM users WHERE deleted_at IS NULL;