Skip to content

Instantly share code, notes, and snippets.

@nelsam
Created January 31, 2015 02:20
Show Gist options
  • Save nelsam/d34ca88b91b02fa5ea71 to your computer and use it in GitHub Desktop.
Save nelsam/d34ca88b91b02fa5ea71 to your computer and use it in GitHub Desktop.
package datastore
import (
"errors"
"reflect"
"github.com/coopernurse/gorp"
)
// DbColumnStorer is a type that can be stored as a column's value
// in the database. DbColumnStorers are usually also DbColumnReaders.
type DbColumnStorer interface {
// DbColumnValue should return the value that will represent the
// DbColumnStorer in a column in the database.
DbColumnValue() interface{}
}
// DbColumnReader is a type that can load data from a database
// column, doing all necessary type conversions to convert the
// database column's value to the DbColumnReader's type.
// DbColumnReaders are usually also DbColumnStorers.
type DbColumnReader interface {
// DbColumnTypePtr should return a pointer to a value of the type
// that the DbColumnReader's data is stored as. For example, if
// the database column representation of this DbColumnReader is an
// int, then DbColumnTypePtr() should return new(int)
DbColumnTypePtr() interface{}
// FromDbColumnValue should take the database representation of
// this DbColumnReader and convert it to the final Go value,
// returning an error if conversion fails at any point.
FromDbColumnValue(interface{}) error
}
// DbColumnConverter includes all methods from both DbColumnStorer and
// DbColumnReader, mostly for convenience when testing whether or not
// types implement these methods.
type DbColumnConverter interface {
DbColumnStorer
DbColumnReader
}
// DbColumnConverterTypeConverter is a gorp.TypeConverter that converts
// any values matching the DbColumnConverter interface.
type DbColumnConverterTypeConverter struct {
}
// ToDb will attempt to typecast the passed in value to DbColumnConverter
// and return the result of calling DbColumnValue() on that value. If the
// passed in value does not successfully typecast to DbColumnConverter, it
// will instead just return the passed in value.
func (converter DbColumnConverterTypeConverter) ToDb(value interface{}) (interface{}, error) {
refValue := reflect.ValueOf(value)
if refValue.Kind() == reflect.Ptr && refValue.IsNil() {
return nil, nil
}
if converter, ok := value.(DbColumnStorer); ok {
return converter.DbColumnValue(), nil
}
return value, nil
}
// FromDb will attempt to typecast the passed in target to DbColumnConverter and
// return a CustomScanner that uses the DbColumnConverter's DbColumnTypePtr and
// FromDbColumnValue methods to convert from the database value. If the passed
// in target does not successfully typecast to DbColumnConverter, it returns an
// empty scanner and false.
func (converter DbColumnConverterTypeConverter) FromDb(target interface{}) (gorp.CustomScanner, bool) {
scanner := gorp.CustomScanner{}
convert := true
targetIsPtr := false
targetVal := reflect.ValueOf(target)
// target will always be a pointer to the actual target, which
// itself could be a pointer.
if targetVal.Elem().Kind() == reflect.Ptr {
targetIsPtr = true
targetVal = targetVal.Elem()
if targetVal.IsNil() {
targetVal.Set(reflect.New(targetVal.Type().Elem()))
}
target = targetVal.Interface()
}
switch converter := target.(type) {
case DbColumnReader:
scanner.Target = converter
holder := converter.DbColumnTypePtr()
holderVal := reflect.ValueOf(holder)
newHolder := reflect.New(holderVal.Type())
scanner.Holder = newHolder.Interface()
scanner.Binder = func(interface{}, interface{}) error {
holderVal = newHolder.Elem()
if targetIsPtr {
// If the DB value is nil, set the value to nil and
// return; otherwise, initialize the pointer.
if holderVal.IsNil() {
if !targetVal.IsNil() {
targetVal.Set(reflect.Zero(targetVal.Type()))
}
return nil
}
} else if holderVal.IsNil() {
return errors.New("Non-pointer types cannot be nil")
}
return converter.FromDbColumnValue(holderVal.Interface())
}
default:
convert = false
}
return scanner, convert
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment