improve with feature by supporting attributes struct which also have 'with' tag

This commit is contained in:
John Guo
2021-05-12 21:34:15 +08:00
parent 9d362c3738
commit d21b9d58e1
3 changed files with 574 additions and 170 deletions

View File

@ -53,18 +53,41 @@ func (m *Model) WithAll() *Model {
return model
}
// getWithTagObjectArrayFrom retrieves and returns object array that have "with" tag in the struct.
func (m *Model) getWithTagObjectArrayFrom(pointer interface{}) ([]interface{}, error) {
// doWithScanStruct handles model association operations feature for single struct.
func (m *Model) doWithScanStruct(pointer interface{}) error {
var (
err error
allowedTypeStrArray = make([]string, 0)
)
fieldMap, err := structs.FieldMap(pointer, nil, false)
if err != nil {
return nil, err
return err
}
withTagObjectArray := make([]interface{}, 0)
for _, fieldValue := range fieldMap {
// It checks the with array and automatically calls the ScanList to complete association querying.
if !m.withAll {
for _, field := range fieldMap {
for _, withItem := range m.withArray {
withItemReflectValueType, err := structs.StructType(withItem)
if err != nil {
return err
}
var (
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
withItemReflectValueTypeStr = gstr.TrimAll(withItemReflectValueType.String(), "*[]")
)
// It does select operation if the field type is in the specified with type array.
if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 {
allowedTypeStrArray = append(allowedTypeStrArray, fieldTypeStr)
}
}
}
}
for _, field := range fieldMap {
var (
withTag string
ormTag = fieldValue.Tag(OrmTagForStruct)
match, _ = gregex.MatchString(
withTag string
ormTag = field.Tag(OrmTagForStruct)
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
match, _ = gregex.MatchString(
fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith),
ormTag,
)
@ -75,103 +98,63 @@ func (m *Model) getWithTagObjectArrayFrom(pointer interface{}) ([]interface{}, e
if withTag == "" {
continue
}
withTagObjectArray = append(withTagObjectArray, fieldValue.Value.Interface())
}
return withTagObjectArray, nil
}
// doWithScanStruct handles model association operations feature for single struct.
func (m *Model) doWithScanStruct(pointer interface{}) error {
var (
err error
withArray = m.withArray
)
// If with all feature is enabled, it then retrieves all the attributes which have with tag defined.
if m.withAll {
withArray, err = m.getWithTagObjectArrayFrom(pointer)
if err != nil {
return err
if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) {
continue
}
}
if len(withArray) == 0 {
return nil
}
fieldMap, err := structs.FieldMap(pointer, nil, false)
if err != nil {
return err
}
// Check the with array and automatically call the ScanList to complete association querying.
for withIndex, withItem := range withArray {
withItemReflectValueType, err := structs.StructType(withItem)
if err != nil {
return err
array := gstr.SplitAndTrim(withTag, "=")
if len(array) == 1 {
// It supports using only one column name
// if both tables associates using the same column name.
array = append(array, withTag)
}
withItemReflectValueTypeStr := gstr.TrimAll(withItemReflectValueType.String(), "*[]")
for _, fieldValue := range fieldMap {
var (
fieldType = fieldValue.Type()
fieldTypeStr = gstr.TrimAll(fieldType.String(), "*[]")
)
// It does select operation if the field type is in the specified with type array.
if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 {
var (
withTag string
ormTag = fieldValue.Tag(OrmTagForStruct)
match, _ = gregex.MatchString(
fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith),
ormTag,
)
)
if len(match) > 1 {
withTag = match[1]
}
if withTag == "" {
continue
}
array := gstr.SplitAndTrim(withTag, "=")
if len(array) != 2 {
return gerror.Newf(`invalid with tag "%s"`, withTag)
}
var (
relatedFieldName = array[0]
relatedAttrName = array[1]
relatedFieldValue interface{}
)
// Find the value of related attribute from `pointer`.
for attributeName, attributeValue := range fieldMap {
if utils.EqualFoldWithoutChars(attributeName, relatedAttrName) {
relatedFieldValue = attributeValue.Value.Interface()
break
}
}
if relatedFieldValue == nil {
return gerror.Newf(
`cannot find the related value for attribute name "%s" of with tag "%s"`,
relatedAttrName, withTag,
)
}
bindToReflectValue := fieldValue.Value
switch bindToReflectValue.Kind() {
case reflect.Array, reflect.Slice:
if bindToReflectValue.CanAddr() {
bindToReflectValue = bindToReflectValue.Addr()
}
}
model := m.db.With(fieldValue.Value)
for i, v := range withArray {
if i == withIndex {
continue
}
model = model.With(v)
}
err = model.Fields(withItemReflectValueType.FieldKeys()).
Where(relatedFieldName, relatedFieldValue).
Scan(bindToReflectValue)
if err != nil {
return err
}
var (
model *Model
fieldKeys []string
relatedFieldName = array[0]
relatedAttrName = array[1]
relatedFieldValue interface{}
)
// Find the value of related attribute from `pointer`.
for attributeName, attributeValue := range fieldMap {
if utils.EqualFoldWithoutChars(attributeName, relatedAttrName) {
relatedFieldValue = attributeValue.Value.Interface()
break
}
}
if relatedFieldValue == nil {
return gerror.Newf(
`cannot find the related value for attribute name "%s" of with tag "%s"`,
relatedAttrName, withTag,
)
}
bindToReflectValue := field.Value
switch bindToReflectValue.Kind() {
case reflect.Array, reflect.Slice:
if bindToReflectValue.CanAddr() {
bindToReflectValue = bindToReflectValue.Addr()
}
}
// It automatically retrieves struct field names from current attribute struct/slice.
if structType, err := structs.StructType(field.Value); err != nil {
return err
} else {
fieldKeys = structType.FieldKeys()
}
// Recursively with feature checks.
model = m.db.With(field.Value)
if m.withAll {
model = model.WithAll()
} else {
model = model.With(m.withArray...)
}
err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).Scan(bindToReflectValue)
if err != nil {
return err
}
}
return nil
}
@ -180,85 +163,98 @@ func (m *Model) doWithScanStruct(pointer interface{}) error {
// Also see doWithScanStruct.
func (m *Model) doWithScanStructs(pointer interface{}) error {
var (
err error
withArray = m.withArray
err error
allowedTypeStrArray = make([]string, 0)
)
if m.withAll {
withArray, err = m.getWithTagObjectArrayFrom(pointer)
if err != nil {
return err
}
}
if len(withArray) == 0 {
return nil
}
fieldMap, err := structs.FieldMap(pointer, nil, false)
if err != nil {
return err
}
for withIndex, withItem := range withArray {
withItemReflectValueType, err := structs.StructType(withItem)
if err != nil {
return err
}
withItemReflectValueTypeStr := gstr.TrimAll(withItemReflectValueType.String(), "*[]")
for fieldName, fieldValue := range fieldMap {
var (
fieldType = fieldValue.Type()
fieldTypeStr = gstr.TrimAll(fieldType.String(), "*[]")
)
if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 {
var (
withTag string
ormTag = fieldValue.Tag(OrmTagForStruct)
match, _ = gregex.MatchString(
fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith),
ormTag,
)
)
if len(match) > 1 {
withTag = match[1]
}
if withTag == "" {
continue
}
array := gstr.SplitAndTrim(withTag, "=")
if len(array) != 2 {
return gerror.Newf(`invalid with tag "%s"`, withTag)
}
var (
relatedFieldName = array[0]
relatedAttrName = array[1]
relatedFieldValue interface{}
)
// Find the value slice of related attribute from `pointer`.
for attributeName, _ := range fieldMap {
if utils.EqualFoldWithoutChars(attributeName, relatedAttrName) {
relatedFieldValue = ListItemValuesUnique(pointer, attributeName)
break
}
}
if relatedFieldValue == nil {
return gerror.Newf(
`cannot find the related value for attribute name "%s" of with tag "%s"`,
relatedAttrName, withTag,
)
}
model := m.db.With(fieldValue.Value)
for i, v := range withArray {
if i == withIndex {
continue
}
model = model.With(v)
}
err = model.Fields(withItemReflectValueType.FieldKeys()).
Where(relatedFieldName, relatedFieldValue).
ScanList(pointer, fieldName, withTag)
// It checks the with array and automatically calls the ScanList to complete association querying.
if !m.withAll {
for _, field := range fieldMap {
for _, withItem := range m.withArray {
withItemReflectValueType, err := structs.StructType(withItem)
if err != nil {
return err
}
var (
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
withItemReflectValueTypeStr = gstr.TrimAll(withItemReflectValueType.String(), "*[]")
)
// It does select operation if the field type is in the specified with type array.
if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 {
allowedTypeStrArray = append(allowedTypeStrArray, fieldTypeStr)
}
}
}
}
for fieldName, field := range fieldMap {
var (
withTag string
ormTag = field.Tag(OrmTagForStruct)
fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
match, _ = gregex.MatchString(
fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith),
ormTag,
)
)
if len(match) > 1 {
withTag = match[1]
}
if withTag == "" {
continue
}
if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) {
continue
}
array := gstr.SplitAndTrim(withTag, "=")
if len(array) == 1 {
// It supports using only one column name
// if both tables associates using the same column name.
array = append(array, withTag)
}
var (
model *Model
fieldKeys []string
relatedFieldName = array[0]
relatedAttrName = array[1]
relatedFieldValue interface{}
)
// Find the value slice of related attribute from `pointer`.
for attributeName, _ := range fieldMap {
if utils.EqualFoldWithoutChars(attributeName, relatedAttrName) {
relatedFieldValue = ListItemValuesUnique(pointer, attributeName)
break
}
}
if relatedFieldValue == nil {
return gerror.Newf(
`cannot find the related value for attribute name "%s" of with tag "%s"`,
relatedAttrName, withTag,
)
}
// It automatically retrieves struct field names from current attribute struct/slice.
if structType, err := structs.StructType(field.Value); err != nil {
return err
} else {
fieldKeys = structType.FieldKeys()
}
// Recursively with feature checks.
model = m.db.With(field.Value)
if m.withAll {
model = model.WithAll()
} else {
model = model.With(m.withArray...)
}
err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).ScanList(pointer, fieldName, withTag)
if err != nil {
return err
}
}
return nil
}

View File

@ -781,3 +781,411 @@ PRIMARY KEY (id)
t.Assert(user.UserScores[4].Score, 5)
})
}
func Test_Table_Relation_WithAll_AttributeStructAlsoHasWithTag(t *testing.T) {
var (
tableUser = "user"
tableUserDetail = "user_detail"
tableUserScores = "user_scores"
)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(45) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUser)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUser)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
address varchar(45) NOT NULL,
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserDetail)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserDetail)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
uid int(10) unsigned NOT NULL,
score int(10) unsigned NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserScores)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserScores)
type UserScores struct {
gmeta.Meta `orm:"table:user_scores"`
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
}
type UserDetail struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
UserScores []*UserScores `orm:"with:uid"`
}
type User struct {
gmeta.Meta `orm:"table:user"`
*UserDetail `orm:"with:uid=id"`
Id int `json:"id"`
Name string `json:"name"`
}
// Initialize the data.
var err error
for i := 1; i <= 5; i++ {
// User.
_, err = db.Insert(tableUser, g.Map{
"id": i,
"name": fmt.Sprintf(`name_%d`, i),
})
gtest.Assert(err, nil)
// Detail.
_, err = db.Insert(tableUserDetail, g.Map{
"uid": i,
"address": fmt.Sprintf(`address_%d`, i),
})
gtest.Assert(err, nil)
// Scores.
for j := 1; j <= 5; j++ {
_, err = db.Insert(tableUserScores, g.Map{
"uid": i,
"score": j,
})
gtest.Assert(err, nil)
}
}
gtest.C(t, func(t *gtest.T) {
var user *User
err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 3)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 3)
t.Assert(user.UserDetail.Address, `address_3`)
t.Assert(len(user.UserDetail.UserScores), 5)
t.Assert(user.UserDetail.UserScores[0].Uid, 3)
t.Assert(user.UserDetail.UserScores[0].Score, 1)
t.Assert(user.UserDetail.UserScores[4].Uid, 3)
t.Assert(user.UserDetail.UserScores[4].Score, 5)
})
gtest.C(t, func(t *gtest.T) {
var user User
err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 4)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 4)
t.Assert(user.UserDetail.Address, `address_4`)
t.Assert(len(user.UserDetail.UserScores), 5)
t.Assert(user.UserDetail.UserScores[0].Uid, 4)
t.Assert(user.UserDetail.UserScores[0].Score, 1)
t.Assert(user.UserDetail.UserScores[4].Uid, 4)
t.Assert(user.UserDetail.UserScores[4].Score, 5)
})
}
func Test_Table_Relation_WithAll_AttributeStructAlsoHasWithTag_MoreDeep(t *testing.T) {
var (
tableUser = "user"
tableUserDetail = "user_detail"
tableUserScores = "user_scores"
)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(45) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUser)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUser)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
address varchar(45) NOT NULL,
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserDetail)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserDetail)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
uid int(10) unsigned NOT NULL,
score int(10) unsigned NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserScores)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserScores)
type UserScores struct {
gmeta.Meta `orm:"table:user_scores"`
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
}
type UserDetail1 struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
UserScores []*UserScores `orm:"with:uid"`
}
type UserDetail2 struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
UserDetail1 *UserDetail1 `orm:"with:uid"`
UserScores []*UserScores `orm:"with:uid"`
}
type UserDetail3 struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
UserDetail2 *UserDetail2 `orm:"with:uid"`
UserScores []*UserScores `orm:"with:uid"`
}
type UserDetail struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
UserDetail3 *UserDetail3 `orm:"with:uid"`
UserScores []*UserScores `orm:"with:uid"`
}
type User struct {
gmeta.Meta `orm:"table:user"`
*UserDetail `orm:"with:uid=id"`
Id int `json:"id"`
Name string `json:"name"`
}
// Initialize the data.
var err error
for i := 1; i <= 5; i++ {
// User.
_, err = db.Insert(tableUser, g.Map{
"id": i,
"name": fmt.Sprintf(`name_%d`, i),
})
gtest.Assert(err, nil)
// Detail.
_, err = db.Insert(tableUserDetail, g.Map{
"uid": i,
"address": fmt.Sprintf(`address_%d`, i),
})
gtest.Assert(err, nil)
// Scores.
for j := 1; j <= 5; j++ {
_, err = db.Insert(tableUserScores, g.Map{
"uid": i,
"score": j,
})
gtest.Assert(err, nil)
}
}
gtest.C(t, func(t *gtest.T) {
var user *User
err := db.Model(tableUser).WithAll().Where("id", 3).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 3)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 3)
t.Assert(user.UserDetail.UserDetail3.Uid, 3)
t.Assert(user.UserDetail.UserDetail3.UserDetail2.Uid, 3)
t.Assert(user.UserDetail.UserDetail3.UserDetail2.UserDetail1.Uid, 3)
t.Assert(user.UserDetail.Address, `address_3`)
t.Assert(len(user.UserDetail.UserScores), 5)
t.Assert(user.UserDetail.UserScores[0].Uid, 3)
t.Assert(user.UserDetail.UserScores[0].Score, 1)
t.Assert(user.UserDetail.UserScores[4].Uid, 3)
t.Assert(user.UserDetail.UserScores[4].Score, 5)
})
gtest.C(t, func(t *gtest.T) {
var user User
err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 4)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 4)
t.Assert(user.UserDetail.UserDetail3.Uid, 4)
t.Assert(user.UserDetail.UserDetail3.UserDetail2.Uid, 4)
t.Assert(user.UserDetail.UserDetail3.UserDetail2.UserDetail1.Uid, 4)
t.Assert(user.UserDetail.Address, `address_4`)
t.Assert(len(user.UserDetail.UserScores), 5)
t.Assert(user.UserDetail.UserScores[0].Uid, 4)
t.Assert(user.UserDetail.UserScores[0].Score, 1)
t.Assert(user.UserDetail.UserScores[4].Uid, 4)
t.Assert(user.UserDetail.UserScores[4].Score, 5)
})
}
func Test_Table_Relation_With_AttributeStructAlsoHasWithTag_MoreDeep(t *testing.T) {
var (
tableUser = "user"
tableUserDetail = "user_detail"
tableUserScores = "user_scores"
)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(45) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUser)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUser)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
address varchar(45) NOT NULL,
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserDetail)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserDetail)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
uid int(10) unsigned NOT NULL,
score int(10) unsigned NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableUserScores)); err != nil {
gtest.Error(err)
}
defer dropTable(tableUserScores)
type UserScores struct {
gmeta.Meta `orm:"table:user_scores"`
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
}
type UserDetail1 struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
UserScores []*UserScores `orm:"with:uid"`
}
type UserDetail2 struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
UserDetail1 *UserDetail1 `orm:"with:uid"`
UserScores []*UserScores `orm:"with:uid"`
}
type UserDetail3 struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
UserDetail2 *UserDetail2 `orm:"with:uid"`
UserScores []*UserScores `orm:"with:uid"`
}
type UserDetail struct {
gmeta.Meta `orm:"table:user_detail"`
Uid int `json:"uid"`
Address string `json:"address"`
UserDetail3 *UserDetail3 `orm:"with:uid"`
UserScores []*UserScores `orm:"with:uid"`
}
type User struct {
gmeta.Meta `orm:"table:user"`
*UserDetail `orm:"with:uid=id"`
Id int `json:"id"`
Name string `json:"name"`
}
// Initialize the data.
var err error
for i := 1; i <= 5; i++ {
// User.
_, err = db.Insert(tableUser, g.Map{
"id": i,
"name": fmt.Sprintf(`name_%d`, i),
})
gtest.Assert(err, nil)
// Detail.
_, err = db.Insert(tableUserDetail, g.Map{
"uid": i,
"address": fmt.Sprintf(`address_%d`, i),
})
gtest.Assert(err, nil)
// Scores.
for j := 1; j <= 5; j++ {
_, err = db.Insert(tableUserScores, g.Map{
"uid": i,
"score": j,
})
gtest.Assert(err, nil)
}
}
gtest.C(t, func(t *gtest.T) {
var user *User
err := db.Model(tableUser).With(UserDetail{}, UserDetail2{}, UserDetail3{}, UserScores{}).Where("id", 3).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 3)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 3)
t.Assert(user.UserDetail.UserDetail3.Uid, 3)
t.Assert(user.UserDetail.UserDetail3.UserDetail2.Uid, 3)
t.Assert(user.UserDetail.UserDetail3.UserDetail2.UserDetail1, nil)
t.Assert(user.UserDetail.Address, `address_3`)
t.Assert(len(user.UserDetail.UserScores), 5)
t.Assert(user.UserDetail.UserScores[0].Uid, 3)
t.Assert(user.UserDetail.UserScores[0].Score, 1)
t.Assert(user.UserDetail.UserScores[4].Uid, 3)
t.Assert(user.UserDetail.UserScores[4].Score, 5)
})
gtest.C(t, func(t *gtest.T) {
var user User
err := db.Model(tableUser).With(UserDetail{}, UserDetail2{}, UserDetail3{}, UserScores{}).Where("id", 4).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 4)
t.AssertNE(user.UserDetail, nil)
t.Assert(user.UserDetail.Uid, 4)
t.Assert(user.UserDetail.UserDetail3.Uid, 4)
t.Assert(user.UserDetail.UserDetail3.UserDetail2.Uid, 4)
t.Assert(user.UserDetail.UserDetail3.UserDetail2.UserDetail1, nil)
t.Assert(user.UserDetail.Address, `address_4`)
t.Assert(len(user.UserDetail.UserScores), 5)
t.Assert(user.UserDetail.UserScores[0].Uid, 4)
t.Assert(user.UserDetail.UserScores[0].Score, 1)
t.Assert(user.UserDetail.UserScores[4].Uid, 4)
t.Assert(user.UserDetail.UserScores[4].Score, 5)
})
}