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