Your Question
How to handle related models inside a plugin?
The document you expected this should be explained
https://gorm.io/docs/write_plugins.html
Expected answer
I did a custom plugin to handle timestamppb.Timestamp protobuf type:
package timestamppb
import (
"reflect"
"time"
"google.golang.org/protobuf/types/known/timestamppb"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type TimestamppbPlugin struct{}
func (p *TimestamppbPlugin) Name() string {
return "timestamppb"
}
func (p *TimestamppbPlugin) Initialize(db *gorm.DB) (err error) {
// Soft deleteAt when type is timestamppb
db.Callback().Delete().Before("gorm:before_delete").Register(p.Name(), p.BeforeDelete)
// Set createAt when type is timestamppb
db.Callback().Create().Before("gorm:before_create").Register(p.Name(), p.BeforeCreate)
// Set updateAt when type is timestamppb
db.Callback().Update().Before("gorm:update").Register(p.Name(), p.BeforeUpdate)
// Add where clause
db.Callback().Query().Before("gorm:query").Register(p.Name(), p.BeforeQuery)
return
}
func (p *TimestamppbPlugin) BeforeCreate(db *gorm.DB) {
p.updateFields("AUTOCREATETIMESTAMPPB", db)
p.updateFields("AUTOUPDATETIMESTAMPPB", db)
}
func (p *TimestamppbPlugin) BeforeUpdate(db *gorm.DB) {
p.updateFields("AUTOUPDATETIMESTAMPPB", db)
}
func (p *TimestamppbPlugin) BeforeQuery(db *gorm.DB) {
if db.Statement.Schema == nil || db.Statement.Unscoped {
return
}
if _, ok := db.Statement.Schema.FieldsByName["DeletedAt"]; !ok {
return
}
deletedAtField := db.Statement.Schema.FieldsByName["DeletedAt"]
if deletedAtField.FieldType == reflect.TypeOf(×tamppb.Timestamp{}) {
// Modify query to add deleteAt is NULL
db = db.Where(db.Statement.Table + "." + deletedAtField.DBName + " IS NULL")
}
}
func (p *TimestamppbPlugin) BeforeDelete(db *gorm.DB) {
if db.Statement.Schema == nil || db.Statement.Unscoped {
return
}
if _, ok := db.Statement.Schema.FieldsByName["DeletedAt"]; !ok {
return
}
var set clause.Set
deletedAtField := db.Statement.Schema.FieldsByName["DeletedAt"]
if deletedAtField.FieldType == reflect.TypeOf(×tamppb.Timestamp{}) {
// Modify query to update instead of delete the record
timeNow := time.Now()
now := timestamppb.New(timeNow)
set = append(clause.Set{{Column: clause.Column{Name: deletedAtField.DBName}, Value: timeNow}}, set...)
db.Statement.AddClause(set)
db.Statement.SetColumn(deletedAtField.DBName, now, true)
db.Statement.AddClauseIfNotExists(clause.Update{})
db.Statement.Build(db.Statement.DB.Callback().Update().Clauses...)
}
}
// Update field value
func (p *TimestamppbPlugin) updateFields(trigger string, db *gorm.DB) (err error) {
if db.Statement.Schema != nil {
for _, field := range db.Statement.Schema.Fields {
if db.Statement.ReflectValue.Kind() == reflect.Struct {
// Update field
if field.TagSettings[trigger] != "" && field.FieldType == reflect.TypeOf(×tamppb.Timestamp{}) {
now := timestamppb.New(time.Now())
fieldValue := db.Statement.ReflectValue.FieldByName(field.Name)
if fieldValue.CanSet() {
db.Statement.SetColumn(field.Name, now)
}
}
}
}
}
return nil
}
Now is working only for main model, is there a way to handle has many has one relations as well?
Comment From: asfwebmaster
I have manage to fix it, the problem was that the related model is slice, not a struct here is updated working version of the plugin:
``` package timestamppb
import ( "reflect" "time"
"google.golang.org/protobuf/types/known/timestamppb"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type TimestamppbPlugin struct{}
func (p *TimestamppbPlugin) Name() string { return "timestamppb" }
func (p TimestamppbPlugin) Initialize(db gorm.DB) (err error) { // Set createAt when type is timestamppb db.Callback().Create().Before("").Register(p.Name(), p.BeforeCreate) // Set updateAt when type is timestamppb db.Callback().Update().Before("").Register(p.Name(), p.BeforeUpdate) // Soft deleteAt when type is timestamppb db.Callback().Delete().Before("gorm:before_delete").Register(p.Name(), p.BeforeDelete) // Add where clause db.Callback().Query().Before("gorm:query").Register(p.Name(), p.BeforeQuery) return }
func (p TimestamppbPlugin) BeforeCreate(db gorm.DB) { p.updateFields("AUTOCREATETIMESTAMPPB", db) }
func (p TimestamppbPlugin) BeforeUpdate(db gorm.DB) { p.updateFields("AUTOUPDATETIMESTAMPPB", db) }
func (p TimestamppbPlugin) BeforeQuery(db gorm.DB) {
if db.Statement.Schema == nil || db.Statement.Unscoped {
return
}
if _, ok := db.Statement.Schema.FieldsByName["DeletedAt"]; !ok {
return
}
deletedAtField := db.Statement.Schema.FieldsByName["DeletedAt"]
if deletedAtField.FieldType == reflect.TypeOf(×tamppb.Timestamp{}) {
// Modify query to add deleteAt is NULL
db = db.Where(db.Statement.Table + "." + deletedAtField.DBName + " IS NULL")
}
}
func (p TimestamppbPlugin) BeforeDelete(db gorm.DB) {
if db.Statement.Schema == nil || db.Statement.Unscoped {
return
}
if _, ok := db.Statement.Schema.FieldsByName["DeletedAt"]; !ok {
return
}
var set clause.Set
deletedAtField := db.Statement.Schema.FieldsByName["DeletedAt"]
if deletedAtField.FieldType == reflect.TypeOf(×tamppb.Timestamp{}) {
// Modify query to update instead of delete the record
timeNow := time.Now()
now := timestamppb.New(timeNow)
set = append(clause.Set{{Column: clause.Column{Name: deletedAtField.DBName}, Value: timeNow}}, set...)
db.Statement.AddClause(set)
db.Statement.SetColumn(deletedAtField.DBName, now, true)
db.Statement.AddClauseIfNotExists(clause.Update{})
db.Statement.Build(db.Statement.DB.Callback().Update().Clauses...)
}
}
// Update field value func (p TimestamppbPlugin) updateFields(trigger string, db gorm.DB) (err error) {
if db.Statement.Schema != nil {
now := timestamppb.New(time.Now())
for _, field := range db.Statement.Schema.Fields {
switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < db.Statement.ReflectValue.Len(); i++ {
if field.TagSettings[trigger] != "" && field.FieldType == reflect.TypeOf(×tamppb.Timestamp{}) {
field.Set(db.Statement.Context, db.Statement.ReflectValue.Index(i), now)
}
}
case reflect.Struct:
if field.TagSettings[trigger] != "" && field.FieldType == reflect.TypeOf(×tamppb.Timestamp{}) {
// Set value to field
err := field.Set(db.Statement.Context, db.Statement.ReflectValue, now)
return err
}
}
}
}
return nil
}
```