resource/meta.go

Summary

Maintainability
F
4 days
Test Coverage
package resource

import (
    "database/sql"
    "errors"
    "fmt"
    "log"
    "reflect"
    "runtime/debug"
    "strconv"
    "strings"
    "time"

    "github.com/jinzhu/gorm"
    "github.com/qor/qor"
    "github.com/qor/qor/utils"
    "github.com/qor/roles"
    "github.com/qor/validations"
)

// CompositePrimaryKeySeparator to separate composite primary keys like ID and version_name
const CompositePrimaryKeySeparator = "^|^"

// CompositePrimaryKey the string that represents the composite primary key
const CompositePrimaryKeyFieldName = "CompositePrimaryKeyField"

// CompositePrimaryKeyField to embed into the struct that requires composite primary key in select many
type CompositePrimaryKeyField struct {
    CompositePrimaryKey string `gorm:"-"`
}

// CompositePrimaryKeyStruct container for store id & version combination temporarily
type CompositePrimaryKeyStruct struct {
    ID          uint   `json:"id"`
    VersionName string `json:"version_name"`
}

// GenCompositePrimaryKey generates composite primary key in a specific format
func GenCompositePrimaryKey(id interface{}, versionName string) string {
    return fmt.Sprintf("%d%s%s", id, CompositePrimaryKeySeparator, versionName)
}

// Metaor interface
type Metaor interface {
    GetName() string
    GetFieldName() string
    GetSetter() func(resource interface{}, metaValue *MetaValue, context *qor.Context)
    GetFormattedValuer() func(interface{}, *qor.Context) interface{}
    GetValuer() func(interface{}, *qor.Context) interface{}
    GetResource() Resourcer
    GetMetas() []Metaor
    SetPermission(*roles.Permission)
    HasPermission(roles.PermissionMode, *qor.Context) bool
}

// ConfigureMetaBeforeInitializeInterface if a struct's field's type implemented this interface, it will be called when initializing a meta
type ConfigureMetaBeforeInitializeInterface interface {
    ConfigureQorMetaBeforeInitialize(Metaor)
}

// ConfigureMetaInterface if a struct's field's type implemented this interface, it will be called after configed
type ConfigureMetaInterface interface {
    ConfigureQorMeta(Metaor)
}

// MetaConfigInterface meta configuration interface
type MetaConfigInterface interface {
    ConfigureMetaInterface
}

// MetaConfig base meta config struct
type MetaConfig struct {
}

// ConfigureQorMeta implement the MetaConfigInterface
func (MetaConfig) ConfigureQorMeta(Metaor) {
}

// Meta meta struct definition
type Meta struct {
    Name            string
    FieldName       string
    FieldStruct     *gorm.StructField
    Setter          func(resource interface{}, metaValue *MetaValue, context *qor.Context)
    Valuer          func(interface{}, *qor.Context) interface{}
    FormattedValuer func(interface{}, *qor.Context) interface{}
    Config          MetaConfigInterface
    BaseResource    Resourcer
    Resource        Resourcer
    Permission      *roles.Permission
}

// GetBaseResource get base resource from meta
func (meta Meta) GetBaseResource() Resourcer {
    return meta.BaseResource
}

// GetName get meta's name
func (meta Meta) GetName() string {
    return meta.Name
}

// GetFieldName get meta's field name
func (meta Meta) GetFieldName() string {
    return meta.FieldName
}

// SetFieldName set meta's field name
func (meta *Meta) SetFieldName(name string) {
    meta.FieldName = name
}

// GetSetter get setter from meta
func (meta Meta) GetSetter() func(resource interface{}, metaValue *MetaValue, context *qor.Context) {
    return meta.Setter
}

// SetSetter set setter to meta
func (meta *Meta) SetSetter(fc func(resource interface{}, metaValue *MetaValue, context *qor.Context)) {
    meta.Setter = fc
}

// GetValuer get valuer from meta
func (meta Meta) GetValuer() func(interface{}, *qor.Context) interface{} {
    return meta.Valuer
}

// SetValuer set valuer for meta
func (meta *Meta) SetValuer(fc func(interface{}, *qor.Context) interface{}) {
    meta.Valuer = fc
}

// GetFormattedValuer get formatted valuer from meta
func (meta *Meta) GetFormattedValuer() func(interface{}, *qor.Context) interface{} {
    if meta.FormattedValuer != nil {
        return meta.FormattedValuer
    }
    return meta.Valuer
}

// SetFormattedValuer set formatted valuer for meta
func (meta *Meta) SetFormattedValuer(fc func(interface{}, *qor.Context) interface{}) {
    meta.FormattedValuer = fc
}

// HasPermission check has permission or not
func (meta Meta) HasPermission(mode roles.PermissionMode, context *qor.Context) bool {
    if meta.Permission == nil {
        return true
    }
    var roles = []interface{}{}
    for _, role := range context.Roles {
        roles = append(roles, role)
    }
    return meta.Permission.HasPermission(mode, roles...)
}

// SetPermission set permission for meta
func (meta *Meta) SetPermission(permission *roles.Permission) {
    meta.Permission = permission
}

// PreInitialize when will be run before initialize, used to fill some basic necessary information
func (meta *Meta) PreInitialize() error {
    if meta.Name == "" {
        utils.ExitWithMsg("Meta should have name: %v", reflect.TypeOf(meta))
    } else if meta.FieldName == "" {
        meta.FieldName = meta.Name
    }

    // parseNestedField used to handle case like Profile.Name
    var parseNestedField = func(value reflect.Value, name string) (reflect.Value, string) {
        fields := strings.Split(name, ".")
        value = reflect.Indirect(value)
        for _, field := range fields[:len(fields)-1] {
            value = value.FieldByName(field)
        }

        return value, fields[len(fields)-1]
    }

    var getField = func(fields []*gorm.StructField, name string) *gorm.StructField {
        for _, field := range fields {
            if field.Name == name || field.DBName == name {
                return field
            }
        }
        return nil
    }

    var nestedField = strings.Contains(meta.FieldName, ".")
    var scope = &gorm.Scope{Value: meta.BaseResource.GetResource().Value}
    if nestedField {
        subModel, name := parseNestedField(reflect.ValueOf(meta.BaseResource.GetResource().Value), meta.FieldName)
        meta.FieldStruct = getField(scope.New(subModel.Interface()).GetStructFields(), name)
    } else {
        meta.FieldStruct = getField(scope.GetStructFields(), meta.FieldName)
    }
    return nil
}

// Initialize initialize meta, will set valuer, setter if haven't configure it
func (meta *Meta) Initialize() error {
    // Set Valuer for Meta
    if meta.Valuer == nil {
        setupValuer(meta, meta.FieldName, meta.GetBaseResource().NewStruct())
    }

    if meta.Valuer == nil {
        utils.ExitWithMsg("Meta %v is not supported for resource %v, no `Valuer` configured for it", meta.FieldName, reflect.TypeOf(meta.BaseResource.GetResource().Value))
    }

    // Set Setter for Meta
    if meta.Setter == nil {
        setupSetter(meta, meta.FieldName, meta.GetBaseResource().NewStruct())
    }
    return nil
}

// setCompositePrimaryKey if the association has CompositePrimaryKey integrated, generates value for it by our conventional format
// the PrimaryKeyOf function will return the value from CompositePrimaryKey instead of ID, so that frontend could find correct version
func setCompositePrimaryKey(f *gorm.Field) {
    for i := 0; i < f.Field.Len(); i++ {
        associatedRecord := reflect.Indirect(f.Field.Index(i))
        if v := associatedRecord.FieldByName(CompositePrimaryKeyFieldName); v.IsValid() {
            id := associatedRecord.FieldByName("ID").Uint()
            versionName := associatedRecord.FieldByName("VersionName").String()
            associatedRecord.FieldByName("CompositePrimaryKey").SetString(fmt.Sprintf("%d%s%s", id, CompositePrimaryKeySeparator, versionName))
        }
    }
}

func setupValuer(meta *Meta, fieldName string, record interface{}) {
    nestedField := strings.Contains(fieldName, ".")

    // Setup nested fields
    if nestedField {
        fieldNames := strings.Split(fieldName, ".")
        setupValuer(meta, strings.Join(fieldNames[1:], "."), getNestedModel(record, strings.Join(fieldNames[0:2], "."), nil))

        oldValuer := meta.Valuer
        meta.Valuer = func(record interface{}, context *qor.Context) interface{} {
            return oldValuer(getNestedModel(record, strings.Join(fieldNames[0:2], "."), context), context)
        }
        return
    }

    if meta.FieldStruct != nil {
        meta.Valuer = func(value interface{}, context *qor.Context) interface{} {
            // get scope of current record. like Collection, then iterate its fields
            scope := context.GetDB().NewScope(value)

            if f, ok := scope.FieldByName(fieldName); ok {
                if relationship := f.Relationship; relationship != nil && f.Field.CanAddr() && !scope.PrimaryKeyZero() {
                    // Iterate each field see if it is an relationship field like
                    // Factories []factory.Factory
                    // If so, set the CompositePrimaryKey value for PrimaryKeyOf to read
                    if (relationship.Kind == "has_many" || relationship.Kind == "many_to_many") && f.Field.Len() == 0 {
                        // Retrieve the associated records from db
                        context.GetDB().Set("publish:version:mode", "multiple").Model(value).Related(f.Field.Addr().Interface(), fieldName)

                        setCompositePrimaryKey(f)
                    } else if (relationship.Kind == "has_one" || relationship.Kind == "belongs_to") && context.GetDB().NewScope(f.Field.Interface()).PrimaryKeyZero() {
                        if f.Field.Kind() == reflect.Ptr && f.Field.IsNil() {
                            f.Field.Set(reflect.New(f.Field.Type().Elem()))
                        }

                        context.GetDB().Set("publish:version:mode", "multiple").Model(value).Related(f.Field.Addr().Interface(), fieldName)
                    }
                }

                return f.Field.Interface()
            }

            return ""
        }
    }
}

// switchRecordToNewVersionIfNeeded is for switching to new version of the record when creating a new version.
// The given record must has function 'AssignVersionName' defined, with *Pointer* receiver to create associations on new version
// Otherwise, the operation would be omitted
// e.g. the user is creating a new version based on version "2021-3-3-v1". which would be "2021-3-3-v2".
//      the associations added during the creation should be associated with "2021-3-3-v2" rather than "2021-3-3-v1"
func switchRecordToNewVersionIfNeeded(context *qor.Context, record interface{}) interface{} {
    if context.Request == nil {
        return record
    }

    currentVersionName := context.Request.Form.Get("QorResource.VersionName")
    recordValue := reflect.ValueOf(record)
    if recordValue.Kind() == reflect.Ptr {
        recordValue = recordValue.Elem()
    }

    // Handle situation when the primary key is a uint64 not general uint
    var id uint64
    idUint, ok := recordValue.FieldByName("ID").Interface().(uint)
    if !ok {
        id64, ok := recordValue.FieldByName("ID").Interface().(uint64)
        if !ok {
            panic("ID filed must be uint or uint64")
        }
        id = id64
    } else {
        id = uint64(idUint)
    }

    // if currentVersionName is blank, we consider it is creating a new version
    if id != 0 && currentVersionName == "" {
        arguments := []reflect.Value{reflect.ValueOf(context.GetDB())}

        // Handle the situation when record is NOT a pointer
        if reflect.ValueOf(record).Kind() != reflect.Ptr {
            // We create a new pointer to be able to invoke the AssignVersionName method on Pointer receiver
            recordPtr := reflect.New(reflect.TypeOf(record))
            recordPtr.Elem().Set(reflect.ValueOf(record))
            fn := recordPtr.MethodByName("AssignVersionName")

            if !fn.IsValid() {
                log.Printf("Struct %v must has function 'AssignVersionName' defined, with *Pointer* receiver to create associations on new version", reflect.TypeOf(record).Name())
                return record
            }
            fn.Call(arguments)

            // Since it is a new pointer, we have to return the new record
            return recordPtr.Elem().Interface()
        }

        // When the record is a pointer
        fn := reflect.ValueOf(record).MethodByName("AssignVersionName")
        if !fn.IsValid() {
            log.Printf("Struct %v must has function 'AssignVersionName' defined, with *Pointer* receiver to create associations on new version", reflect.TypeOf(record).Name())
            return record
        }

        // AssignVersionName set the record's version name as the new version, so when execute the SQL, we can find correct object to apply the association
        fn.Call(arguments)
        return record
    }

    return record
}

func HandleBelongsTo(context *qor.Context, record reflect.Value, field reflect.Value, relationship *gorm.Relationship, primaryKeys []string) {
    // Read value from foreign key field. e.g.  TagID => 1
    oldPrimaryKeys := utils.ToArray(record.FieldByName(relationship.ForeignFieldNames[0]).Interface())
    // if not changed, return immediately
    if fmt.Sprint(primaryKeys) == fmt.Sprint(oldPrimaryKeys) {
        return
    }

    foreignKeyField := record.FieldByName(relationship.ForeignFieldNames[0])
    if len(primaryKeys) == 0 {
        // if foreign key removed
        foreignKeyField.Set(reflect.Zero(foreignKeyField.Type()))
    } else {
        // if foreign key changed. We need to make sure the field is a blank object
        // Suppose this is a Collection belongs to Tag association
        // non-blank field will perform a query like `SELECT * FROM "tags"  WHERE "tags"."deleted_at" IS NULL AND "tags"."id" = 1 AND (("tags"."id" IN ('2')))`
        // Usually this won't happen, cause the Tag field of Collection will be blank by default. it is a double assurance.
        field.FieldByName("ID").SetUint(0)
        context.GetDB().Set("publish:version:mode", "multiple").Where(primaryKeys).Find(field.Addr().Interface())
    }
}

func HandleVersioningBelongsTo(context *qor.Context, record reflect.Value, field reflect.Value, relationship *gorm.Relationship, primaryKeys []string, fieldHasVersion bool) {
    foreignKeyName := relationship.ForeignFieldNames[0]
    // Construct version name foreign key. e.g.  ManagerID -> ManagerVersionName
    foreignVersionName := strings.Replace(foreignKeyName, "ID", "VersionName", -1)

    foreignKeyField := record.FieldByName(foreignKeyName)
    foreignVersionField := record.FieldByName(foreignVersionName)

    oldPrimaryKeys := utils.ToArray(foreignKeyField.Interface())
    // If field struct has version and it defined XXVersionName foreignKey field
    // then construct ID+VersionName and compare with composite primarykey
    if fieldHasVersion && len(oldPrimaryKeys) != 0 && foreignVersionField.IsValid() {
        oldPrimaryKeys[0] = GenCompositePrimaryKey(oldPrimaryKeys[0], foreignVersionField.String())
    }

    // if not changed
    if fmt.Sprint(primaryKeys) == fmt.Sprint(oldPrimaryKeys) {
        return
    }

    // foreignkey removed
    if len(primaryKeys) == 0 {
        foreignKeyField.Set(reflect.Zero(foreignKeyField.Type()))
        // if field has version, we have to set both the id and version_name to zero value.
        if fieldHasVersion {
            foreignKeyField.Set(reflect.Zero(foreignKeyField.Type()))
            foreignVersionField.Set(reflect.Zero(foreignVersionField.Type()))
        }
        // foreignkey updated
    } else {
        // if foreign key changed. We need to make sure the field is a blank object
        // Suppose this is a Collection belongs to Tag association
        // non-blank field will perform a query like `SELECT * FROM "tags"  WHERE "tags"."deleted_at" IS NULL AND "tags"."id" = 1 AND (("tags"."id" IN ('2')))`
        // Usually this won't happen, cause the Tag field of Collection will be blank by default. it is a double assurance.
        field.FieldByName("ID").SetUint(0)

        compositePKeys := strings.Split(primaryKeys[0], CompositePrimaryKeySeparator)
        // If primaryKeys doesn't include version name, process it as an ID
        if len(compositePKeys) == 1 {
            context.GetDB().Set("publish:version:mode", "multiple").Where(primaryKeys).Find(field.Addr().Interface())
        } else {
            context.GetDB().Set("publish:version:mode", "multiple").Where("id = ? AND version_name = ?", compositePKeys[0], compositePKeys[1]).Find(field.Addr().Interface())
        }
    }
}

func CollectPrimaryKeys(metaValueForCompositePrimaryKeys []string) (compositePKeys []CompositePrimaryKeyStruct, compositePKeyConvertErr error) {
    // To convert []string{"1^|^2020-09-14-v1", "2^|^2020-09-14-v3"} to []compositePrimaryKey
    for _, rawCpk := range metaValueForCompositePrimaryKeys {
        // Skip blank string when it is not the only element
        if len(rawCpk) == 0 && len(metaValueForCompositePrimaryKeys) > 1 {
            continue
        }

        pks := strings.Split(rawCpk, CompositePrimaryKeySeparator)
        if len(pks) != 2 {
            compositePKeyConvertErr = errors.New("metaValue is not for composite primary key")
            break
        }

        id, convErr := strconv.ParseUint(pks[0], 10, 32)
        if convErr != nil {
            compositePKeyConvertErr = fmt.Errorf("composite primary key has incorrect id %s", pks[0])
            break
        }

        cpk := CompositePrimaryKeyStruct{
            ID:          uint(id),
            VersionName: pks[1],
        }

        compositePKeys = append(compositePKeys, cpk)
    }

    return
}

func HandleManyToMany(context *qor.Context, scope *gorm.Scope, meta *Meta, record interface{}, metaValue *MetaValue, field reflect.Value, fieldHasVersion bool) {
    metaValueForCompositePrimaryKeys, ok := metaValue.Value.([]string)
    compositePKeys := []CompositePrimaryKeyStruct{}
    var compositePKeyConvertErr error

    if ok {
        compositePKeys, compositePKeyConvertErr = CollectPrimaryKeys(metaValueForCompositePrimaryKeys)
    }

    // If the field is a struct with version and metaValue is present and we can collect id + version_name combination
    // It means we can make the query by specific condition
    if fieldHasVersion && metaValue.Value != nil && compositePKeyConvertErr == nil && len(compositePKeys) > 0 {
        HandleVersionedManyToMany(context, field, compositePKeys)
    } else {
        HandleNormalManyToMany(context, field, metaValue, fieldHasVersion, compositePKeyConvertErr)
    }

    if !scope.PrimaryKeyZero() {
        context.GetDB().Model(record).Association(meta.FieldName).Replace(field.Interface())
        field.Set(reflect.Zero(field.Type()))
    }
}

// HandleNormalManyToMany not only handle normal many_to_many relationship, it also handled the situation that user set the association to blank
func HandleNormalManyToMany(context *qor.Context, field reflect.Value, metaValue *MetaValue, fieldHasVersion bool, compositePKeyConvertErr error) {
    if fieldHasVersion && metaValue.Value != nil && compositePKeyConvertErr != nil {
        fmt.Println("given meta value contains no version name, this might cause the association is incorrect")
    }

    primaryKeys := utils.ToArray(metaValue.Value)
    if metaValue.Value == nil {
        primaryKeys = []string{}
    }

    // set current field value to blank. This line responsible for set field to blank value when metaValue is nil
    // which means user removed all associations
    field.Set(reflect.Zero(field.Type()))

    if len(primaryKeys) > 0 {
        // replace it with new value
        context.GetDB().Set("publish:version:mode", "multiple").Where(primaryKeys).Find(field.Addr().Interface())
    }
}

// HandleVersionedManyToMany handle id+version_name composite primary key, query and set the correct result to the "Many" field
// e.g. Collection.Products
// This doesn't handle compositePKeys is blank logic, if it is, this function should not be invoked
func HandleVersionedManyToMany(context *qor.Context, field reflect.Value, compositePKeys []CompositePrimaryKeyStruct) {
    // set current field value to blank
    field.Set(reflect.Zero(field.Type()))

    // eliminate potential version_name condition on the main object, we don't need it when querying associated records
    // it usually added by qor/publish2.
    db := context.GetDB().Set("publish:version:mode", "multiple")
    for i, compositePKey := range compositePKeys {
        if i == 0 {
            db = db.Where("id = ? AND version_name = ?", compositePKey.ID, compositePKey.VersionName)
        } else {
            db = db.Or("id = ? AND version_name = ?", compositePKey.ID, compositePKey.VersionName)
        }
    }

    db.Find(field.Addr().Interface())
}

func setupSetter(meta *Meta, fieldName string, record interface{}) {
    nestedField := strings.Contains(fieldName, ".")

    // Setup nested fields
    if nestedField {
        fieldNames := strings.Split(fieldName, ".")
        setupSetter(meta, strings.Join(fieldNames[1:], "."), getNestedModel(record, strings.Join(fieldNames[0:2], "."), nil))

        oldSetter := meta.Setter
        meta.Setter = func(record interface{}, metaValue *MetaValue, context *qor.Context) {
            oldSetter(getNestedModel(record, strings.Join(fieldNames[0:2], "."), context), metaValue, context)
        }
        return
    }

    commonSetter := func(setter func(field reflect.Value, metaValue *MetaValue, context *qor.Context, record interface{})) func(record interface{}, metaValue *MetaValue, context *qor.Context) {
        return func(record interface{}, metaValue *MetaValue, context *qor.Context) {
            if metaValue == nil {
                return
            }

            defer func() {
                if r := recover(); r != nil {
                    fmt.Println(r)
                    debug.PrintStack()
                    context.AddError(validations.NewError(record, meta.Name, fmt.Sprintf("Failed to set Meta %v's value with %v, got %v", meta.Name, metaValue.Value, r)))
                }
            }()

            field := utils.Indirect(reflect.ValueOf(record)).FieldByName(fieldName)
            if field.Kind() == reflect.Ptr {
                if field.IsNil() && utils.ToString(metaValue.Value) != "" {
                    field.Set(utils.NewValue(field.Type()).Elem())
                }

                if utils.ToString(metaValue.Value) == "" {
                    field.Set(reflect.Zero(field.Type()))
                    return
                }

                for field.Kind() == reflect.Ptr {
                    field = field.Elem()
                }
            }

            if field.IsValid() && field.CanAddr() {
                setter(field, metaValue, context, record)
            }
        }
    }

    // Setup belongs_to / many_to_many Setter
    if meta.FieldStruct != nil {
        if relationship := meta.FieldStruct.Relationship; relationship != nil {
            if relationship.Kind == "belongs_to" || relationship.Kind == "many_to_many" {
                meta.Setter = commonSetter(func(field reflect.Value, metaValue *MetaValue, context *qor.Context, record interface{}) {
                    var (
                        scope         = context.GetDB().NewScope(record)
                        recordAsValue = reflect.Indirect(reflect.ValueOf(record))
                    )
                    switchRecordToNewVersionIfNeeded(context, record)

                    // If the field struct has version
                    fieldHasVersion := fieldIsStructAndHasVersion(field)

                    if relationship.Kind == "belongs_to" {
                        primaryKeys := utils.ToArray(metaValue.Value)
                        if metaValue.Value == nil {
                            primaryKeys = []string{}
                        }

                        // For normal belongs_to association
                        if len(relationship.ForeignFieldNames) == 1 {
                            HandleBelongsTo(context, recordAsValue, field, relationship, primaryKeys)
                        }

                        // For versioning association
                        if len(relationship.ForeignFieldNames) == 2 {
                            HandleVersioningBelongsTo(context, recordAsValue, field, relationship, primaryKeys, fieldHasVersion)
                        }
                    }

                    if relationship.Kind == "many_to_many" {
                        // The reason why we use `record` as an interface{} here rather than `recordAsValue` is
                        // we need make query by record, it must be a pointer, but belongs_to make query based on field, no need to be pointer.
                        HandleManyToMany(context, scope, meta, record, metaValue, field, fieldHasVersion)
                    }
                })

                return
            }
        }
    }

    field := reflect.Indirect(reflect.ValueOf(record)).FieldByName(fieldName)
    for field.Kind() == reflect.Ptr {
        if field.IsNil() {
            field.Set(utils.NewValue(field.Type().Elem()))
        }
        field = field.Elem()
    }

    if !field.IsValid() {
        return
    }

    switch field.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        meta.Setter = commonSetter(func(field reflect.Value, metaValue *MetaValue, context *qor.Context, record interface{}) {
            field.SetInt(utils.ToInt(metaValue.Value))
        })
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        meta.Setter = commonSetter(func(field reflect.Value, metaValue *MetaValue, context *qor.Context, record interface{}) {
            field.SetUint(utils.ToUint(metaValue.Value))
        })
    case reflect.Float32, reflect.Float64:
        meta.Setter = commonSetter(func(field reflect.Value, metaValue *MetaValue, context *qor.Context, record interface{}) {
            field.SetFloat(utils.ToFloat(metaValue.Value))
        })
    case reflect.Bool:
        meta.Setter = commonSetter(func(field reflect.Value, metaValue *MetaValue, context *qor.Context, record interface{}) {
            if utils.ToString(metaValue.Value) == "true" {
                field.SetBool(true)
            } else {
                field.SetBool(false)
            }
        })
    default:
        if _, ok := field.Addr().Interface().(sql.Scanner); ok {
            meta.Setter = commonSetter(func(field reflect.Value, metaValue *MetaValue, context *qor.Context, record interface{}) {
                if scanner, ok := field.Addr().Interface().(sql.Scanner); ok {
                    if metaValue.Value == nil && len(metaValue.MetaValues.Values) > 0 {
                        decodeMetaValuesToField(meta.Resource, field, metaValue, context)
                        return
                    }

                    if scanner.Scan(metaValue.Value) != nil {
                        if err := scanner.Scan(utils.ToString(metaValue.Value)); err != nil {
                            context.AddError(err)
                            return
                        }
                    }
                }
            })
        } else if reflect.TypeOf("").ConvertibleTo(field.Type()) {
            meta.Setter = commonSetter(func(field reflect.Value, metaValue *MetaValue, context *qor.Context, record interface{}) {
                field.Set(reflect.ValueOf(utils.ToString(metaValue.Value)).Convert(field.Type()))
            })
        } else if reflect.TypeOf([]string{}).ConvertibleTo(field.Type()) {
            meta.Setter = commonSetter(func(field reflect.Value, metaValue *MetaValue, context *qor.Context, record interface{}) {
                field.Set(reflect.ValueOf(utils.ToArray(metaValue.Value)).Convert(field.Type()))
            })
        } else if _, ok := field.Addr().Interface().(*time.Time); ok {
            meta.Setter = commonSetter(func(field reflect.Value, metaValue *MetaValue, context *qor.Context, record interface{}) {
                if str := utils.ToString(metaValue.Value); str != "" {
                    if newTime, err := utils.ParseTime(str, context); err == nil {
                        field.Set(reflect.ValueOf(newTime))
                    }
                } else {
                    field.Set(reflect.Zero(field.Type()))
                }
            })
        }
    }
}

func getNestedModel(value interface{}, fieldName string, context *qor.Context) interface{} {
    model := reflect.Indirect(reflect.ValueOf(value))
    fields := strings.Split(fieldName, ".")
    for _, field := range fields[:len(fields)-1] {
        if model.CanAddr() {
            submodel := model.FieldByName(field)
            if context != nil && context.GetDB() != nil && context.GetDB().NewRecord(submodel.Interface()) && !context.GetDB().NewRecord(model.Addr().Interface()) {
                if submodel.CanAddr() {
                    context.GetDB().Model(model.Addr().Interface()).Association(field).Find(submodel.Addr().Interface())
                    model = submodel
                } else {
                    break
                }
            } else {
                model = submodel
            }
        }
    }

    if model.CanAddr() {
        return model.Addr().Interface()
    }
    return nil
}

// fieldStructHasVersion determine if the given field is a struct
// if so, detect if it has publish2.Version integrated
func fieldIsStructAndHasVersion(field reflect.Value) bool {
    // If the field struct has version
    if field.Type().Kind() == reflect.Slice || field.Type().Kind() == reflect.Struct {
        underlyingType := field.Type()
        // If the field is a slice of struct, we retrive one element(struct) as a sample to determine whether it has version
        // e.g. []User -> User
        if field.Type().Kind() == reflect.Slice {
            underlyingType = underlyingType.Elem()
            if underlyingType.Kind() == reflect.Ptr {
                underlyingType = underlyingType.Elem()
            }
        }

        for i := 0; i < underlyingType.NumField(); i++ {
            if underlyingType.Field(i).Name == "Version" && underlyingType.Field(i).Type.String() == "publish2.Version" {
                return true
            }
        }
    }

    return false
}