Skip to content

Commit fd34a6f

Browse files
optimanstephenafamo
authored andcommitted
support loading model relationships when binding to a struct with embedded model
1 parent e9d8e58 commit fd34a6f

File tree

4 files changed

+152
-6
lines changed

4 files changed

+152
-6
lines changed

queries/eager_load.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,3 +309,101 @@ var (
309309
applicatorSentinel Applicator
310310
applicatorSentinelVal = reflect.ValueOf(&applicatorSentinel).Elem()
311311
)
312+
313+
// SetFromEmbeddedStruct sets `to` value from embedded struct
314+
// of the `from` struct or slice of structs.
315+
// Expects `to` and `from` to be a pair of pre-allocated **struct or *[]*struct.
316+
// Returns false if types do not match.
317+
func SetFromEmbeddedStruct(to interface{}, from interface{}) bool {
318+
toPtrVal := reflect.ValueOf(to)
319+
fromPtrVal := reflect.ValueOf(from)
320+
if toPtrVal.Kind() != reflect.Ptr || fromPtrVal.Kind() != reflect.Ptr {
321+
return false
322+
}
323+
toStructTyp, ok := singularStructType(to)
324+
if !ok {
325+
return false
326+
}
327+
fromStructTyp, ok := singularStructType(from)
328+
if !ok {
329+
return false
330+
}
331+
fieldNum, ok := embeddedStructFieldNum(fromStructTyp, toStructTyp)
332+
if !ok {
333+
return false
334+
}
335+
toVal := toPtrVal.Elem()
336+
if toVal.Kind() == reflect.Interface {
337+
toVal = reflect.ValueOf(toVal.Interface())
338+
}
339+
fromVal := fromPtrVal.Elem()
340+
if fromVal.Kind() == reflect.Interface {
341+
fromVal = reflect.ValueOf(fromVal.Interface())
342+
}
343+
344+
if toVal.Kind() == reflect.Ptr && toVal.Elem().Kind() == reflect.Struct &&
345+
fromVal.Kind() == reflect.Ptr && fromVal.Elem().Kind() == reflect.Struct {
346+
toVal.Set(fromVal.Elem().Field(fieldNum).Addr())
347+
348+
return true
349+
}
350+
351+
toKind := toPtrVal.Type().Elem().Kind()
352+
fromKind := fromPtrVal.Type().Elem().Kind()
353+
354+
if toKind == reflect.Slice && fromKind == reflect.Slice {
355+
toSlice := reflect.MakeSlice(toVal.Type(), fromVal.Len(), fromVal.Len())
356+
for i := 0; i < fromVal.Len(); i++ {
357+
toSlice.Index(i).Set(fromVal.Index(i).Elem().Field(fieldNum).Addr())
358+
}
359+
toVal.Set(toSlice)
360+
361+
return true
362+
}
363+
364+
return false
365+
}
366+
367+
// singularStructType returns singular struct type
368+
// from **struct or *[]*struct types.
369+
// Used for Load* methods during binding.
370+
func singularStructType(obj interface{}) (reflect.Type, bool) {
371+
val := reflect.Indirect(reflect.ValueOf(obj))
372+
if val.Kind() == reflect.Interface {
373+
val = reflect.ValueOf(val.Interface())
374+
}
375+
typ := val.Type()
376+
inSlice := false
377+
SWITCH:
378+
switch typ.Kind() {
379+
case reflect.Ptr:
380+
typ = typ.Elem()
381+
382+
goto SWITCH
383+
case reflect.Slice:
384+
if inSlice {
385+
// Slices inside other slices are not supported
386+
return nil, false
387+
}
388+
inSlice = true
389+
typ = typ.Elem()
390+
391+
goto SWITCH
392+
case reflect.Struct:
393+
return typ, true
394+
default:
395+
return nil, false
396+
}
397+
}
398+
399+
// embeddedStructFieldNum returns the index of embedded struct field of type `emb` inside `obj` struct.
400+
func embeddedStructFieldNum(obj reflect.Type, emb reflect.Type) (int, bool) {
401+
for i := 0; i < obj.NumField(); i++ {
402+
v := obj.Field(i)
403+
if v.Type.Kind() == reflect.Struct &&
404+
v.Anonymous && v.Type == emb {
405+
return i, true
406+
}
407+
}
408+
return 0, false
409+
}

templates/main/07_relationship_to_one_eager.go.tpl

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,25 @@ func ({{$ltable.DownSingular}}L) Load{{$rel.Foreign}}({{if $.NoContext}}e boil.E
1616
var object *{{$ltable.UpSingular}}
1717

1818
if singular {
19-
object = {{$arg}}.(*{{$ltable.UpSingular}})
19+
var ok bool
20+
object, ok = {{$arg}}.(*{{$ltable.UpSingular}})
21+
if !ok {
22+
object = new({{$ltable.UpSingular}})
23+
ok = queries.SetFromEmbeddedStruct(&object, &{{$arg}})
24+
if !ok {
25+
return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, {{$arg}}))
26+
}
27+
}
2028
} else {
21-
slice = *{{$arg}}.(*[]*{{$ltable.UpSingular}})
29+
s, ok := {{$arg}}.(*[]*{{$ltable.UpSingular}})
30+
if ok {
31+
slice = *s
32+
} else {
33+
ok = queries.SetFromEmbeddedStruct(&slice, {{$arg}})
34+
if !ok {
35+
return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, {{$arg}}))
36+
}
37+
}
2238
}
2339

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

templates/main/08_relationship_one_to_one_eager.go.tpl

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,25 @@ func ({{$ltable.DownSingular}}L) Load{{$relAlias.Local}}({{if $.NoContext}}e boi
1616
var object *{{$ltable.UpSingular}}
1717

1818
if singular {
19-
object = {{$arg}}.(*{{$ltable.UpSingular}})
19+
var ok bool
20+
object, ok = {{$arg}}.(*{{$ltable.UpSingular}})
21+
if !ok {
22+
object = new({{$ltable.UpSingular}})
23+
ok = queries.SetFromEmbeddedStruct(&object, &{{$arg}})
24+
if !ok {
25+
return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, {{$arg}}))
26+
}
27+
}
2028
} else {
21-
slice = *{{$arg}}.(*[]*{{$ltable.UpSingular}})
29+
s, ok := {{$arg}}.(*[]*{{$ltable.UpSingular}})
30+
if ok {
31+
slice = *s
32+
} else {
33+
ok = queries.SetFromEmbeddedStruct(&slice, {{$arg}})
34+
if !ok {
35+
return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, {{$arg}}))
36+
}
37+
}
2238
}
2339

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

templates/main/09_relationship_to_many_eager.go.tpl

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,25 @@ func ({{$ltable.DownSingular}}L) Load{{$relAlias.Local}}({{if $.NoContext}}e boi
1717
var object *{{$ltable.UpSingular}}
1818

1919
if singular {
20-
object = {{$arg}}.(*{{$ltable.UpSingular}})
20+
var ok bool
21+
object, ok = {{$arg}}.(*{{$ltable.UpSingular}})
22+
if !ok {
23+
object = new({{$ltable.UpSingular}})
24+
ok = queries.SetFromEmbeddedStruct(&object, &{{$arg}})
25+
if !ok {
26+
return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, {{$arg}}))
27+
}
28+
}
2129
} else {
22-
slice = *{{$arg}}.(*[]*{{$ltable.UpSingular}})
30+
s, ok := {{$arg}}.(*[]*{{$ltable.UpSingular}})
31+
if ok {
32+
slice = *s
33+
} else {
34+
ok = queries.SetFromEmbeddedStruct(&slice, {{$arg}})
35+
if !ok {
36+
return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, {{$arg}}))
37+
}
38+
}
2339
}
2440

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

0 commit comments

Comments
 (0)