How to use Go interface{} for generic gorm insert

74 Views Asked by At

I am trying to setup a Go base module that can allow other modules to perform CRUD data to database via GORM.

  1. The base module defines a Create method that takes a ServiceInput struct as input and performs the insertion logic using GORM.
  2. The session module (as an example of base client module) defines a Session struct to represent a session and a CreateSess method to create a new session. Inside CreateSess, it creates a new session instance, prepares a ServiceInput struct with the session data and the table name, and then calls the base.Create method to perform the insertion. The GORM database instance (db) is defined and initiated as a global variable.

I have omited some codes that defines the req CdRequest param which has not participated at the create process. It only used for post create process.

Starting with Create, on the base I have:

package base

import (
    "gorm.io/gorm"
)

var db = Conn()

// ServiceInput struct represents the input for the Create method
type ServiceInput struct {
    ServiceModel interface{}
    ModelName    string
    DocName      string
    Cmd          Cmd
    DSource      int
}

func Create(req CdRequest, servInput ServiceInput) CdResponse {
    logger.LogInfo("Starting BaseModule::CdCreate()...")
    logger.LogInfo("Base::CbCreate()/servInput.ServiceModel:" + fmt.Sprint(servInput.ServiceModel))
    result := db.Table(servInput.ModelName).Create(&servInput.ServiceModel)
    if result.Error != nil {
        respMsg = "Could not create " + servInput.ModelName
        logger.LogInfo("Base::CbCreate()/respMsg:" + respMsg)
        logger.LogError("Base::CbCreate():" + fmt.Sprint(result.Error))
        var appState = CdAppState{false, respMsg, nil, "", ""}
        var appData = RespData{Data: nil, RowsAffected: 0, NumberOfResult: 0}
        resp := CdResponse{AppState: appState, Data: appData}
        return resp
    }
    //...weeded off some codes below this for the sake of clariry
    resp := CdResponse{}
    var appState = CdAppState{true, respMsg, jsonResult, "", ""}
    var appData = RespData{Data: jsonResult, RowsAffected: 0, NumberOfResult: 1}
    resp = CdResponse{AppState: appState, Data: appData}
    return resp
}

As an example, I have started with session module which is set up as below:

package session

import (
    "gorm.io/gorm"
    "time"
    "base_module_path/base"
)

// Session model
type Session struct {
    SessionId     uint `gorm:"primaryKey"`
    CurrentUserId uint
    CdToken       string
    Active        bool
    Ttl           uint
    AccTime       time.Time
    StartTime     time.Time
    DeviceNetId   datatypes.JSON
    ConsumerGuid  string
}

func CreateSess(req base.CdRequest) base.CdResponse {
    logger.LogInfo("Starting UserModule::Session::SessNew()...")

    // Create a new session instance
    cdToken := uuid.New()
    sess := Session{
        CdToken:       cdToken.String(),
        StartTime:     time.Now(),
        AccTime:       time.Now(),
        Active:        true,
        ConsumerGuid:  "",
        CurrentUserId: 1000,
        DeviceNetId:   nil,
    }
    servInput := base.ServiceInput{}
    servInput.ModelName = "session"
    servInput.ServiceModel = sess
    resp := base.Create(req, servInput)
    return resp
}

Below is the reported error:

2024/03/25 08:45:03 /media/emp-06/disk-02/projects/test-projects/cd-core/sys/base/b.go:226 unsupported data
[0.553ms] [rows:0] INSERT INTO `session` (`current_user_id`,`cd_token`,`active`,`ttl`,`acc_time`,`start_time`,`device_net_id`,`consumer_guid`) VALUES 
[INFO] Base::Create()/respMsg:Could not create session
[ERROR] Base::Create():unsupported data

1

There are 1 best solutions below

8
Shubham Dixit On

For this you have to do the type assertion with switch case something like below This is the example I tried in local ,hopefully it works

This solution works but you have to keep your table name columns same as your struct field names

package main

import (
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
    "log"
    "reflect"
    "strings"
)

// ServiceInput struct represents the input for the Create method
type ServiceInput struct {
    ServiceModel interface{}
    ModelName    string
    DocName      string
    DSource      int
}

type Session struct {
    SessionId     uint   `gorm:"session_id,primaryKey"`
    CurrentUserId uint   `gorm:"current_user_id"`
    CdToken       string `gorm:"cd_token"`
    Active        bool   `gorm:"active"`
    Ttl           uint   `gorm:"ttl"`
    ConsumerGuid  string `gorm:"consumer_guid"`
}

func getStructTag(f reflect.StructField, tagName string) string {
    return strings.Split(f.Tag.Get(tagName), ",")[0]
}

func Create(servInput ServiceInput, db *gorm.DB) {
    structValue := reflect.ValueOf(servInput.ServiceModel)
    record := make(map[string]interface{})
    for i := 0; i < structValue.NumField(); i++ {
        field := structValue.Type().Field(i)
        fieldValue := structValue.Field(i)
        record[getStructTag(field, "gorm")] = fieldValue.Interface()
    }

    result := db.Table(servInput.ModelName).Create(record)
    if result.Error != nil {
        log.Fatal(result.Error)
    }

}

func main() {
    db, err := gorm.Open(sqlite.Open("mydatabase.db"), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }
    sv := ServiceInput{
        ServiceModel: Session{
            SessionId:     1,
            CurrentUserId: 2,
            CdToken:       "hello",
            Active:        true,
        },
        ModelName: "sessions",
    }

    Create(sv, db)

}