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
Post a Comment