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