Convert database fields to uppercase automatically with GORM Callbacks

Sometimes I would like to convert the fields for an entity to uppercase every time the record is saved or updated automatically, without littering the code with code to perform those uppercase conversions and risk forgetting to place them elsewhere. Here is a simple callback function for use with GORM to achieve that:


func WithToUpperCaseCallback(db *gorm.DB, table string, fields ...string) {
    cb := UpperCaseCallback(fields...)
    db.Callback().Create().Before("*").Register("create:uppercase:" + table, cb) 
    db.Callback().Update().Before("*").Register("update:uppercase:" + table, cb)
}

func UpperCaseCallback(fields ...string) func(db *gorm.DB) {
    return func(db *gorm.DB) {
        if db.Statement.Schema == nil {
            return
        }
        // Only process statements that operate on a table
        if db.Statement.Schema.Table == "" {
            return
        }

        for _, argField := range fields {
            arr := strings.SplitN(argField, ".", 2)
            tableName, desiredFieldName := arr[0], arr[1]

            if tableName != db.Statement.Schema.Table {
                continue
            }
            field := db.Statement.Schema.LookUpField(desiredFieldName)
            if field == nil {
                continue
            }
            if db.Statement.ReflectValue.Kind() != reflect.String {
                continue
            }
            if fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
                if actualStr, ok := fieldValue.(string); ok {
                    db.AddError(field.Set(db.Statement.Context, db.Statement.ReflectValue, strings.ToUpper(actualStr)))
                }
            }
        }
    }
}

You can register the call back as shown below - this will enable gorm to convert your fields to upper case (in this case first_name and last_name on the "people" table) whenever the entity is saved or updated by gorm, automatically.

type Person struct {
    ID        int64  `gorm:"`
    FirstName string `gorm:""`
    LastName  string `gorm:""`
}

func main() {
    db, err := gorm.Open(postgres.Open("..."), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    db.Callback().Create().Before("*").Register("create:uppercase", UpperCaseCallback("people.first_name", "people.last_name"))

    db.Callback().Update().Before("*").Register("update:uppercase", UpperCaseCallback("people.first_name", "people.last_name"))

    // or using utility function
    WithToUpperCaseCallback(db, "people", "first_name", "last_name")

    // the code below would result in first_name and last_name being capitalized to "JOHN" and "BANDA", respectively before being saved
    person := &Person{FirstName: "john", LastName: "banda"}
    tx = s.db.Save(person)
    if tx.Error != nil {
        panic("failed to save")
    }
}

Conclusion

As you can see, it is pretty straightforward to augment GORM with callbacks to achieve the behavior we wanted. However, use this judiciously, as it adds a layer of "magic" to your codebase - one day you may be left scratching your head as to why some fields are appearing as uppercase when you didn't input them as such.

Funny enough, I have written various similar versions of this functionality in different languages/frameworks, e.g. one for JDBI/Java is [here](https://github.com/nndi-oss/jdbi-utils/blob/main/jdbi3-utils/src/main/java/cloud/nndi/oss/jdbi/customizers/CapitalizeCustomizer.java)

I hope you found this useful.