Javier Manzano
Javier Manzano

Reputation: 4831

How to store embedded struct with GORM?

How can I store an embedded struct with GORM if I have a type like this

type A struct {
    point GeoPoint
}

type GeoPoint struct {
    Lat float64
    Lon float64
}

GORM tries to add it in a new table, but I want to add it as another field.

How can this be done?

Upvotes: 14

Views: 13836

Answers (4)

Muerwre
Muerwre

Reputation: 76

For anyone, who's searching the way to put struct inside GORM model and make it marshalling and unmarshalling automatically.

This solution is based on chris's answer. And it works!

For example, I want to put array of Childrens inside Parent as marshalled JSON:

type Child struct {
    Lat float64
    Lng float64
}

type ChildArray []Children

func (sla *ChildArray) Scan(src interface{}) error {
    return json.Unmarshal(src.([]byte), &sla)
}

func (sla ChildArray) Value() (driver.Value, error) {
    val, err := json.Marshal(sla)
    return string(val), err
}

type Parent struct {
    *gorm.Model    
    Childrens ChildArray `gorm:"column:childrens;type:longtext"`
}

Upvotes: 6

fwhez
fwhez

Reputation: 581

First, using gorm you never define fields with the first letter in lower case.

If you make sure GeoPoint is linked as Aid , you can use ForeingKey tag like, MAKE SURE Id is table A 's primary key.

type A struct {
    Id int `gorm:"column:"id""`
    Point GeoPoint `gorm:"column:geo_point;ForeignKey:OrderId"`
}

type GeoPoint struct {
    Aid int
    Lat float64
    Lon float64
}

func main(){
    ...
    ...
    var as []A
    if e:=db.Model(&A{}).Find(&as).Error;e!=nil{
        handle(e)
    }
    fmt.Println(as)
}

If not linked by primary key. You can use middleware like

type A struct {
    Id int `gorm:"column:"id""`
    Point GeoPoint `gorm:"column:geo_point"`
}

func (a *A) AfterFind()error{
   return db.Model(&GeoPoint{}).First(&(a.Point)).Error
}

type GeoPoint struct {
    Aid int
    Lat float64
    Lon float64
}

func main(){
    ...
    ...
    var as []A
    if e:=db.Model(&A{}).Find(&as).Error;e!=nil{
        handle(e)
    }
    fmt.Println(as)
}

Upvotes: -1

Blunt Wang
Blunt Wang

Reputation: 59

You can try:

   type A struct {
    point GeoPoint `gorm:"embedded"`
} 

Upvotes: 5

chris
chris

Reputation: 4351

I am not familiar with Gorm, but with sqlx you will need to implement the sql.Scanner and sql.Valuer interfaces to allow the data to be converted to the postgres point type. The code below isn't checking for errors so it will need some tweaks.

https://play.golang.org/p/2-Y-wSeAWnj

package main

import (
    "database/sql/driver"
    "fmt"
    "strconv"
    "strings"
)

type GeoPoint struct {
    Lat float64
    Lon float64
}

func (gp GeoPoint) Value() (driver.Value, error) {
    return fmt.Sprintf("(%d,%d)", gp.Lat, gp.Lon), nil
}

func (gp GeoPoint) Scan(src interface{}) error {
    raw := src.(string)
    coords := raw[1 : len(raw)-1]
    vals := strings.Split(coords, ",")
    gp.Lat, _ = strconv.ParseFloat(vals[0], 64)
    gp.Lon, _ = strconv.ParseFloat(vals[1], 64)
    return nil
}

func main() {
    gp := GeoPoint{Lat: 112, Lon: 53}
    d, _ := gp.Value()
    fmt.Printf("%+v\n", d)

    gp.Scan("(53, -110)")
    fmt.Printf("%+v\n", gp)
}

Upvotes: 0

Related Questions