github.com/royge/pop@v4.13.1+incompatible/associations/has_many_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/jmoiron/sqlx"
    10  )
    11  
    12  // hasManyAssociation is the implementation for the has_many
    13  // association type in a model.
    14  type hasManyAssociation struct {
    15  	tableName string
    16  	field     reflect.StructField
    17  	value     reflect.Value
    18  	ownerName string
    19  	ownerID   interface{}
    20  	owner     interface{}
    21  	fkID      string
    22  	orderBy   string
    23  	*associationSkipable
    24  	*associationComposite
    25  }
    26  
    27  func init() {
    28  	associationBuilders["has_many"] = hasManyAssociationBuilder
    29  }
    30  
    31  func hasManyAssociationBuilder(p associationParams) (Association, error) {
    32  	// Validates if ownerID is nil, this association will be skipped.
    33  	var skipped bool
    34  	ownerID := p.modelValue.FieldByName("ID")
    35  	if fieldIsNil(ownerID) {
    36  		skipped = true
    37  	}
    38  
    39  	return &hasManyAssociation{
    40  		owner:     p.model,
    41  		tableName: p.popTags.Find("has_many").Value,
    42  		field:     p.field,
    43  		value:     p.modelValue.FieldByName(p.field.Name),
    44  		ownerName: p.modelType.Name(),
    45  		ownerID:   ownerID.Interface(),
    46  		fkID:      p.popTags.Find("fk_id").Value,
    47  		orderBy:   p.popTags.Find("order_by").Value,
    48  		associationSkipable: &associationSkipable{
    49  			skipped: skipped,
    50  		},
    51  		associationComposite: &associationComposite{innerAssociations: p.innerAssociations},
    52  	}, nil
    53  }
    54  
    55  func (a *hasManyAssociation) Kind() reflect.Kind {
    56  	if a.field.Type.Kind() == reflect.Ptr {
    57  		return a.field.Type.Elem().Kind()
    58  	}
    59  	return a.field.Type.Kind()
    60  }
    61  
    62  func (a *hasManyAssociation) Interface() interface{} {
    63  	if a.value.Kind() == reflect.Ptr {
    64  		val := reflect.New(a.field.Type.Elem())
    65  		a.value.Set(val)
    66  		return a.value.Interface()
    67  	}
    68  
    69  	// This piece of code clears a slice in case it is filled with elements.
    70  	if a.value.Kind() == reflect.Slice || a.value.Kind() == reflect.Array {
    71  		valPointer := a.value.Addr()
    72  		valPointer.Elem().Set(reflect.MakeSlice(valPointer.Type().Elem(), 0, valPointer.Elem().Cap()))
    73  		return valPointer.Interface()
    74  	}
    75  
    76  	return a.value.Addr().Interface()
    77  }
    78  
    79  // Constraint returns the content for a where clause, and the args
    80  // needed to execute it.
    81  func (a *hasManyAssociation) Constraint() (string, []interface{}) {
    82  	tn := flect.Underscore(a.ownerName)
    83  	condition := fmt.Sprintf("%s_id = ?", tn)
    84  	if a.fkID != "" {
    85  		condition = fmt.Sprintf("%s = ?", a.fkID)
    86  	}
    87  	return condition, []interface{}{a.ownerID}
    88  }
    89  
    90  func (a *hasManyAssociation) OrderBy() string {
    91  	return a.orderBy
    92  }
    93  
    94  func (a *hasManyAssociation) AfterInterface() interface{} {
    95  	if a.value.Kind() == reflect.Ptr {
    96  		return a.value.Interface()
    97  	}
    98  	return a.value.Addr().Interface()
    99  }
   100  
   101  func (a *hasManyAssociation) AfterSetup() error {
   102  	ownerID := reflect.Indirect(reflect.ValueOf(a.owner)).FieldByName("ID").Interface()
   103  
   104  	v := a.value
   105  	if v.Kind() == reflect.Ptr {
   106  		v = v.Elem()
   107  	}
   108  
   109  	for i := 0; i < v.Len(); i++ {
   110  		fval := v.Index(i).FieldByName(a.ownerName + "ID")
   111  		if fval.CanSet() {
   112  			if n := nulls.New(fval.Interface()); n != nil {
   113  				fval.Set(reflect.ValueOf(n.Parse(ownerID)))
   114  			} else {
   115  				fval.Set(reflect.ValueOf(ownerID))
   116  			}
   117  		} else {
   118  			return fmt.Errorf("could not set field '%s' in table '%s' to value '%s' for 'has_many' relation", a.ownerName+"ID", a.tableName, ownerID)
   119  		}
   120  	}
   121  	return nil
   122  }
   123  
   124  func (a *hasManyAssociation) AfterProcess() AssociationStatement {
   125  	v := a.value
   126  	if v.Kind() == reflect.Ptr {
   127  		v = v.Elem()
   128  	}
   129  
   130  	belongingIDFieldName := "ID"
   131  
   132  	ownerIDFieldName := "ID"
   133  	ownerID := reflect.Indirect(reflect.ValueOf(a.owner)).FieldByName(ownerIDFieldName).Interface()
   134  
   135  	var ids []interface{}
   136  
   137  	for i := 0; i < v.Len(); i++ {
   138  		id := v.Index(i).FieldByName(belongingIDFieldName).Interface()
   139  		if !IsZeroOfUnderlyingType(id) {
   140  			ids = append(ids, id)
   141  		}
   142  	}
   143  	if len(ids) == 0 {
   144  		return AssociationStatement{
   145  			Statement: "",
   146  			Args:      []interface{}{},
   147  		}
   148  	}
   149  
   150  	fk := a.fkID
   151  	if fk == "" {
   152  		fk = flect.Underscore(a.ownerName) + "_id"
   153  	}
   154  
   155  	// This will be used to update all of our owned models' foreign keys to our ID.
   156  	ret := fmt.Sprintf("UPDATE %s SET %s = ? WHERE %s in (?);", a.tableName, fk, belongingIDFieldName)
   157  
   158  	update, args, err := sqlx.In(ret, ownerID, ids)
   159  	if err != nil {
   160  		return AssociationStatement{
   161  			Statement: "",
   162  			Args:      []interface{}{},
   163  		}
   164  	}
   165  
   166  	return AssociationStatement{
   167  		Statement: update,
   168  		Args:      args,
   169  	}
   170  }