How can I make this object mapping more dry and reusable in Go? -


i have created object mapping in go not relational, simple.

i have several structs looks this:

type message struct {     id       int64     message  string     replyto  sql.nullint64 `db:"reply_to"`     fromid   int64         `db:"from_id"`     toid     int64         `db:"to_id"`     isactive bool          `db:"is_active"`     senttime int64         `db:"sent_time"`     isviewed bool          `db:"is_viewed"`      method   string `db:"-"`     appendto int64  `db:"-"` } 

to create new message run function:

func new() *message {     return &message{         isactive: true,         senttime: time.now().unix(),         method:   "new",     } } 

and have message_crud.go file struct looks this:

to find message unique column (for example id) run function:

func byunique(column string, value interface{}) (*message, error) {      query := fmt.sprintf(`         select *         message         %s = ?         limit 1;     `, column)      message := &message{}     err := sql.db.queryrowx(query, value).structscan(message)     if err != nil {         return nil, err     }     return message, nil } 

and save message (insert or update in database) run method:

func (this *message) save() error {     s := ""     if this.id == 0 {         s = "insert message set %s;"     } else {         s = "update message set %s id=:id;"     }     query := fmt.sprintf(s, sql.placeholderpairs(this))      nstmt, err := sql.db.preparenamed(query)     if err != nil {         return err     }      res, err := nstmt.exec(*this)     if err != nil {         return err     }      if this.id == 0 {         lastid, err := res.lastinsertid()         if err != nil {             return err         }         this.id = lastid     }      return nil } 

the sql.placeholderpairs() function looks this:

func placeholderpairs(i interface{}) string {      s := ""     val := reflect.valueof(i).elem()     count := val.numfield()      := 0; < count; i++ {         typefield := val.type().field(i)         tag := typefield.tag          fname := strings.tolower(typefield.name)          if fname == "id" {             continue         }          if t := tag.get("db"); t == "-" {             continue         } else if t != "" {             s += t + "=:" + t         } else {             s += fname + "=:" + fname         }         s += ", "     }     s = s[:len(s)-2]     return s } 

but every time create new struct, example user struct have copy paste "crud section" above , create user_crud.go file , replace words "message" "user", , words "message" "user". repeat alot of code , not dry. there not repeat code things reuse? have save() method, , have function byunique() can return struct , search unique column.

in php easy because php not statically typed.

is possible in go?

your byunique generic already. pull out piece varies (the table , destination):

func byunique(table string, column string, value interface{}, dest interface{}) error {     query := fmt.sprintf(`             select *             %s             %s = ?             limit 1;         `, table, column)      return sql.db.queryrowx(query, value).structscan(dest) }  func byuniquemessage(column string, value interface{}) (*message, error) {     message := &message{}     if err := byunique("message", column, value, &message); err != nil {         return nil, err     }     return message, error } 

your save similar. need make generic save function along lines of:

func save(table string, identifier int64, source interface{}) { ... } 

then inside of (*message)save, you'd call general save() function. looks pretty straightforward.

side notes: not use this name of object inside method. see link @oneofone more on that. , not obsessed dry. not goal in itself. go focuses on code being simple, clear, , reliable. not create complicated , fragile avoid typing simple line of error handling. doesn't mean shouldn't extract duplicated code. means in go better repeat simple code little bit rather create complicated code avoid it.


edit: if want implement save using interface, that's no problem. create identifier interface.

type ider interface {     id() int64     setid(newid int64) }  func (msg *message) id() int64 {     return msg.id }  func (msg *message) setid(newid int64) {     msg.id = newid }  func save(table string, source ider) error {     s := ""     if source.id() == 0 {         s = fmt.sprintf("insert %s set %%s;", table)     } else {         s = fmt.sprintf("update %s set %%s id=:id;", table)     }     query := fmt.sprintf(s, sql.placeholderpairs(source))      nstmt, err := sql.db.preparenamed(query)     if err != nil {         return err     }      res, err := nstmt.exec(source)     if err != nil {         return err     }      if source.id() == 0 {         lastid, err := res.lastinsertid()         if err != nil {             return err         }         source.setid(lastid)     }      return nil }  func (msg *message) save() error {     return save("message", msg) } 

the 1 piece might blow call exec. don't know package you're using, , it's possible exec won't work correctly if pass interface rather actual struct, work. said, i'd pass identifier rather adding overhead.


Comments

Popular posts from this blog

javascript - jQuery: Add class depending on URL in the best way -

caching - How to check if a url path exists in the service worker cache -

Redirect to a HTTPS version using .htaccess -