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