github.com/rjgonzale/pop/v5@v5.1.3-dev/associations/belongs_to_association.go (about) 1 package associations 2 3 import ( 4 "fmt" 5 "reflect" 6 7 "github.com/gobuffalo/flect" 8 "github.com/gobuffalo/nulls" 9 "github.com/gobuffalo/pop/v5/columns" 10 "github.com/gobuffalo/pop/v5/internal/defaults" 11 ) 12 13 // belongsToAssociation is the implementation for the belongs_to association type in a model. 14 type belongsToAssociation struct { 15 ownerModel reflect.Value 16 ownerType reflect.Type 17 ownerID reflect.Value 18 primaryID string 19 ownedModel interface{} 20 *associationSkipable 21 *associationComposite 22 23 primaryTableID string 24 } 25 26 func init() { 27 associationBuilders["belongs_to"] = belongsToAssociationBuilder 28 } 29 30 func belongsToAssociationBuilder(p associationParams) (Association, error) { 31 ownerVal := p.modelValue.FieldByName(p.field.Name) 32 tags := p.popTags 33 primaryIDField := defaults.String(tags.Find("primary_id").Value, "ID") 34 ownerIDField := fmt.Sprintf("%s%s", p.field.Name, "ID") 35 36 if tags.Find("fk_id").Value != "" { 37 dbTag := tags.Find("fk_id").Value 38 if _, found := p.modelType.FieldByName(dbTag); !found { 39 t := p.modelValue.Type() 40 for i := 0; i < t.NumField(); i++ { 41 f := t.Field(i) 42 if f.Tag.Get("db") == dbTag { 43 ownerIDField = f.Name 44 break 45 } 46 } 47 } else { 48 ownerIDField = dbTag 49 } 50 } 51 52 // belongs_to requires an holding field for the foreign model ID. 53 if _, found := p.modelType.FieldByName(ownerIDField); !found { 54 return nil, fmt.Errorf("there is no '%s' defined in model '%s'", ownerIDField, p.modelType.Name()) 55 } 56 57 // If ownerIDField is nil, this association will be skipped. 58 var skipped bool 59 f := p.modelValue.FieldByName(ownerIDField) 60 if fieldIsNil(f) || IsZeroOfUnderlyingType(f.Interface()) { 61 skipped = true 62 } 63 // associated model 64 ownerPk := "id" 65 if primaryIDField != "ID" { 66 ownerModel := reflect.Indirect(ownerVal) 67 ownerPrimaryField, found := ownerModel.Type().FieldByName(primaryIDField) 68 if !found { 69 return nil, fmt.Errorf("there is no primary field '%s' defined in model '%s'", primaryIDField, ownerModel.Type()) 70 } 71 ownerPTags := columns.TagsFor(ownerPrimaryField) 72 ownerPk = defaults.String(ownerPTags.Find("db").Value, flect.Underscore(ownerPrimaryField.Name)) 73 } 74 75 return &belongsToAssociation{ 76 ownerModel: ownerVal, 77 ownerType: ownerVal.Type(), 78 ownerID: f, 79 primaryID: primaryIDField, 80 ownedModel: p.model, 81 associationSkipable: &associationSkipable{ 82 skipped: skipped, 83 }, 84 associationComposite: &associationComposite{innerAssociations: p.innerAssociations}, 85 primaryTableID: ownerPk, 86 }, nil 87 } 88 89 func (b *belongsToAssociation) Kind() reflect.Kind { 90 if b.ownerType.Kind() == reflect.Ptr { 91 return b.ownerType.Elem().Kind() 92 } 93 return b.ownerType.Kind() 94 } 95 96 func (b *belongsToAssociation) Interface() interface{} { 97 if b.ownerModel.Kind() == reflect.Ptr { 98 val := reflect.New(b.ownerType.Elem()) 99 b.ownerModel.Set(val) 100 return b.ownerModel.Interface() 101 } 102 return b.ownerModel.Addr().Interface() 103 } 104 105 // Constraint returns the content for a where clause, and the args 106 // needed to execute it. 107 func (b *belongsToAssociation) Constraint() (string, []interface{}) { 108 return fmt.Sprintf("%s = ?", b.primaryTableID), []interface{}{b.ownerID.Interface()} 109 } 110 111 func (b *belongsToAssociation) BeforeInterface() interface{} { 112 // if the owner field is set, don't try to create the association to prevent conflicts. 113 if !b.skipped { 114 return nil 115 } 116 117 m := b.ownerModel 118 if m.Kind() == reflect.Ptr && !m.IsNil() { 119 m = b.ownerModel.Elem() 120 } 121 122 if IsZeroOfUnderlyingType(m.Interface()) { 123 return nil 124 } 125 126 return m.Addr().Interface() 127 } 128 129 func (b *belongsToAssociation) BeforeSetup() error { 130 ownerID := reflect.Indirect(reflect.ValueOf(b.ownerModel.Interface())).FieldByName("ID") 131 if b.ownerID.CanSet() { 132 if n := nulls.New(b.ownerID.Interface()); n != nil { 133 b.ownerID.Set(reflect.ValueOf(n.Parse(ownerID.Interface()))) 134 } else if b.ownerID.Kind() == reflect.Ptr { 135 b.ownerID.Set(ownerID.Addr()) 136 } else { 137 b.ownerID.Set(ownerID) 138 } 139 return nil 140 } 141 return fmt.Errorf("could not set '%s' to '%s'", ownerID, b.ownerID) 142 }