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