github.com/tufanbarisyildirim/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 }