github.com/tufanbarisyildirim/pop@v4.13.1+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  }