One method to handle all the struct types that emb

2019-08-02 06:42发布

I have a gin-gonic web app with somewhat MVC architecture. I created a couple of models, all of them embed one common struct:

type User struct {
   ID int
   Name string
}

type Admin struct {
   User
   Level int
}

... {
   User
}

Now I want to store them in database in json format. The goal I want to accomplish is to code only one function/method that will marshal any model and will save save it into DB. This method must marshal all the fields of current model, not only from User struct, e.g. User must be marshaled into {id: 1, name: "zhora"}, while Admin will go into {id: 1, name: "gena", level: 2}.

Like this one:

func (i *User) Save() {
  data, err := json.Marshal(i)
  check(err)
  if i.ID == 0 {
    _, err = app.DB.Exec(`INSERT INTO users(data) VALUES ($1) `, string(data))
  } else {
    _, err = app.DB.Exec(`UPDATE users SET data = $1 WHERE id=$2`, string(data), i.ID)
  }
  check(err)
}

Right now I have to copy/paste this func to every model file, changing only method receiver. Can this be avoided?

1条回答
我欲成王,谁敢阻挡
2楼-- · 2019-08-02 07:10

You may use one func Save(d interface{}) like this:

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID   int
    Name string
}

type Admin struct {
    User
    Level int
}

func main() {
    Save(User{})
    Save(Admin{})
}

func Save(d interface{}) {
    body, err := json.Marshal(d)
    if err != nil {
        panic(err)
    }
    st := string(body)
    fmt.Println(st)
}

output:

{"ID":0,"Name":""}
{"ID":0,"Name":"","Level":0}

for your case, use this one function for all types:

func Save(i interface{}, id int) {
    data, err := json.Marshal(i)
    check(err)
    if id == 0 {
        _, err = app.DB.Exec(`INSERT INTO users(data) VALUES ($1) `, string(data))
    } else {
        _, err = app.DB.Exec(`UPDATE users SET data = $1 WHERE id=$2`, string(data), id)
    }
    check(err)
}

And call it like this:

u := User{}
a := Admin{}

Save(u, u.ID)
Save(a, a.ID)

And Yes, this is even simplifies call to Save to one parameter:

package main

import (
    "encoding/json"
    "fmt"
)

type Model interface {
    ID() int
    setID(int)
}

type User struct {
    Id   int
    Name string
}

func (t User) ID() int      { return t.Id }
func (t User) setID(id int) { t.Id = id }

type Admin struct {
    User
    Level int
}

func main() {
    Save(User{})
    Save(Admin{})
}

func Save(d Model) {
    body, err := json.Marshal(d)
    if err != nil {
        panic(err)
    }
    st := string(body)
    fmt.Println(st)

    fmt.Println("ID is ", d.ID())
}

output:

{"Id":0,"Name":""}
ID is  0
{"Id":0,"Name":"","Level":0}
ID is  0

Now You may use this one function for all types:

func Save(i Model) {
    data, err := json.Marshal(i)
    check(err)
    id := i.ID()
    if id == 0 {
        _, err = app.DB.Exec(`INSERT INTO users(data) VALUES ($1) `, string(data))
    } else {
        _, err = app.DB.Exec(`UPDATE users SET data = $1 WHERE id=$2`, string(data), id)
    }
    check(err)
}

And call it like this:

u := User{}
a := Admin{}

Save(u)
Save(a)

Effective Go:

Getters

Go doesn't provide automatic support for getters and setters. There's nothing wrong with providing getters and setters yourself, and it's often appropriate to do so, but it's neither idiomatic nor necessary to put Get into the getter's name. If you have a field called owner (lower case, unexported), the getter method should be called Owner (upper case, exported), not GetOwner. The use of upper-case names for export provides the hook to discriminate the field from the method. A setter function, if needed, will likely be called SetOwner. Both names read well in practice:

owner := obj.Owner()
if owner != user {
    obj.SetOwner(user)
}
查看更多
登录 后发表回答