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  }