github.com/dolanor/pop@v4.13.0+incompatible/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/columns" 10 "github.com/gobuffalo/pop/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 := defaults.String(tags.Find("fk_id").Value, fmt.Sprintf("%s%s", p.field.Name, "ID")) 35 36 // belongs_to requires an holding field for the foreign model ID. 37 if _, found := p.modelType.FieldByName(ownerIDField); !found { 38 return nil, fmt.Errorf("there is no '%s' defined in model '%s'", ownerIDField, p.modelType.Name()) 39 } 40 41 // If ownerIDField is nil, this association will be skipped. 42 var skipped bool 43 f := p.modelValue.FieldByName(ownerIDField) 44 if fieldIsNil(f) || IsZeroOfUnderlyingType(f.Interface()) { 45 skipped = true 46 } 47 // associated model 48 ownerPk := "id" 49 if primaryIDField != "ID" { 50 ownerModel := reflect.Indirect(ownerVal) 51 ownerPrimaryField, found := ownerModel.Type().FieldByName(primaryIDField) 52 if !found { 53 return nil, fmt.Errorf("there is no primary field '%s' defined in model '%s'", primaryIDField, ownerModel.Type()) 54 } 55 ownerPTags := columns.TagsFor(ownerPrimaryField) 56 ownerPk = defaults.String(ownerPTags.Find("db").Value, flect.Underscore(ownerPrimaryField.Name)) 57 } 58 59 return &belongsToAssociation{ 60 ownerModel: ownerVal, 61 ownerType: ownerVal.Type(), 62 ownerID: f, 63 primaryID: primaryIDField, 64 ownedModel: p.model, 65 associationSkipable: &associationSkipable{ 66 skipped: skipped, 67 }, 68 associationComposite: &associationComposite{innerAssociations: p.innerAssociations}, 69 primaryTableID: ownerPk, 70 }, nil 71 } 72 73 func (b *belongsToAssociation) Kind() reflect.Kind { 74 if b.ownerType.Kind() == reflect.Ptr { 75 return b.ownerType.Elem().Kind() 76 } 77 return b.ownerType.Kind() 78 } 79 80 func (b *belongsToAssociation) Interface() interface{} { 81 if b.ownerModel.Kind() == reflect.Ptr { 82 val := reflect.New(b.ownerType.Elem()) 83 b.ownerModel.Set(val) 84 return b.ownerModel.Interface() 85 } 86 return b.ownerModel.Addr().Interface() 87 } 88 89 // Constraint returns the content for a where clause, and the args 90 // needed to execute it. 91 func (b *belongsToAssociation) Constraint() (string, []interface{}) { 92 return fmt.Sprintf("%s = ?", b.primaryTableID), []interface{}{b.ownerID.Interface()} 93 } 94 95 func (b *belongsToAssociation) BeforeInterface() interface{} { 96 // if the owner field is set, don't try to create the association to prevent conflicts. 97 if !b.skipped { 98 return nil 99 } 100 101 m := b.ownerModel 102 if m.Kind() == reflect.Ptr && !m.IsNil() { 103 m = b.ownerModel.Elem() 104 } 105 106 if IsZeroOfUnderlyingType(m.Interface()) { 107 return nil 108 } 109 110 return m.Addr().Interface() 111 } 112 113 func (b *belongsToAssociation) BeforeSetup() error { 114 ownerID := reflect.Indirect(reflect.ValueOf(b.ownerModel.Interface())).FieldByName("ID") 115 if b.ownerID.CanSet() { 116 if n := nulls.New(b.ownerID.Interface()); n != nil { 117 b.ownerID.Set(reflect.ValueOf(n.Parse(ownerID.Interface()))) 118 } else if b.ownerID.Kind() == reflect.Ptr { 119 b.ownerID.Set(ownerID.Addr()) 120 } else { 121 b.ownerID.Set(ownerID) 122 } 123 return nil 124 } 125 return fmt.Errorf("could not set '%s' to '%s'", ownerID, b.ownerID) 126 }