diff --git a/database/gdb/gdb_model_with.go b/database/gdb/gdb_model_with.go index 892474228..bd6d37883 100644 --- a/database/gdb/gdb_model_with.go +++ b/database/gdb/gdb_model_with.go @@ -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 } diff --git a/database/gdb/gdb_z_mysql_association_test.go b/database/gdb/gdb_z_mysql_association_scanlist_test.go similarity index 100% rename from database/gdb/gdb_z_mysql_association_test.go rename to database/gdb/gdb_z_mysql_association_scanlist_test.go diff --git a/database/gdb/gdb_z_mysql_association_with_test.go b/database/gdb/gdb_z_mysql_association_with_test.go index 7cf1bfd92..43a0f3ab7 100644 --- a/database/gdb/gdb_z_mysql_association_with_test.go +++ b/database/gdb/gdb_z_mysql_association_with_test.go @@ -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) + }) +}