github.com/paweljw/pop/v5@v5.4.6/model.go (about) 1 package pop 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "strings" 8 "time" 9 10 nflect "github.com/gobuffalo/flect/name" 11 12 "github.com/gobuffalo/pop/v5/columns" 13 "github.com/pkg/errors" 14 15 "github.com/gobuffalo/flect" 16 "github.com/gofrs/uuid" 17 ) 18 19 var nowFunc = time.Now 20 21 // Value is the contents of a `Model`. 22 type Value interface{} 23 24 type modelIterable func(*Model) error 25 26 // Model is used throughout Pop to wrap the end user interface 27 // that is passed in to many functions. 28 type Model struct { 29 Value 30 ctx context.Context 31 As string 32 } 33 34 // NewModel returns a new model with the specified value and context. 35 func NewModel(v Value, ctx context.Context) *Model { 36 return &Model{Value: v, ctx: ctx} 37 } 38 39 // ID returns the ID of the Model. All models must have an `ID` field this is 40 // of type `int`,`int64` or of type `uuid.UUID`. 41 func (m *Model) ID() interface{} { 42 fbn, err := m.fieldByName("ID") 43 if err != nil { 44 return nil 45 } 46 if pkt, _ := m.PrimaryKeyType(); pkt == "UUID" { 47 return fbn.Interface().(uuid.UUID).String() 48 } 49 return fbn.Interface() 50 } 51 52 // IDField returns the name of the DB field used for the ID. 53 // By default, it will return "id". 54 func (m *Model) IDField() string { 55 modelType := reflect.TypeOf(m.Value) 56 57 // remove all indirections 58 for modelType.Kind() == reflect.Slice || modelType.Kind() == reflect.Ptr || modelType.Kind() == reflect.Array { 59 modelType = modelType.Elem() 60 } 61 62 if modelType.Kind() == reflect.String { 63 return "id" 64 } 65 66 field, ok := modelType.FieldByName("ID") 67 if !ok { 68 return "id" 69 } 70 dbField := field.Tag.Get("db") 71 if dbField == "" { 72 return "id" 73 } 74 return dbField 75 } 76 77 // PrimaryKeyType gives the primary key type of the `Model`. 78 func (m *Model) PrimaryKeyType() (string, error) { 79 fbn, err := m.fieldByName("ID") 80 if err != nil { 81 return "", errors.Errorf("model %T is missing required field ID", m.Value) 82 } 83 return fbn.Type().Name(), nil 84 } 85 86 // TableNameAble interface allows for the customize table mapping 87 // between a name and the database. For example the value 88 // `User{}` will automatically map to "users". Implementing `TableNameAble` 89 // would allow this to change to be changed to whatever you would like. 90 type TableNameAble interface { 91 TableName() string 92 } 93 94 // TableNameAbleWithContext is equal to TableNameAble but will 95 // be passed the queries' context. Useful in cases where the 96 // table name depends on e.g. 97 type TableNameAbleWithContext interface { 98 TableName(ctx context.Context) string 99 } 100 101 // TableName returns the corresponding name of the underlying database table 102 // for a given `Model`. See also `TableNameAble` to change the default name of the table. 103 func (m *Model) TableName() string { 104 if s, ok := m.Value.(string); ok { 105 return s 106 } 107 108 if n, ok := m.Value.(TableNameAble); ok { 109 return n.TableName() 110 } 111 112 if n, ok := m.Value.(TableNameAbleWithContext); ok { 113 if m.ctx == nil { 114 m.ctx = context.TODO() 115 } 116 return n.TableName(m.ctx) 117 } 118 119 return m.typeName(reflect.TypeOf(m.Value)) 120 } 121 122 func (m *Model) Columns() columns.Columns { 123 return columns.ForStructWithAlias(m.Value, m.TableName(), m.As, m.IDField()) 124 } 125 126 func (m *Model) cacheKey(t reflect.Type) string { 127 return t.PkgPath() + "." + t.Name() 128 } 129 130 func (m *Model) typeName(t reflect.Type) (name string) { 131 if t.Kind() == reflect.Ptr { 132 t = t.Elem() 133 } 134 switch t.Kind() { 135 case reflect.Slice, reflect.Array: 136 el := t.Elem() 137 if el.Kind() == reflect.Ptr { 138 el = el.Elem() 139 } 140 141 // validates if the elem of slice or array implements TableNameAble interface. 142 var tableNameAble *TableNameAble 143 if el.Implements(reflect.TypeOf(tableNameAble).Elem()) { 144 v := reflect.New(el) 145 out := v.MethodByName("TableName").Call([]reflect.Value{}) 146 return out[0].String() 147 } 148 149 // validates if the elem of slice or array implements TableNameAbleWithContext interface. 150 var tableNameAbleWithContext *TableNameAbleWithContext 151 if el.Implements(reflect.TypeOf(tableNameAbleWithContext).Elem()) { 152 v := reflect.New(el) 153 out := v.MethodByName("TableName").Call([]reflect.Value{reflect.ValueOf(m.ctx)}) 154 return out[0].String() 155 156 // We do not want to cache contextualized TableNames because that would break 157 // the contextualization. 158 } 159 return nflect.Tableize(el.Name()) 160 default: 161 return nflect.Tableize(t.Name()) 162 } 163 } 164 165 func (m *Model) fieldByName(s string) (reflect.Value, error) { 166 el := reflect.ValueOf(m.Value).Elem() 167 fbn := el.FieldByName(s) 168 if !fbn.IsValid() { 169 return fbn, fmt.Errorf("model does not have a field named %s", s) 170 } 171 return fbn, nil 172 } 173 174 func (m *Model) associationName() string { 175 tn := flect.Singularize(m.TableName()) 176 return fmt.Sprintf("%s_id", tn) 177 } 178 179 func (m *Model) setID(i interface{}) { 180 fbn, err := m.fieldByName("ID") 181 if err == nil { 182 v := reflect.ValueOf(i) 183 switch fbn.Kind() { 184 case reflect.Int, reflect.Int64: 185 fbn.SetInt(v.Int()) 186 default: 187 fbn.Set(reflect.ValueOf(i)) 188 } 189 } 190 } 191 192 func (m *Model) setCreatedAt(now time.Time) { 193 fbn, err := m.fieldByName("CreatedAt") 194 if err == nil { 195 v := fbn.Interface() 196 if !IsZeroOfUnderlyingType(v) { 197 // Do not override already set CreatedAt 198 return 199 } 200 switch v.(type) { 201 case int, int64: 202 fbn.SetInt(now.Unix()) 203 default: 204 fbn.Set(reflect.ValueOf(now)) 205 } 206 } 207 } 208 209 func (m *Model) setUpdatedAt(now time.Time) { 210 fbn, err := m.fieldByName("UpdatedAt") 211 if err == nil { 212 v := fbn.Interface() 213 switch v.(type) { 214 case int, int64: 215 fbn.SetInt(now.Unix()) 216 default: 217 fbn.Set(reflect.ValueOf(now)) 218 } 219 } 220 } 221 222 func (m *Model) whereID() string { 223 return fmt.Sprintf("%s.%s = ?", m.alias(), m.IDField()) 224 } 225 226 func (m *Model) alias() string { 227 as := m.As 228 if as == "" { 229 as = strings.ReplaceAll(m.TableName(), ".", "_") 230 } 231 return as 232 } 233 234 func (m *Model) whereNamedID() string { 235 return fmt.Sprintf("%s.%s = :%s", m.alias(), m.IDField(), m.IDField()) 236 } 237 238 func (m *Model) isSlice() bool { 239 v := reflect.Indirect(reflect.ValueOf(m.Value)) 240 return v.Kind() == reflect.Slice || v.Kind() == reflect.Array 241 } 242 243 func (m *Model) iterate(fn modelIterable) error { 244 if m.isSlice() { 245 v := reflect.Indirect(reflect.ValueOf(m.Value)) 246 for i := 0; i < v.Len(); i++ { 247 val := v.Index(i) 248 newModel := &Model{ 249 Value: val.Addr().Interface(), 250 ctx: m.ctx, 251 } 252 err := fn(newModel) 253 254 if err != nil { 255 return err 256 } 257 } 258 return nil 259 } 260 261 return fn(m) 262 }