Skip to content

Commit

Permalink
support loading model relationships when binding to a struct with emb…
Browse files Browse the repository at this point in the history
…edded model
  • Loading branch information
optiman authored and stephenafamo committed Jul 24, 2022
1 parent e9d8e58 commit fd34a6f
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 6 deletions.
98 changes: 98 additions & 0 deletions queries/eager_load.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,101 @@ var (
applicatorSentinel Applicator
applicatorSentinelVal = reflect.ValueOf(&applicatorSentinel).Elem()
)

// SetFromEmbeddedStruct sets `to` value from embedded struct
// of the `from` struct or slice of structs.
// Expects `to` and `from` to be a pair of pre-allocated **struct or *[]*struct.
// Returns false if types do not match.
func SetFromEmbeddedStruct(to interface{}, from interface{}) bool {
toPtrVal := reflect.ValueOf(to)
fromPtrVal := reflect.ValueOf(from)
if toPtrVal.Kind() != reflect.Ptr || fromPtrVal.Kind() != reflect.Ptr {
return false
}
toStructTyp, ok := singularStructType(to)
if !ok {
return false
}
fromStructTyp, ok := singularStructType(from)
if !ok {
return false
}
fieldNum, ok := embeddedStructFieldNum(fromStructTyp, toStructTyp)
if !ok {
return false
}
toVal := toPtrVal.Elem()
if toVal.Kind() == reflect.Interface {
toVal = reflect.ValueOf(toVal.Interface())
}
fromVal := fromPtrVal.Elem()
if fromVal.Kind() == reflect.Interface {
fromVal = reflect.ValueOf(fromVal.Interface())
}

if toVal.Kind() == reflect.Ptr && toVal.Elem().Kind() == reflect.Struct &&
fromVal.Kind() == reflect.Ptr && fromVal.Elem().Kind() == reflect.Struct {
toVal.Set(fromVal.Elem().Field(fieldNum).Addr())

return true
}

toKind := toPtrVal.Type().Elem().Kind()
fromKind := fromPtrVal.Type().Elem().Kind()

if toKind == reflect.Slice && fromKind == reflect.Slice {
toSlice := reflect.MakeSlice(toVal.Type(), fromVal.Len(), fromVal.Len())
for i := 0; i < fromVal.Len(); i++ {
toSlice.Index(i).Set(fromVal.Index(i).Elem().Field(fieldNum).Addr())
}
toVal.Set(toSlice)

return true
}

return false
}

// singularStructType returns singular struct type
// from **struct or *[]*struct types.
// Used for Load* methods during binding.
func singularStructType(obj interface{}) (reflect.Type, bool) {
val := reflect.Indirect(reflect.ValueOf(obj))
if val.Kind() == reflect.Interface {
val = reflect.ValueOf(val.Interface())
}
typ := val.Type()
inSlice := false
SWITCH:
switch typ.Kind() {
case reflect.Ptr:
typ = typ.Elem()

goto SWITCH
case reflect.Slice:
if inSlice {
// Slices inside other slices are not supported
return nil, false
}
inSlice = true
typ = typ.Elem()

goto SWITCH
case reflect.Struct:
return typ, true
default:
return nil, false
}
}

// embeddedStructFieldNum returns the index of embedded struct field of type `emb` inside `obj` struct.
func embeddedStructFieldNum(obj reflect.Type, emb reflect.Type) (int, bool) {
for i := 0; i < obj.NumField(); i++ {
v := obj.Field(i)
if v.Type.Kind() == reflect.Struct &&
v.Anonymous && v.Type == emb {
return i, true
}
}
return 0, false
}
20 changes: 18 additions & 2 deletions templates/main/07_relationship_to_one_eager.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,25 @@ func ({{$ltable.DownSingular}}L) Load{{$rel.Foreign}}({{if $.NoContext}}e boil.E
var object *{{$ltable.UpSingular}}

if singular {
object = {{$arg}}.(*{{$ltable.UpSingular}})
var ok bool
object, ok = {{$arg}}.(*{{$ltable.UpSingular}})
if !ok {
object = new({{$ltable.UpSingular}})
ok = queries.SetFromEmbeddedStruct(&object, &{{$arg}})
if !ok {
return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, {{$arg}}))
}
}
} else {
slice = *{{$arg}}.(*[]*{{$ltable.UpSingular}})
s, ok := {{$arg}}.(*[]*{{$ltable.UpSingular}})
if ok {
slice = *s
} else {
ok = queries.SetFromEmbeddedStruct(&slice, {{$arg}})
if !ok {
return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, {{$arg}}))
}
}
}

args := make([]interface{}, 0, 1)
Expand Down
20 changes: 18 additions & 2 deletions templates/main/08_relationship_one_to_one_eager.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,25 @@ func ({{$ltable.DownSingular}}L) Load{{$relAlias.Local}}({{if $.NoContext}}e boi
var object *{{$ltable.UpSingular}}

if singular {
object = {{$arg}}.(*{{$ltable.UpSingular}})
var ok bool
object, ok = {{$arg}}.(*{{$ltable.UpSingular}})
if !ok {
object = new({{$ltable.UpSingular}})
ok = queries.SetFromEmbeddedStruct(&object, &{{$arg}})
if !ok {
return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, {{$arg}}))
}
}
} else {
slice = *{{$arg}}.(*[]*{{$ltable.UpSingular}})
s, ok := {{$arg}}.(*[]*{{$ltable.UpSingular}})
if ok {
slice = *s
} else {
ok = queries.SetFromEmbeddedStruct(&slice, {{$arg}})
if !ok {
return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, {{$arg}}))
}
}
}

args := make([]interface{}, 0, 1)
Expand Down
20 changes: 18 additions & 2 deletions templates/main/09_relationship_to_many_eager.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,25 @@ func ({{$ltable.DownSingular}}L) Load{{$relAlias.Local}}({{if $.NoContext}}e boi
var object *{{$ltable.UpSingular}}

if singular {
object = {{$arg}}.(*{{$ltable.UpSingular}})
var ok bool
object, ok = {{$arg}}.(*{{$ltable.UpSingular}})
if !ok {
object = new({{$ltable.UpSingular}})
ok = queries.SetFromEmbeddedStruct(&object, &{{$arg}})
if !ok {
return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, {{$arg}}))
}
}
} else {
slice = *{{$arg}}.(*[]*{{$ltable.UpSingular}})
s, ok := {{$arg}}.(*[]*{{$ltable.UpSingular}})
if ok {
slice = *s
} else {
ok = queries.SetFromEmbeddedStruct(&slice, {{$arg}})
if !ok {
return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, {{$arg}}))
}
}
}

args := make([]interface{}, 0, 1)
Expand Down

0 comments on commit fd34a6f

Please sign in to comment.