Scan into struct that has a map of structs

Viewed 319

I have a struct with a map of objects. I wish to store elements of that map in a separate table. I was able to get it working for one struct, FooEntry, using the AfterFind() and BeforeSave() hooks; the former is called after the array of links is loaded. It works great when loading a specific row of FooEntryDb.

However, the very same code does not work for a struct, FooParent that has an array of FooEntryDb. The problem is that when a row of FooParent is read in, the AfterFind() hook is called before the FooEntryDb's links are added.

My query and code are as follows

Query

    var pdb api.FooParentDb
    result := db.
        Joins("FooEntriesDb").
        Preload("FooEntriesDb.FooEntryLinks").
        Find(&pdb, 104185244543150166)

Code


type FooParent struct {
    FooParentPK uint64      `gorm:"primary_key;autoIncrement:false;column:foo_parent_pk;type:INT8;"`
    Entries     []*FooEntry `gorm:"-"`
}

type FooParentDb struct {
    FooParent
    FooEntriesDb []*FooEntryDb `gorm:"foreignKey:FooParentPK;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

// TableName overrides the table name used by FooParentDb to `foo_parents`
func (FooParentDb) TableName() string {
    return "foo_parents"
}

func (tdb *FooParentDb) AfterFind(tx *gorm.DB) (err error) {
    if tx.Error == nil {
        for _, edb := range tdb.FooEntriesDb {
            edb.FooParentPK = tdb.FooParentPK
            tdb.Entries = append(tdb.Entries, &edb.FooEntry)
        }
    }
    return
}

func (tdb *FooParentDb) BeforeSave(tx *gorm.DB) (err error) {
    for _, e := range tdb.Entries {
        tdb.FooEntriesDb = append(tdb.FooEntriesDb, &FooEntryDb{FooEntry: *e, FooParentPK: tdb.FooParentPK})
    }
    return
}

// FooEntry holds one row from the foo_entries table
type FooEntry struct {
    FooEntryPK uint64           `gorm:"primary_key;autoIncrement:false;column:foo_entry_pk;type:INT8;"`
    Links      map[string]int64 `gorm:"-"`
}

// FooEntryDb holds one row from the foo_entries table
type FooEntryDb struct {
    FooEntry
    FooParentPK uint64    `gorm:"association_foreignkey:FooParentPK;column:foo_parent_pk;type:INT8;"`
    FooLinks    []FooLink `gorm:"foreignKey:EntryPK;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

// TableName overrides the table name used by FooEntryDb to `foo_entries`
func (FooEntryDb) TableName() string {
    return "foo_entries"
}

func (edb *FooEntryDb) AfterFind(tx *gorm.DB) (err error) {
    if tx.Error == nil {
        edb.Links = make(map[string]int64)
        for _, l := range edb.FooLinks {
            if l.Key == "" {
                return fmt.Errorf("empty key for entry links")
            }
            edb.Links[l.Key] = l.Link
        }
    }
    return
}

func (edb *FooEntryDb) BeforeSave(tx *gorm.DB) (err error) {
    var sorted []string
    for key := range edb.Links {
        sorted = append(sorted, key)
    }
    sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })
    edb.FooLinks = nil
    for _, key := range sorted {
        edb.FooLinks = append(edb.FooLinks, FooLink{Key: key, Link: edb.Links[key]})
    }
    return
}

// FooLink holds one row from the foo_entry_links table
type FooLink struct {
    FooEntryPK uint64 `gorm:"primary_key;autoIncrement:false;association_foreignkey:FooEntryPK;column:foo_entry_pk;type:INT8;"`
    Key        string `gorm:"primary_key;column:foo_key;type:VARCHAR;size:64;"`
    Link       int64  `gorm:"column:foo_link;type:INT4;"`
}

// TableName overrides the table name used by FooLink to `foo_entry_links`
func (FooLink) TableName() string {
    return "foo_entry_links"
}
0 Answers
Related