github.com/reggieriser/pop@v4.13.1+incompatible/model.go (about) 1 package pop 2 3 import ( 4 "fmt" 5 "reflect" 6 "sync" 7 "time" 8 9 "github.com/gobuffalo/flect" 10 nflect "github.com/gobuffalo/flect/name" 11 "github.com/gofrs/uuid" 12 ) 13 14 var nowFunc = time.Now 15 16 var tableMap = map[string]string{} 17 var tableMapMu = sync.RWMutex{} 18 19 // Value is the contents of a `Model`. 20 type Value interface{} 21 22 type modelIterable func(*Model) error 23 24 // Model is used throughout Pop to wrap the end user interface 25 // that is passed in to many functions. 26 type Model struct { 27 Value 28 tableName string 29 As string 30 } 31 32 // ID returns the ID of the Model. All models must have an `ID` field this is 33 // of type `int`,`int64` or of type `uuid.UUID`. 34 func (m *Model) ID() interface{} { 35 fbn, err := m.fieldByName("ID") 36 if err != nil { 37 return 0 38 } 39 if m.PrimaryKeyType() == "UUID" { 40 return fbn.Interface().(uuid.UUID).String() 41 } 42 return fbn.Interface() 43 } 44 45 // IDField returns the name of the DB field used for the ID. 46 // By default, it will return "id". 47 func (m *Model) IDField() string { 48 field, ok := reflect.TypeOf(m.Value).Elem().FieldByName("ID") 49 if !ok { 50 return "id" 51 } 52 dbField := field.Tag.Get("db") 53 if dbField == "" { 54 return "id" 55 } 56 return dbField 57 } 58 59 // PrimaryKeyType gives the primary key type of the `Model`. 60 func (m *Model) PrimaryKeyType() string { 61 fbn, err := m.fieldByName("ID") 62 if err != nil { 63 return "int" 64 } 65 return fbn.Type().Name() 66 } 67 68 // TableNameAble interface allows for the customize table mapping 69 // between a name and the database. For example the value 70 // `User{}` will automatically map to "users". Implementing `TableNameAble` 71 // would allow this to change to be changed to whatever you would like. 72 type TableNameAble interface { 73 TableName() string 74 } 75 76 // TableName returns the corresponding name of the underlying database table 77 // for a given `Model`. See also `TableNameAble` to change the default name of the table. 78 func (m *Model) TableName() string { 79 if s, ok := m.Value.(string); ok { 80 return s 81 } 82 if n, ok := m.Value.(TableNameAble); ok { 83 return n.TableName() 84 } 85 86 if m.tableName != "" { 87 return m.tableName 88 } 89 90 t := reflect.TypeOf(m.Value) 91 name, cacheKey := m.typeName(t) 92 93 defer tableMapMu.Unlock() 94 tableMapMu.Lock() 95 96 if tableMap[cacheKey] == "" { 97 m.tableName = nflect.Tableize(name) 98 tableMap[cacheKey] = m.tableName 99 } 100 return tableMap[cacheKey] 101 } 102 103 func (m *Model) cacheKey(t reflect.Type) string { 104 return t.PkgPath() + "." + t.Name() 105 } 106 107 func (m *Model) typeName(t reflect.Type) (name, cacheKey string) { 108 if t.Kind() == reflect.Ptr { 109 t = t.Elem() 110 } 111 switch t.Kind() { 112 case reflect.Slice, reflect.Array: 113 el := t.Elem() 114 if el.Kind() == reflect.Ptr { 115 el = el.Elem() 116 } 117 118 // validates if the elem of slice or array implements TableNameAble interface. 119 tableNameAble := (*TableNameAble)(nil) 120 if el.Implements(reflect.TypeOf(tableNameAble).Elem()) { 121 v := reflect.New(el) 122 out := v.MethodByName("TableName").Call([]reflect.Value{}) 123 name := out[0].String() 124 if tableMap[m.cacheKey(el)] == "" { 125 tableMap[m.cacheKey(el)] = name 126 } 127 } 128 129 return el.Name(), m.cacheKey(el) 130 default: 131 return t.Name(), m.cacheKey(t) 132 } 133 } 134 135 func (m *Model) fieldByName(s string) (reflect.Value, error) { 136 el := reflect.ValueOf(m.Value).Elem() 137 fbn := el.FieldByName(s) 138 if !fbn.IsValid() { 139 return fbn, fmt.Errorf("model does not have a field named %s", s) 140 } 141 return fbn, nil 142 } 143 144 func (m *Model) associationName() string { 145 tn := flect.Singularize(m.TableName()) 146 return fmt.Sprintf("%s_id", tn) 147 } 148 149 func (m *Model) setID(i interface{}) { 150 fbn, err := m.fieldByName("ID") 151 if err == nil { 152 v := reflect.ValueOf(i) 153 switch fbn.Kind() { 154 case reflect.Int, reflect.Int64: 155 fbn.SetInt(v.Int()) 156 default: 157 fbn.Set(reflect.ValueOf(i)) 158 } 159 } 160 } 161 162 func (m *Model) touchCreatedAt() { 163 fbn, err := m.fieldByName("CreatedAt") 164 if err == nil { 165 now := nowFunc().Truncate(time.Microsecond) 166 v := fbn.Interface() 167 if !IsZeroOfUnderlyingType(v) { 168 // Do not override already set CreatedAt 169 return 170 } 171 switch v.(type) { 172 case int, int64: 173 fbn.SetInt(now.Unix()) 174 default: 175 fbn.Set(reflect.ValueOf(now)) 176 } 177 } 178 } 179 180 func (m *Model) touchUpdatedAt() { 181 fbn, err := m.fieldByName("UpdatedAt") 182 if err == nil { 183 now := nowFunc().Truncate(time.Microsecond) 184 v := fbn.Interface() 185 switch v.(type) { 186 case int, int64: 187 fbn.SetInt(now.Unix()) 188 default: 189 fbn.Set(reflect.ValueOf(now)) 190 } 191 } 192 } 193 194 func (m *Model) whereID() string { 195 return fmt.Sprintf("%s.%s = ?", m.TableName(), m.IDField()) 196 } 197 198 func (m *Model) whereNamedID() string { 199 return fmt.Sprintf("%s.%s = :id", m.TableName(), m.IDField()) 200 } 201 202 func (m *Model) isSlice() bool { 203 v := reflect.Indirect(reflect.ValueOf(m.Value)) 204 return v.Kind() == reflect.Slice || v.Kind() == reflect.Array 205 } 206 207 func (m *Model) iterate(fn modelIterable) error { 208 if m.isSlice() { 209 v := reflect.Indirect(reflect.ValueOf(m.Value)) 210 for i := 0; i < v.Len(); i++ { 211 val := v.Index(i) 212 newModel := &Model{Value: val.Addr().Interface()} 213 err := fn(newModel) 214 215 if err != nil { 216 return err 217 } 218 } 219 return nil 220 } 221 222 return fn(m) 223 }