Skip to content

Commit

Permalink
More Consistent Assoc Definition (go-rel#18)
Browse files Browse the repository at this point in the history
* use db field name for assoc definition, and shorten structag name

* added todo
  • Loading branch information
Fs02 committed Dec 25, 2019
1 parent e11057b commit a1c8912
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 52 deletions.
84 changes: 45 additions & 39 deletions association.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package rel
import (
"reflect"
"sync"

"github.com/azer/snakecase"
)

// AssociationType defines the type of association in database.
Expand All @@ -26,9 +28,9 @@ type associationData struct {
typ AssociationType
targetIndex []int
referenceColumn string
referenceIndex []int
referenceIndex int
foreignField string
foreignIndex []int
foreignIndex int
}

var associationCache sync.Map
Expand Down Expand Up @@ -114,7 +116,7 @@ func (a Association) ReferenceField() string {

// ReferenceValue of the association.
func (a Association) ReferenceValue() interface{} {
return indirect(a.rv.FieldByIndex(a.data.referenceIndex))
return indirect(a.rv.Field(a.data.referenceIndex))
}

// ForeignField of the association.
Expand All @@ -141,85 +143,89 @@ func (a Association) ForeignValue() interface{} {
rv = rv.Elem()
}

return indirect(rv.FieldByIndex(a.data.foreignIndex))
return indirect(rv.Field(a.data.foreignIndex))
}

func newAssociation(rv reflect.Value, index int) Association {
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}

return Association{
data: extractAssociationData(rv.Type(), index),
rv: rv,
}
}

func extractAssociationData(rt reflect.Type, index int) associationData {
var (
rt = rv.Type()
key = associationKey{
rt: rt,
index: index,
}
)

if val, cached := associationCache.Load(key); cached {
return Association{
data: val.(associationData),
rv: rv,
}
return val.(associationData)
}

// TODO: maybe use column name instead of field name for ref and fk key
var (
st = rt.Field(index)
ft = st.Type
ref = st.Tag.Get("references")
fk = st.Tag.Get("foreign_key")
typ = st.Tag.Get("association")
data = associationData{
targetIndex: st.Index,
sf = rt.Field(index)
ft = sf.Type
ref = sf.Tag.Get("ref")
fk = sf.Tag.Get("fk")
fName = fieldName(sf)
assocData = associationData{
targetIndex: sf.Index,
}
)

if ft.Kind() == reflect.Ptr || ft.Kind() == reflect.Slice || ft.Kind() == reflect.Array {
ft = ft.Elem()
}

var (
refDocData = extractDocumentData(rt, true)
fkDocData = extractDocumentData(ft, true)
)

// Try to guess ref and fk if not defined.
if ref == "" || fk == "" {
if _, isBelongsTo := rt.FieldByName(st.Name + "ID"); isBelongsTo {
ref = st.Name + "ID"
fk = "ID"
if _, isBelongsTo := refDocData.index[fName+"_id"]; isBelongsTo {
ref = fName + "_id"
fk = "id"
} else {
ref = "ID"
fk = rt.Name() + "ID"
ref = "id"
fk = snakecase.SnakeCase(rt.Name()) + "_id"
}
}

if reft, exist := rt.FieldByName(ref); !exist {
if id, exist := refDocData.index[ref]; !exist {
panic("rel: references (" + ref + ") field not found ")
} else {
data.referenceIndex = reft.Index
data.referenceColumn = fieldName(reft)
assocData.referenceIndex = id
assocData.referenceColumn = ref
}

if fkt, exist := ft.FieldByName(fk); !exist {
panic("rel: foreign_key (" + fk + ") field not found " + fk)
if id, exist := fkDocData.index[fk]; !exist {
panic("rel: foreign_key (" + fk + ") field not found")
} else {
data.foreignIndex = fkt.Index
data.foreignField = fieldName(fkt)
assocData.foreignIndex = id
assocData.foreignField = fk
}

// guess assoc type
if st.Type.Kind() == reflect.Slice || st.Type.Kind() == reflect.Array {
data.typ = HasMany
if sf.Type.Kind() == reflect.Slice || sf.Type.Kind() == reflect.Array {
assocData.typ = HasMany
} else {
if typ == "belongs_to" || len(data.referenceColumn) > len(data.foreignField) {
data.typ = BelongsTo
if len(assocData.referenceColumn) > len(assocData.foreignField) {
assocData.typ = BelongsTo
} else {
data.typ = HasOne
assocData.typ = HasOne
}
}

associationCache.Store(key, data)
associationCache.Store(key, assocData)

return Association{
data: data,
rv: rv,
}
return assocData
}
27 changes: 16 additions & 11 deletions document.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,12 +303,12 @@ func newDocument(v interface{}, rv reflect.Value, readonly bool) *Document {
v: v,
rv: rv,
rt: rt,
data: extractdocumentData(rv, rt),
data: extractDocumentData(rt, false),
}
}

func extractdocumentData(rv reflect.Value, rt reflect.Type) documentData {
if data, cached := documentDataCache.Load(rv.Type()); cached {
func extractDocumentData(rt reflect.Type, skipAssoc bool) documentData {
if data, cached := documentDataCache.Load(rt); cached {
return data.(documentData)
}

Expand All @@ -318,6 +318,7 @@ func extractdocumentData(rv reflect.Value, rt reflect.Type) documentData {
}
)

// TODO probably better to use slice index instead.
for i := 0; i < rt.NumField(); i++ {
var (
sf = rt.Field(i)
Expand Down Expand Up @@ -347,17 +348,21 @@ func extractdocumentData(rv reflect.Value, rt reflect.Type) documentData {
continue
}

switch newAssociation(rv, i).Type() {
case BelongsTo:
data.belongsTo = append(data.belongsTo, name)
case HasOne:
data.hasOne = append(data.hasOne, name)
case HasMany:
data.hasMany = append(data.hasMany, name)
if !skipAssoc {
switch extractAssociationData(rt, i).typ {
case BelongsTo:
data.belongsTo = append(data.belongsTo, name)
case HasOne:
data.hasOne = append(data.hasOne, name)
case HasMany:
data.hasMany = append(data.hasMany, name)
}
}
}

documentDataCache.Store(rt, data)
if !skipAssoc {
documentDataCache.Store(rt, data)
}

return data
}
Expand Down
4 changes: 2 additions & 2 deletions rel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type User struct {
ID int
Name string
Age int
Transactions []Transaction `references:"ID" foreign_key:"BuyerID"`
Transactions []Transaction `ref:"id" fk:"user_id"`
Address Address
CreatedAt time.Time
UpdatedAt time.Time
Expand All @@ -21,7 +21,7 @@ type Transaction struct {
Item string
Status Status
BuyerID int `db:"user_id"`
Buyer User `references:"BuyerID" foreign_key:"ID"`
Buyer User `ref:"user_id" fk:"id"`
}

type Address struct {
Expand Down

0 comments on commit a1c8912

Please sign in to comment.