forked from gobuffalo/pop
-
Notifications
You must be signed in to change notification settings - Fork 0
/
associations_for_struct.go
132 lines (111 loc) · 3.68 KB
/
associations_for_struct.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package associations
import (
"fmt"
"reflect"
"regexp"
"strings"
"github.com/gobuffalo/pop/columns"
"github.com/markbates/oncer"
)
// If a field match with the regexp, it will be considered as a valid field definition.
// e.g: "MyField" => valid.
// e.g: "MyField.NestedField" => valid.
// e.g: "MyField." => not valid.
// e.g: "MyField.*" => not valid for now.
var validAssociationExpRegexp = regexp.MustCompile(`^(([a-zA-Z0-9]*)(\.[a-zA-Z0-9]+)?)+$`)
// associationBuilders is a map that helps to aisle associations finding process
// with the associations implementation. Every association MUST register its builder
// in this map using its init() method. see ./has_many_association.go as a guide.
var associationBuilders = map[string]associationBuilder{}
// AssociationsForStruct returns all associations for
// the struct specified. It takes into account tags
// associations like has_many, belongs_to, has_one.
// it throws an error when it finds a field that does
// not exist for a model.
//
// Deprecated: use ForStruct instead.
func AssociationsForStruct(s interface{}, fields ...string) (Associations, error) {
oncer.Deprecate(0, "associations.AssociationsForStruct", "Use associations.ForStruct instead.")
return ForStruct(s, fields...)
}
// ForStruct returns all associations for
// the struct specified. It takes into account tags
// associations like has_many, belongs_to, has_one.
// it throws an error when it finds a field that does
// not exist for a model.
func ForStruct(s interface{}, fields ...string) (Associations, error) {
associations := Associations{}
innerAssociations := InnerAssociations{}
t, v := getModelDefinition(s)
fields = trimFields(fields)
// validate if fields contains a non existing field in struct.
// and vefiry is it has inner associations.
for i := range fields {
var innerField, field string
if !validAssociationExpRegexp.MatchString(fields[i]) {
return associations, fmt.Errorf("association '%s' does not match the format %s", fields[i], "'<field>' or '<field>.<nested-field>'")
}
if strings.Contains(fields[i], ".") {
field = fields[i][:strings.Index(fields[i], ".")]
innerField = fields[i][strings.Index(fields[i], ".")+1:]
fields[i] = field
}
if _, ok := t.FieldByName(fields[i]); !ok {
return associations, fmt.Errorf("field %s does not exist in model %s", fields[i], t.Name())
}
if innerField != "" {
innerAssociations = append(innerAssociations, InnerAssociation{fields[i], innerField})
}
}
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
// ignores those fields not included in fields list.
if len(fields) > 0 && fieldIgnoredIn(fields, f.Name) {
continue
}
tags := columns.TagsFor(f)
for name, builder := range associationBuilders {
tag := tags.Find(name)
if !tag.Empty() {
params := associationParams{
field: f,
model: s,
modelType: t,
modelValue: v,
popTags: tags,
innerAssociations: innerAssociations,
}
a, err := builder(params)
if err != nil {
return associations, err
}
associations = append(associations, a)
break
}
}
}
return associations, nil
}
func getModelDefinition(s interface{}) (reflect.Type, reflect.Value) {
v := reflect.ValueOf(s)
v = reflect.Indirect(v)
t := v.Type()
return t, v
}
func trimFields(fields []string) []string {
trimFields := []string{}
for _, f := range fields {
if strings.TrimSpace(f) != "" {
trimFields = append(trimFields, strings.TrimSpace(f))
}
}
return trimFields
}
func fieldIgnoredIn(fields []string, field string) bool {
for _, f := range fields {
if f == field {
return false
}
}
return true
}