Custom GORM Callbacks
GORM itself is powered by Callbacks, so you could fully customize GORM as you want
Project address:https://github.com/EDDYCJY/go …
GORM itself is callback driven, so we can completely customize GORM as needed to achieve our goal.
- Register a new callback
- Delete existing callbacks
- Replace an existing callback
- The order in which callbacks are registered
GORM includes the above four types of Callbacks, and we use “replace existing callbacks” to solve a minor pain point in combination with the project.
Problem
In the models directory, we include tag.go and article.go. they have a problem, that is, BeforeCreate and BeforeUpdate appear repeatedly. is it necessary to write 100 times for 100 files?
1、tag.go
2、article.go
Obviously, this is impossible. If you have realized this problem before, it is OK, but if not, it will be changed from now on.
Solve
Here we use Callbacks to implement the function, and do not need to write files one by one.
Implement Callbacks
Open the models. Go file in the Models directory to implement the following two methods:
1、updateTimeStampForCreateCallback
// updateTimeStampForCreateCallback will set `CreatedOn`, `ModifiedOn` when creating
func updateTimeStampForCreateCallback(scope *gorm.Scope) {
if !scope.HasError() {
nowTime := time.Now().Unix()
if createTimeField, ok := scope.FieldByName("CreatedOn"); ok {
if createTimeField.IsBlank {
createTimeField.Set(nowTime)
}
}
if modifyTimeField, ok := scope.FieldByName("ModifiedOn"); ok {
if modifyTimeField.IsBlank {
modifyTimeField.Set(nowTime)
}
}
}
}
In this method, the following functions will be completed
- Check for errors (db.Error)
-
scope.FieldByName
viascope.Fields()
Acquiring all fields and judging whether the required fields are currently contained or not
for _, field := range scope.Fields() {
if field.Name == name || field.DBName == name {
return field, true
}
if field.DBName == dbName {
mostMatchedField = field
}
}
-
field.IsBlank
It can be judged whether the value of this field is empty or not.
func isBlank(value reflect.Value) bool {
switch value.Kind() {
case reflect.String:
return value.Len() == 0
case reflect.Bool:
return !value.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return value.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return value.Uint() == 0
case reflect.Float32, reflect.Float64:
return value.Float() == 0
case reflect.Interface, reflect.Ptr:
return value.IsNil()
}
return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface())
}
- If it is empty
field.Set
Used to set a value for this field, the parameter isinterface{}
2、updateTimeStampForUpdateCallback
// updateTimeStampForUpdateCallback will set `ModifyTime` when updating
func updateTimeStampForUpdateCallback(scope *gorm.Scope) {
if _, ok := scope.Get("gorm:update_column"); !ok {
scope.SetColumn("ModifiedOn", time.Now().Unix())
}
}
-
scope.Get(...)
Parameters with literal values set are obtained according to the input parameters, for example, in this articlegorm:update_column
Which looks for the field attribute with this literal value -
scope.SetColumn(...)
Assuming no designationupdate_column
We are updating callback settings by defaultModifiedOn
The value of
Register Callbacks
I have already written the callback method in the above section. Next, I need to register it in GORM’s hook, but it has its own Create and Update callbacks, so I can call for replacement.
In the init function of models.go, add the following statement
db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback)
db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback)
Verification
After accessing the AddTag interface and checking the database successfully, it can be found thatcreated_on
Andmodified_on
All fields are the current execution time.
Access EditTag interface to discovermodified_on
When was the last update performed
Expand
We think that hard deletions are rare in actual projects, so can Callbacks be used to complete this function?
The answer is yes, we in the previousModel struct
IncreaseDeletedOn
Variable
type Model struct {
ID int `gorm:"primary_key" json:"id"`
CreatedOn int `json:"created_on"`
ModifiedOn int `json:"modified_on"`
DeletedOn int `json:"deleted_on"`
}
Implement Callbacks
Open the models.go file in the models directory to implement the following methods:
func deleteCallback(scope *gorm.Scope) {
if !scope.HasError() {
var extraOption string
if str, ok := scope.Get("gorm:delete_option"); ok {
extraOption = fmt.Sprint(str)
}
deletedOnField, hasDeletedOnField := scope.FieldByName("DeletedOn")
if !scope.Search.Unscoped && hasDeletedOnField {
scope.Raw(fmt.Sprintf(
"UPDATE %v SET %v=%v%v%v",
scope.QuotedTableName(),
scope.Quote(deletedOnField.DBName),
scope.AddToVars(time.Now().Unix()),
addExtraSpaceIfExist(scope.CombinedConditionSql()),
addExtraSpaceIfExist(extraOption),
)).Exec()
} else {
scope.Raw(fmt.Sprintf(
"DELETE FROM %v%v%v",
scope.QuotedTableName(),
addExtraSpaceIfExist(scope.CombinedConditionSql()),
addExtraSpaceIfExist(extraOption),
)).Exec()
}
}
}
func addExtraSpaceIfExist(str string) string {
if str != "" {
return " " + str
}
return ""
}
-
scope.Get("gorm:delete_option")
Check if delete_option is specified manually -
scope.FieldByName("DeletedOn")
Get the delete field we agreed upon, if it existsUPDATE
Soft delete, if it does not existDELETE
Hard delete -
scope.QuotedTableName()
Returns the referenced table name. This method GORM performs some processing on the table name according to its own logic -
scope.CombinedConditionSql()
Return to the combined conditional SQL and look at the prototype of the method
func (scope *Scope) CombinedConditionSql() string {
joinSQL := scope.joinsSQL()
whereSQL := scope.whereSQL()
if scope.Search.raw {
whereSQL = strings.TrimSuffix(strings.TrimPrefix(whereSQL, "WHERE ("), ")")
}
return joinSQL + whereSQL + scope.groupSQL() +
scope.havingSQL() + scope.orderSQL() + scope.limitAndOffsetSQL()
}
-
scope.AddToVars
This method can add values as SQL parameters and can also be used to prevent SQL injection.
func (scope *Scope) AddToVars(value interface{}) string {
_, skipBindVar := scope.InstanceGet("skip_bindvar")
if expr, ok := value.(*expr); ok {
exp := expr.expr
for _, arg := range expr.args {
if skipBindVar {
scope.AddToVars(arg)
} else {
exp = strings.Replace(exp, "?", scope.AddToVars(arg), 1)
}
}
return exp
}
scope.SQLVars = append(scope.SQLVars, value)
if skipBindVar {
return "?"
}
return scope.Dialect().BindVar(len(scope.SQLVars))
}
Register Callbacks
In the init function of models.go, add the following deleted callback
db.Callback().Delete().Replace("gorm:delete", deleteCallback)
Verification
Restart the service and access the DeleteTag interface. after success, the deleted_on field will be found to have a value.
Summary
In this chapter, we have completed Callbacks for adding, updating and querying in combination with GORM, which is often used in actual projects.
After all, for a hook, there is no need to write too much unnecessary code yourself.
(Note that after adding soft deletions, the previous code needs to be added.)deleted_on
The judgment of the)
References
This series of sample codes
This series of catalogues
- Serial One Golang Introduction and Environmental Installation
- Serialized 2 to build Blog API’s (1)
- Serial 3 to build Blog API’s (2)
- Serial 4 to build Blog API’s (3)
- Serial 5 Use JWT for Identity Verification
- Serial 6 Write a Simple File Log
- Serial Seven Golang Gracefully Restart HTTP Service
- Serial 8 Add Swagger to It
- Serial 9 Deploying Golang Applications to Docker
- Serial Ten Customized GORM Callbacks
- Serial Eleven Cron Scheduled Tasks
- Serial 12 Optimizing Configuration Structure and Realizing Picture Upload
- Serialization 13 Optimize Your Application Structure and Implement Redis Cache
- Serial 14 Realize Export and Import into Excel
- Serial 15 Generate Two-dimensional Code and Merge Posters
- Serial 16 Draw Text on Pictures
- Serial 17 Deploying Go Applications with Nginx
- Cross-compilation of safari Golang
- Please get started with Makefile