Gorm: How to store a struct in a field

Issue

I am trying to save an hederea contract ID of type *hedera.ContractID into a Gorm field but i get the error "invalid field found for struct github.com/hashgraph/hedera-sdk-go/v2.AccountID’s field AliasKey: define a valid foreign key for relations or implement the Valuer interface"

package contract

import (
    "fmt"

    "github.com/.../scanner/controllers/blockchain"
    database "github.com/.../scanner/db"
    model "github.com/.../scanner/models"
    "github.com/rs/xid"
    "gorm.io/gorm"
)

func DeployContract() *gorm.DB {

    //connect to database
    db, err := database.ConnectToDB()

    //if db connection fails
    if err != nil {
        panic(err)
    }

    //init model
    var modelContract model.Contract

    //check if a contract has been deployed
    if err := db.First(&modelContract); err.Error != nil {
        //no deployment found

        //Migrate the schema
        db.AutoMigrate(&model.Contract{})

        //deploy contract
        contract, _ := blockchain.DeployContract()

        //create record

        // generate random id
        id := xid.New()

        // Create
        db.Create(&model.Contract{
            Id:            id.String(),
            ContractId:    contract.Receipt.ContractID,
            GasUsed:       contract.CallResult.GasUsed,
            TransactionId: fmt.Sprint(contract.TransactionID),
            Timestamp:     contract.ConsensusTimestamp,
            ChargeFee:     fmt.Sprint(contract.TransactionFee),
            PayerAccount:  fmt.Sprint(contract.TransactionID.AccountID),
            Status:        fmt.Sprint(contract.Receipt.Status),
        })

    }

    return db
}

Gorm Model

package models

import (
    "time"

    "github.com/hashgraph/hedera-sdk-go/v2"
    "gorm.io/gorm"
)

type Contract struct {
    gorm.Model
    Id            string
    ContractId    *hedera.ContractID
    GasUsed       uint64
    TransactionId string
    Timestamp     time.Time
    ChargeFee     string
    PayerAccount  string
    Status        string
}

Solution

For custom data types, you need to specify how the value will be stored and retrieved from your database. This is done by implementing the Scanner and Valuer interfaces.

However, since hedera.ContractID is defined in another package, you will need to create your own ContractID and implement these interfaces. Something like this:

type ContractID hedera.ContractID

type Contract struct {
    gorm.Model
    Id            string
    ContractId    *ContractID
    GasUsed       uint64
    TransactionId string
    Timestamp     time.Time
    ChargeFee     string
    PayerAccount  string
    Status        string
}     

func (c *ContractID) Scan(value interface{}) error {
  bytes, ok := value.([]byte)
  if !ok {
    return errors.New(fmt.Sprint("Failed to unmarshal ContractID value:", value))
  }

  return json.Unmarshal(bytes, c)
}

func (c ContractID) Value() (driver.Value, error) {
  return json.Marshal(c)
}

Additionally, cast hedera.ContractID into model.ContractID wherever it is used. For example:

    cID := model.ContractID(*contract.Receipt.ContractID)

    // Create
    db.Create(&model.Contract{
        Id:            id.String(),
        ContractId:    &cID,
        GasUsed:       contract.CallResult.GasUsed,
        TransactionId: fmt.Sprint(contract.TransactionID),
        Timestamp:     contract.ConsensusTimestamp,
        ChargeFee:     fmt.Sprint(contract.TransactionFee),
        PayerAccount:  fmt.Sprint(contract.TransactionID.AccountID),
        Status:        fmt.Sprint(contract.Receipt.Status),
    })

Answered By – Emin Laletovic

Answer Checked By – Jay B. (GoLangFix Admin)

Leave a Reply

Your email address will not be published.