overhaul models to be more sensible by separating "instance" logic into a separate Document type ✨🪷
This commit is contained in:
parent
bcf3985360
commit
fcd7cb2013
223
document.go
Normal file
223
document.go
Normal file
@ -0,0 +1,223 @@
|
||||
package orm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Document struct {
|
||||
// Created time. updated/added automatically.
|
||||
Created time.Time `bson:"createdAt" json:"createdAt"`
|
||||
// Modified time. updated/added automatically.
|
||||
Modified time.Time `bson:"updatedAt" json:"updatedAt"`
|
||||
model *Model
|
||||
exists bool `bson:"-"`
|
||||
self any `bson:"-"`
|
||||
}
|
||||
type IDocument interface {
|
||||
Append(field string, a ...interface{}) error
|
||||
Pull(field string, a ...any) error
|
||||
Swap(field string, i, j int) error
|
||||
Delete() error
|
||||
Remove() error
|
||||
Save() error
|
||||
setSelf(arg interface{})
|
||||
getExists() bool
|
||||
setExists(n bool)
|
||||
setModified(Modified time.Time)
|
||||
setCreated(Modified time.Time)
|
||||
getModified() time.Time
|
||||
getCreated() time.Time
|
||||
serializeToStore() any
|
||||
getModel() *Model
|
||||
setModel(m Model)
|
||||
}
|
||||
|
||||
func (d *Document) getCreated() time.Time {
|
||||
return d.Created
|
||||
}
|
||||
|
||||
func (d *Document) setCreated(Created time.Time) {
|
||||
d.Created = Created
|
||||
}
|
||||
|
||||
func (d *Document) getModified() time.Time {
|
||||
return d.Modified
|
||||
}
|
||||
|
||||
func (d *Document) setModified(Modified time.Time) {
|
||||
d.Modified = Modified
|
||||
}
|
||||
func (d *Document) setSelf(arg interface{}) {
|
||||
d.self = arg
|
||||
}
|
||||
|
||||
func (d *Document) getModel() *Model {
|
||||
return d.model
|
||||
}
|
||||
|
||||
func (d *Document) setModel(m Model) {
|
||||
d.model = &m
|
||||
}
|
||||
|
||||
func (d *Document) getExists() bool {
|
||||
return d.exists
|
||||
}
|
||||
func (d *Document) setExists(n bool) {
|
||||
d.exists = n
|
||||
}
|
||||
|
||||
// Delete - deletes a model instance from the database
|
||||
func (d *Document) Delete() error {
|
||||
var err error
|
||||
val := valueOf(d.self)
|
||||
if val.Kind() == reflect.Slice {
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
cur := val.Index(i)
|
||||
if err = doDelete(d, cur.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return doDelete(d, d.self)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove - alias for Delete
|
||||
func (d *Document) Remove() error {
|
||||
return d.Delete()
|
||||
}
|
||||
|
||||
// Save - updates this Model in the database,
|
||||
// or inserts it if it doesn't exist
|
||||
func (d *Document) Save() error {
|
||||
val := valueOf(d.self)
|
||||
if val.Kind() == reflect.Slice {
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
cur := val.Index(i)
|
||||
if err := doSave(d.model.getColl(), !d.exists, cur.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return doSave(d.model.getColl(), !d.exists, d.self)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Document) serializeToStore() any {
|
||||
return serializeIDs((d).self)
|
||||
}
|
||||
|
||||
// Append appends one or more items to `field`.
|
||||
// will error if this Model contains a reference
|
||||
// to multiple documents, or if `field` is not a
|
||||
// slice.
|
||||
func (d *Document) Append(field string, a ...interface{}) error {
|
||||
var d0 IDocument = d
|
||||
d0.getCreated()
|
||||
rv := reflect.ValueOf(d.self)
|
||||
selfRef := rv
|
||||
rt := reflect.TypeOf(d.self)
|
||||
if selfRef.Kind() == reflect.Pointer {
|
||||
selfRef = selfRef.Elem()
|
||||
rt = rt.Elem()
|
||||
}
|
||||
if err := checkStruct(selfRef); err != nil {
|
||||
return err
|
||||
}
|
||||
_, origV, err := getNested(field, selfRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
origRef := makeSettable(*origV, (*origV).Interface())
|
||||
fv := origRef
|
||||
if fv.Kind() == reflect.Pointer {
|
||||
fv = fv.Elem()
|
||||
}
|
||||
if fv.Kind() != reflect.Slice {
|
||||
return ErrNotASlice
|
||||
}
|
||||
for _, b := range a {
|
||||
val := reflect.ValueOf(incrementTagged(b))
|
||||
fv.Set(reflect.Append(fv, val))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pull - removes elements from the subdocument slice stored in `field`.
|
||||
func (d *Document) Pull(field string, a ...any) error {
|
||||
rv := reflect.ValueOf(d.self)
|
||||
selfRef := rv
|
||||
rt := reflect.TypeOf(d.self)
|
||||
if selfRef.Kind() == reflect.Pointer {
|
||||
selfRef = selfRef.Elem()
|
||||
rt = rt.Elem()
|
||||
}
|
||||
if err := checkStruct(selfRef); err != nil {
|
||||
return err
|
||||
}
|
||||
_, origV, err := getNested(field, selfRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
origRef := makeSettable(*origV, (*origV).Interface())
|
||||
fv := origRef
|
||||
if fv.Kind() == reflect.Pointer {
|
||||
fv = fv.Elem()
|
||||
}
|
||||
if fv.Kind() != reflect.Slice {
|
||||
return ErrNotASlice
|
||||
}
|
||||
outer:
|
||||
for _, b := range a {
|
||||
for i := 0; i < fv.Len(); i++ {
|
||||
if reflect.DeepEqual(b, fv.Index(i).Interface()) {
|
||||
fv.Set(pull(fv, i, fv.Index(i).Type()))
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Swap - swaps the elements at indexes `i` and `j` in the
|
||||
// slice stored at `field`
|
||||
func (d *Document) Swap(field string, i, j int) error {
|
||||
rv := reflect.ValueOf(d.self)
|
||||
selfRef := rv
|
||||
rt := reflect.TypeOf(d.self)
|
||||
if selfRef.Kind() == reflect.Pointer {
|
||||
selfRef = selfRef.Elem()
|
||||
rt = rt.Elem()
|
||||
}
|
||||
if err := checkStruct(selfRef); err != nil {
|
||||
return err
|
||||
}
|
||||
_, origV, err := getNested(field, selfRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
origRef := makeSettable(*origV, (*origV).Interface())
|
||||
fv := origRef
|
||||
if fv.Kind() == reflect.Pointer {
|
||||
fv = fv.Elem()
|
||||
}
|
||||
if err = checkSlice(fv); err != nil {
|
||||
return err
|
||||
}
|
||||
if i >= fv.Len() || j >= fv.Len() {
|
||||
return ErrOutOfBounds
|
||||
}
|
||||
oi := fv.Index(i).Interface()
|
||||
oj := fv.Index(j).Interface()
|
||||
|
||||
fv.Index(i).Set(reflect.ValueOf(oj))
|
||||
fv.Index(j).Set(reflect.ValueOf(oi))
|
||||
|
||||
return nil
|
||||
}
|
@ -125,25 +125,25 @@ func serializeIDs(input interface{}) interface{} {
|
||||
}
|
||||
func doSave(c *mongo.Collection, isNew bool, arg interface{}) error {
|
||||
var err error
|
||||
m, ok := arg.(IModel)
|
||||
d, ok := arg.(IDocument)
|
||||
if !ok {
|
||||
return fmt.Errorf(errFmtNotAModel, nameOf(arg))
|
||||
}
|
||||
m.setSelf(m)
|
||||
d.setSelf(d)
|
||||
now := time.Now()
|
||||
selfo := reflect.ValueOf(m)
|
||||
selfo := reflect.ValueOf(d)
|
||||
vp := selfo
|
||||
if vp.Kind() != reflect.Ptr {
|
||||
vp = reflect.New(selfo.Type())
|
||||
vp.Elem().Set(selfo)
|
||||
}
|
||||
var asHasId = vp.Interface().(HasID)
|
||||
var asModel = vp.Interface().(IModel)
|
||||
var asModel = vp.Interface().(IDocument)
|
||||
if isNew {
|
||||
m.setCreated(now)
|
||||
d.setCreated(now)
|
||||
}
|
||||
m.setModified(now)
|
||||
idxs := m.getIdxs()
|
||||
d.setModified(now)
|
||||
idxs := d.getModel().getIdxs()
|
||||
for _, i := range idxs {
|
||||
_, err = c.Indexes().CreateOne(context.TODO(), *i)
|
||||
if err != nil {
|
||||
@ -157,28 +157,28 @@ func doSave(c *mongo.Collection, isNew bool, arg interface{}) error {
|
||||
(asHasId).SetId(pnid)
|
||||
}
|
||||
incrementAll(asHasId)
|
||||
_, im, _ := ModelRegistry.HasByName(asModel.getTypeName())
|
||||
_, im, _ := ModelRegistry.HasByName(asModel.getModel().getTypeName())
|
||||
_ = gridFsSave(asHasId, *im)
|
||||
|
||||
_, err = c.InsertOne(context.TODO(), m.serializeToStore())
|
||||
_, err = c.InsertOne(context.TODO(), d.serializeToStore())
|
||||
if err == nil {
|
||||
m.setExists(true)
|
||||
d.setExists(true)
|
||||
}
|
||||
} else {
|
||||
_, err = c.ReplaceOne(context.TODO(), bson.D{{Key: "_id", Value: m.(HasID).Id()}}, m.serializeToStore())
|
||||
_, err = c.ReplaceOne(context.TODO(), bson.D{{Key: "_id", Value: d.(HasID).Id()}}, d.serializeToStore())
|
||||
}
|
||||
return err
|
||||
}
|
||||
func doDelete(m *Model, arg interface{}) error {
|
||||
func doDelete(d *Document, arg interface{}) error {
|
||||
self, ok := arg.(HasID)
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf(errFmtNotHasID, nameOf(arg))
|
||||
}
|
||||
c := m.getColl()
|
||||
c := d.model.getColl()
|
||||
_, err := c.DeleteOne(context.TODO(), bson.M{"_id": self.Id()})
|
||||
if err == nil {
|
||||
m.exists = false
|
||||
d.exists = false
|
||||
}
|
||||
return err
|
||||
}
|
47
document_slice.go
Normal file
47
document_slice.go
Normal file
@ -0,0 +1,47 @@
|
||||
package orm
|
||||
|
||||
type IDocumentSlice interface {
|
||||
Delete() error
|
||||
Remove() error
|
||||
Save() error
|
||||
setExists(n bool)
|
||||
getModel() *Model
|
||||
}
|
||||
|
||||
type DocumentSlice []IDocument
|
||||
|
||||
func (d *DocumentSlice) Delete() error {
|
||||
var err error
|
||||
for _, doc := range *d {
|
||||
err = doc.Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DocumentSlice) Remove() error {
|
||||
return d.Delete()
|
||||
}
|
||||
|
||||
func (d *DocumentSlice) Save() error {
|
||||
var err error
|
||||
for _, doc := range *d {
|
||||
err = doc.Save()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DocumentSlice) setExists(b bool) {
|
||||
for _, s := range *d {
|
||||
s.setExists(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DocumentSlice) getModel() *Model {
|
||||
return (*d)[0].getModel()
|
||||
}
|
@ -107,7 +107,7 @@ func gridFsLoad(val any, g GridFSReference, field string) any {
|
||||
return doc.Interface()
|
||||
}
|
||||
|
||||
func gridFsSave(val any, imodel InternalModel) error {
|
||||
func gridFsSave(val any, imodel Model) error {
|
||||
var rerr error
|
||||
v := reflect.ValueOf(val)
|
||||
el := v
|
||||
|
248
model.go
248
model.go
@ -3,39 +3,22 @@ package orm
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Model - "base" struct for all queryable models
|
||||
type Model struct {
|
||||
// Created time. updated/added automatically.
|
||||
Created time.Time `bson:"createdAt" json:"createdAt"`
|
||||
// Modified time. updated/added automatically.
|
||||
Modified time.Time `bson:"updatedAt" json:"updatedAt"`
|
||||
typeName string `bson:"-"`
|
||||
self any `bson:"-"`
|
||||
exists bool `bson:"-"`
|
||||
}
|
||||
|
||||
func (m *Model) getCreated() time.Time {
|
||||
return m.Created
|
||||
}
|
||||
|
||||
func (m *Model) setCreated(Created time.Time) {
|
||||
m.Created = Created
|
||||
}
|
||||
|
||||
func (m *Model) getModified() time.Time {
|
||||
return m.Modified
|
||||
}
|
||||
|
||||
func (m *Model) setModified(Modified time.Time) {
|
||||
m.Modified = Modified
|
||||
Idx int
|
||||
Type reflect.Type
|
||||
Collection string
|
||||
References map[string]Reference
|
||||
Indexes map[string][]InternalIndex
|
||||
GridFSReferences map[string]GridFSReference
|
||||
}
|
||||
|
||||
// HasID is a simple interface that you must implement
|
||||
@ -52,29 +35,17 @@ type HasID interface {
|
||||
type HasIDSlice []HasID
|
||||
|
||||
type IModel interface {
|
||||
Append(field string, a ...interface{}) error
|
||||
Delete() error
|
||||
FindRaw(query interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error)
|
||||
Find(query interface{}, opts ...*options.FindOptions) (*Query, error)
|
||||
FindByID(id interface{}) (*Query, error)
|
||||
FindOne(query interface{}, options ...*options.FindOneOptions) (*Query, error)
|
||||
FindPaged(query interface{}, page int64, perPage int64, options ...*options.FindOptions) (*Query, error)
|
||||
Pull(field string, a ...any) error
|
||||
Remove() error
|
||||
Save() error
|
||||
|
||||
getColl() *mongo.Collection
|
||||
getIdxs() []*mongo.IndexModel
|
||||
getParsedIdxs() map[string][]InternalIndex
|
||||
serializeToStore() any
|
||||
getTypeName() string
|
||||
setTypeName(str string)
|
||||
getExists() bool
|
||||
setExists(n bool)
|
||||
setModified(Modified time.Time)
|
||||
setCreated(Modified time.Time)
|
||||
getModified() time.Time
|
||||
getCreated() time.Time
|
||||
setSelf(arg interface{})
|
||||
}
|
||||
|
||||
func (m *Model) getTypeName() string {
|
||||
@ -85,17 +56,6 @@ func (m *Model) setTypeName(str string) {
|
||||
m.typeName = str
|
||||
}
|
||||
|
||||
func (m *Model) setSelf(arg interface{}) {
|
||||
m.self = arg
|
||||
}
|
||||
|
||||
func (m *Model) getExists() bool {
|
||||
return m.exists
|
||||
}
|
||||
func (m *Model) setExists(n bool) {
|
||||
m.exists = n
|
||||
}
|
||||
|
||||
func (m *Model) getColl() *mongo.Collection {
|
||||
_, ri, ok := ModelRegistry.HasByName(m.typeName)
|
||||
if !ok {
|
||||
@ -146,12 +106,12 @@ func (m *Model) Find(query interface{}, opts ...*options.FindOptions) (*Query, e
|
||||
op: OP_FIND_ALL,
|
||||
}
|
||||
q, err := m.FindRaw(query, opts...)
|
||||
|
||||
idoc := (*DocumentSlice)(unsafe.Pointer(qqv.Elem().UnsafeAddr()))
|
||||
if err == nil {
|
||||
rawRes := bson.A{}
|
||||
err = q.All(context.TODO(), &rawRes)
|
||||
if err == nil {
|
||||
m.exists = true
|
||||
idoc.setExists(true)
|
||||
}
|
||||
qq.rawDoc = rawRes
|
||||
err = q.All(context.TODO(), &qq.doc)
|
||||
@ -195,14 +155,16 @@ func (m *Model) FindOne(query interface{}, options ...*options.FindOneOptions) (
|
||||
raw := bson.M{}
|
||||
err := rip.Decode(&raw)
|
||||
panik(err)
|
||||
m.exists = true
|
||||
qqn := ModelRegistry.new_(m.typeName)
|
||||
v := reflect.New(reflect.TypeOf(qqn))
|
||||
v.Elem().Set(reflect.ValueOf(qqn))
|
||||
idoc, ok := qqn.(IDocument)
|
||||
if ok {
|
||||
idoc.setExists(true)
|
||||
}
|
||||
|
||||
qq := &Query{
|
||||
collection: m.getColl(),
|
||||
rawDoc: raw,
|
||||
doc: v.Elem().Interface(),
|
||||
doc: idoc,
|
||||
op: OP_FIND_ONE,
|
||||
model: m,
|
||||
}
|
||||
@ -212,166 +174,13 @@ func (m *Model) FindOne(query interface{}, options ...*options.FindOneOptions) (
|
||||
qq.reOrganize()
|
||||
err = nil
|
||||
}
|
||||
m.self = qq.doc
|
||||
idoc.setSelf(idoc)
|
||||
return qq, err
|
||||
}
|
||||
|
||||
// Delete - deletes a model instance from the database
|
||||
func (m *Model) Delete() error {
|
||||
var err error
|
||||
val := valueOf(m.self)
|
||||
if val.Kind() == reflect.Slice {
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
cur := val.Index(i)
|
||||
if err = doDelete(m, cur.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return doDelete(m, m.self)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove - alias for Delete
|
||||
func (m *Model) Remove() error {
|
||||
return m.Delete()
|
||||
}
|
||||
|
||||
// Append appends one or more items to `field`.
|
||||
// will error if this Model contains a reference
|
||||
// to multiple documents, or if `field` is not a
|
||||
// slice.
|
||||
func (m *Model) Append(field string, a ...interface{}) error {
|
||||
rv := reflect.ValueOf(m.self)
|
||||
selfRef := rv
|
||||
rt := reflect.TypeOf(m.self)
|
||||
if selfRef.Kind() == reflect.Pointer {
|
||||
selfRef = selfRef.Elem()
|
||||
rt = rt.Elem()
|
||||
}
|
||||
if err := checkStruct(selfRef); err != nil {
|
||||
return err
|
||||
}
|
||||
_, origV, err := getNested(field, selfRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
origRef := makeSettable(*origV, (*origV).Interface())
|
||||
fv := origRef
|
||||
if fv.Kind() == reflect.Pointer {
|
||||
fv = fv.Elem()
|
||||
}
|
||||
if fv.Kind() != reflect.Slice {
|
||||
return ErrNotASlice
|
||||
}
|
||||
for _, b := range a {
|
||||
val := reflect.ValueOf(incrementTagged(b))
|
||||
fv.Set(reflect.Append(fv, val))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pull - removes elements from the subdocument slice stored in `field`.
|
||||
func (m *Model) Pull(field string, a ...any) error {
|
||||
rv := reflect.ValueOf(m.self)
|
||||
selfRef := rv
|
||||
rt := reflect.TypeOf(m.self)
|
||||
if selfRef.Kind() == reflect.Pointer {
|
||||
selfRef = selfRef.Elem()
|
||||
rt = rt.Elem()
|
||||
}
|
||||
if err := checkStruct(selfRef); err != nil {
|
||||
return err
|
||||
}
|
||||
_, origV, err := getNested(field, selfRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
origRef := makeSettable(*origV, (*origV).Interface())
|
||||
fv := origRef
|
||||
if fv.Kind() == reflect.Pointer {
|
||||
fv = fv.Elem()
|
||||
}
|
||||
if fv.Kind() != reflect.Slice {
|
||||
return ErrNotASlice
|
||||
}
|
||||
outer:
|
||||
for _, b := range a {
|
||||
for i := 0; i < fv.Len(); i++ {
|
||||
if reflect.DeepEqual(b, fv.Index(i).Interface()) {
|
||||
fv.Set(pull(fv, i, fv.Index(i).Type()))
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Swap - swaps the elements at indexes `i` and `j` in the
|
||||
// slice stored at `field`
|
||||
func (m *Model) Swap(field string, i, j int) error {
|
||||
rv := reflect.ValueOf(m.self)
|
||||
selfRef := rv
|
||||
rt := reflect.TypeOf(m.self)
|
||||
if selfRef.Kind() == reflect.Pointer {
|
||||
selfRef = selfRef.Elem()
|
||||
rt = rt.Elem()
|
||||
}
|
||||
if err := checkStruct(selfRef); err != nil {
|
||||
return err
|
||||
}
|
||||
_, origV, err := getNested(field, selfRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
origRef := makeSettable(*origV, (*origV).Interface())
|
||||
fv := origRef
|
||||
if fv.Kind() == reflect.Pointer {
|
||||
fv = fv.Elem()
|
||||
}
|
||||
if err = checkSlice(fv); err != nil {
|
||||
return err
|
||||
}
|
||||
if i >= fv.Len() || j >= fv.Len() {
|
||||
return ErrOutOfBounds
|
||||
}
|
||||
oi := fv.Index(i).Interface()
|
||||
oj := fv.Index(j).Interface()
|
||||
|
||||
fv.Index(i).Set(reflect.ValueOf(oj))
|
||||
fv.Index(j).Set(reflect.ValueOf(oi))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save - updates this Model in the database,
|
||||
// or inserts it if it doesn't exist
|
||||
func (m *Model) Save() error {
|
||||
val := valueOf(m.self)
|
||||
if val.Kind() == reflect.Slice {
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
cur := val.Index(i)
|
||||
if err := doSave(m.getColl(), !m.exists, cur.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return doSave(m.getColl(), !m.exists, m.self)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) serializeToStore() any {
|
||||
return serializeIDs((m).self)
|
||||
}
|
||||
|
||||
func createBase(d any) (reflect.Value, int, string) {
|
||||
var n string
|
||||
var ri *InternalModel
|
||||
var ri *Model
|
||||
var ok bool
|
||||
|
||||
n, ri, ok = ModelRegistry.HasByName(nameOf(d))
|
||||
@ -393,20 +202,23 @@ func createBase(d any) (reflect.Value, int, string) {
|
||||
} else {
|
||||
r.Elem().Set(reflect.ValueOf(d))
|
||||
}
|
||||
ri.setTypeName(n)
|
||||
r.Interface().(IDocument).setModel(*ri)
|
||||
|
||||
return r, i, n
|
||||
}
|
||||
|
||||
// Create creates a new instance of a given model
|
||||
// and returns a pointer to it.
|
||||
// Create creates a new instance of a given Document
|
||||
// type and returns a pointer to it.
|
||||
func Create(d any) any {
|
||||
r, i, n := createBase(d)
|
||||
df := r.Elem().Field(i)
|
||||
dm := df.Interface().(Model)
|
||||
dm.typeName = n
|
||||
r, _, n := createBase(d)
|
||||
//df := r.Elem().Field(i)
|
||||
dm := r.Interface().(IDocument)
|
||||
dm.getModel().setTypeName(n)
|
||||
what := r.Interface()
|
||||
dm.self = what
|
||||
df.Set(reflect.ValueOf(dm))
|
||||
|
||||
dm.setSelf(what)
|
||||
//df.Set(reflect.ValueOf(dm))
|
||||
return what
|
||||
}
|
||||
|
||||
|
@ -11,9 +11,10 @@ import (
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
initTest()
|
||||
doc := Create(iti_single()).(*story)
|
||||
assert.Equal(t, iti_single().Title, doc.Title)
|
||||
assert.Equal(t, iti_single().Chapters[0].Summary, doc.Chapters[0].Summary)
|
||||
is := iti_single()
|
||||
doc := Create(is).(*story)
|
||||
assert.Equal(t, is.Title, doc.Title)
|
||||
assert.Equal(t, is.Chapters[0].Summary, doc.Chapters[0].Summary)
|
||||
}
|
||||
|
||||
func TestSave(t *testing.T) {
|
||||
@ -46,7 +47,7 @@ func TestPopulate(t *testing.T) {
|
||||
saveDoc(t, storyDoc)
|
||||
assert.Greater(t, storyDoc.ID, int64(0))
|
||||
|
||||
smodel := Create(story{}).(*story)
|
||||
smodel := ModelRegistry["story"]
|
||||
q, err := smodel.FindByID(storyDoc.ID)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.NotPanics(t, func() {
|
||||
@ -67,7 +68,7 @@ func TestUpdate(t *testing.T) {
|
||||
nb.Locked = true
|
||||
saveDoc(t, nb)
|
||||
|
||||
foundM := Create(band{}).(*band)
|
||||
foundM := ModelRegistry["band"]
|
||||
q, err := foundM.FindByID(int64(1))
|
||||
assert.Equal(t, nil, err)
|
||||
found := &band{}
|
||||
@ -81,7 +82,7 @@ func TestModel_FindAll(t *testing.T) {
|
||||
initTest()
|
||||
im := iti_multi()
|
||||
createAndSave(t, &im)
|
||||
smodel := Create(story{}).(*story)
|
||||
smodel := ModelRegistry["story"]
|
||||
query, err := smodel.Find(bson.M{}, options.Find())
|
||||
assert.Equal(t, nil, err)
|
||||
final := CreateSlice(story{})
|
||||
@ -98,7 +99,7 @@ func TestModel_PopulateMulti(t *testing.T) {
|
||||
im := iti_multi()
|
||||
im.Author = mauthor
|
||||
createAndSave(t, &im)
|
||||
smodel := Create(story{}).(*story)
|
||||
smodel := ModelRegistry["story"]
|
||||
query, err := smodel.Find(bson.M{}, options.Find())
|
||||
assert.Equal(t, nil, err)
|
||||
final := CreateSlice(story{})
|
||||
@ -118,7 +119,7 @@ func TestModel_PopulateChained_Multi(t *testing.T) {
|
||||
saveDoc(t, mauthor)
|
||||
im.Author = mauthor
|
||||
createAndSave(t, &im)
|
||||
smodel := Create(story{}).(*story)
|
||||
smodel := ModelRegistry["story"]
|
||||
query, err := smodel.Find(bson.M{}, options.Find())
|
||||
assert.Equal(t, nil, err)
|
||||
final := CreateSlice(story{})
|
||||
@ -141,7 +142,7 @@ func TestPopulate_Chained(t *testing.T) {
|
||||
saveDoc(t, storyDoc)
|
||||
assert.Greater(t, storyDoc.ID, int64(0))
|
||||
|
||||
smodel := Create(story{}).(*story)
|
||||
smodel := ModelRegistry["story"]
|
||||
q, err := smodel.FindByID(storyDoc.ID)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.NotPanics(t, func() {
|
||||
@ -159,15 +160,15 @@ func TestModel_Append(t *testing.T) {
|
||||
initTest()
|
||||
bandDoc := Create(metallica).(*band)
|
||||
saveDoc(t, bandDoc)
|
||||
bmodel := Create(band{}).(*band)
|
||||
bmodel := ModelRegistry["band"]
|
||||
query, err := bmodel.FindByID(int64(1))
|
||||
assert.Equal(t, nil, err)
|
||||
fin := &band{}
|
||||
query.Exec(fin)
|
||||
assert.Greater(t, fin.ID, int64(0))
|
||||
err = bmodel.Append("Characters", "Robert Trujillo")
|
||||
err = fin.Append("Characters", "Robert Trujillo")
|
||||
assert.Equal(t, nil, err)
|
||||
saveDoc(t, bmodel)
|
||||
saveDoc(t, fin)
|
||||
fin = &band{}
|
||||
query, _ = bmodel.FindByID(int64(1))
|
||||
query.Exec(fin)
|
||||
@ -185,7 +186,7 @@ func TestModel_Delete(t *testing.T) {
|
||||
func TestModel_Pull(t *testing.T) {
|
||||
initTest()
|
||||
storyDoc := Create(iti_multi()).(*story)
|
||||
smodel := Create(story{}).(*story)
|
||||
smodel := ModelRegistry["story"]
|
||||
saveDoc(t, storyDoc)
|
||||
err := storyDoc.Pull("Chapters", storyDoc.Chapters[4])
|
||||
assert.Nil(t, err)
|
||||
@ -217,7 +218,7 @@ func TestModel_Swap(t *testing.T) {
|
||||
func TestModel_GridFSLoad(t *testing.T) {
|
||||
initTest()
|
||||
ModelRegistry.Model(somethingWithNestedChapters{})
|
||||
model := Create(somethingWithNestedChapters{}).(*somethingWithNestedChapters)
|
||||
model := ModelRegistry["somethingWithNestedChapters"]
|
||||
thingDoc := Create(doSomethingWithNested()).(*somethingWithNestedChapters)
|
||||
found := &somethingWithNestedChapters{}
|
||||
|
||||
@ -236,7 +237,7 @@ func TestModel_GridFSLoad(t *testing.T) {
|
||||
func TestModel_GridFSLoad_Chained(t *testing.T) {
|
||||
initTest()
|
||||
ModelRegistry.Model(somethingWithNestedChapters{})
|
||||
model := Create(somethingWithNestedChapters{}).(*somethingWithNestedChapters)
|
||||
model := ModelRegistry["somethingWithNestedChapters"]
|
||||
thingDoc := Create(doSomethingWithNested()).(*somethingWithNestedChapters)
|
||||
found := &somethingWithNestedChapters{}
|
||||
|
||||
@ -254,7 +255,7 @@ func TestModel_GridFSLoad_Chained(t *testing.T) {
|
||||
|
||||
func TestModel_GridFSLoad_Complex(t *testing.T) {
|
||||
initTest()
|
||||
model := Create(story{}).(*story)
|
||||
model := ModelRegistry["story"]
|
||||
bandDoc := Create(iti_single().Chapters[0].Bands[0]).(*band)
|
||||
thingDoc := Create(iti_multi()).(*story)
|
||||
mauthor := Create(author).(*user)
|
||||
|
5
query.go
5
query.go
@ -156,7 +156,7 @@ func populate(r Reference, rcoll string, rawDoc interface{}, d string, src inter
|
||||
}
|
||||
|
||||
rawt := ModelRegistry.new_(tto.Name())
|
||||
t := rawt.(IModel)
|
||||
t := rawt.(IDocument)
|
||||
q := bson.M{"_id": rawDoc}
|
||||
reso := DB.Collection(rcoll).FindOne(context.TODO(), q)
|
||||
reso.Decode(t)
|
||||
@ -516,7 +516,7 @@ func (q *Query) Exec(result interface{}) {
|
||||
if doc.Elem().Kind() == reflect.Slice {
|
||||
for i := 0; i < doc.Elem().Len(); i++ {
|
||||
cur := doc.Elem().Index(i)
|
||||
imodel, ok := cur.Interface().(IModel)
|
||||
imodel, ok := cur.Interface().(IDocument)
|
||||
if ok {
|
||||
imodel.setExists(true)
|
||||
doc.Elem().Index(i).Set(reflect.ValueOf(imodel))
|
||||
@ -524,6 +524,5 @@ func (q *Query) Exec(result interface{}) {
|
||||
}
|
||||
}
|
||||
reflect.ValueOf(result).Elem().Set(reflect.ValueOf(q.doc).Elem())
|
||||
q.model.self = q.doc
|
||||
q.done = true
|
||||
}
|
||||
|
40
registry.go
40
registry.go
@ -16,17 +16,6 @@ import (
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// InternalModel, as the name suggests, is used
|
||||
// internally by the model registry
|
||||
type InternalModel struct {
|
||||
Idx int
|
||||
Type reflect.Type
|
||||
Collection string
|
||||
References map[string]Reference
|
||||
Indexes map[string][]InternalIndex
|
||||
GridFSReferences map[string]GridFSReference
|
||||
}
|
||||
|
||||
// Reference stores a typed document reference
|
||||
type Reference struct {
|
||||
// owning model name
|
||||
@ -51,10 +40,10 @@ type GridFSReference struct {
|
||||
Idx int
|
||||
}
|
||||
|
||||
type TModelRegistry map[string]*InternalModel
|
||||
type TModelRegistry map[string]*Model
|
||||
|
||||
// ModelRegistry - the ModelRegistry stores a map containing
|
||||
// pointers to InternalModel instances, keyed by an associated
|
||||
// pointers to Model instances, keyed by an associated
|
||||
// model name
|
||||
var ModelRegistry = make(TModelRegistry, 0)
|
||||
|
||||
@ -182,7 +171,7 @@ func parseTags(t reflect.Type, v reflect.Value) (map[string][]InternalIndex, map
|
||||
ft = ft.Elem()
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
if ft.ConvertibleTo(reflect.TypeOf(Model{})) {
|
||||
if ft.ConvertibleTo(reflect.TypeOf(Document{})) {
|
||||
collTag, err := tags.Get("coll")
|
||||
panik(err)
|
||||
coll = collTag.Name
|
||||
@ -223,10 +212,10 @@ func parseTags(t reflect.Type, v reflect.Value) (map[string][]InternalIndex, map
|
||||
return idcs, refs, gfsRefs, coll
|
||||
}
|
||||
|
||||
// Has returns the model typename and InternalModel instance corresponding
|
||||
// Has returns the model typename and Model instance corresponding
|
||||
// to the argument passed, as well as a boolean indicating whether it
|
||||
// was found. otherwise returns `"", nil, false`
|
||||
func (r TModelRegistry) Has(i interface{}) (string, *InternalModel, bool) {
|
||||
func (r TModelRegistry) Has(i interface{}) (string, *Model, bool) {
|
||||
t := reflect.TypeOf(i)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
@ -240,14 +229,14 @@ func (r TModelRegistry) Has(i interface{}) (string, *InternalModel, bool) {
|
||||
|
||||
// HasByName functions almost identically to Has,
|
||||
// except that it takes a string as its argument.
|
||||
func (r TModelRegistry) HasByName(n string) (string, *InternalModel, bool) {
|
||||
func (r TModelRegistry) HasByName(n string) (string, *Model, bool) {
|
||||
if t, ok := ModelRegistry[n]; ok {
|
||||
return n, t, true
|
||||
}
|
||||
return "", nil, false
|
||||
}
|
||||
|
||||
// Index returns the index at which the Model struct is embedded
|
||||
// Index returns the index at which the Document struct is embedded
|
||||
func (r TModelRegistry) Index(n string) int {
|
||||
if v, ok := ModelRegistry[n]; ok {
|
||||
return v.Idx
|
||||
@ -259,9 +248,12 @@ func (r TModelRegistry) new_(n string) interface{} {
|
||||
if name, m, ok := ModelRegistry.HasByName(n); ok {
|
||||
v := reflect.New(m.Type)
|
||||
df := v.Elem().Field(m.Idx)
|
||||
d := df.Interface().(Model)
|
||||
d.typeName = name
|
||||
df.Set(reflect.ValueOf(d))
|
||||
do := reflect.New(df.Type())
|
||||
d := do.Interface().(IDocument)
|
||||
//d := df.Interface().(IDocument)
|
||||
d.setModel(*m)
|
||||
d.getModel().typeName = name
|
||||
df.Set(reflect.ValueOf(d).Elem())
|
||||
return v.Interface()
|
||||
}
|
||||
return nil
|
||||
@ -303,19 +295,19 @@ func (r TModelRegistry) Model(mdl ...any) {
|
||||
idx := -1
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
ft := t.Field(i)
|
||||
if (ft.Type.ConvertibleTo(reflect.TypeOf(Model{}))) {
|
||||
if (ft.Type.ConvertibleTo(reflect.TypeOf(Document{}))) {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx < 0 {
|
||||
panic("A model must embed the Model struct!")
|
||||
panic("A model must embed the Document struct!")
|
||||
}
|
||||
inds, refs, gfs, coll := parseTags(t, v)
|
||||
if coll == "" {
|
||||
panic(fmt.Sprintf("a model needs to be given a collection name! (passed type: %s)", n))
|
||||
}
|
||||
ModelRegistry[n] = &InternalModel{
|
||||
ModelRegistry[n] = &Model{
|
||||
Idx: idx,
|
||||
Type: t,
|
||||
Collection: coll,
|
||||
|
16
testing.go
16
testing.go
@ -37,19 +37,19 @@ type chapter struct {
|
||||
|
||||
type band struct {
|
||||
ID int64 `bson:"_id" json:"_id"`
|
||||
Model `bson:",inline" json:",inline" coll:"bands"`
|
||||
Document `bson:",inline" json:",inline" coll:"bands"`
|
||||
Name string `bson:"name" json:"name" form:"name"`
|
||||
Locked bool `bson:"locked" json:"locked" form:"locked"`
|
||||
Characters []string `bson:"characters" json:"characters" form:"characters"`
|
||||
}
|
||||
type user struct {
|
||||
ID int64 `bson:"_id" json:"_id"`
|
||||
Model `bson:",inline" json:",inline" coll:"users"`
|
||||
Document `bson:",inline" json:",inline" coll:"users"`
|
||||
Username string `bson:"username" json:"username"`
|
||||
}
|
||||
type story struct {
|
||||
ID int64 `bson:"_id" json:"_id"`
|
||||
Model `bson:",inline" json:",inline" coll:"stories"`
|
||||
Document `bson:",inline" json:",inline" coll:"stories"`
|
||||
Title string `bson:"title" json:"title" form:"title"`
|
||||
Author *user `bson:"author" json:"author" ref:"user"`
|
||||
CoAuthor *user `bson:"coAuthor" json:"coAuthor" ref:"user"`
|
||||
@ -62,7 +62,7 @@ type story struct {
|
||||
}
|
||||
type somethingWithNestedChapters struct {
|
||||
ID int64 `bson:"_id" json:"_id"`
|
||||
Model `bson:",inline" json:",inline" coll:"nested_stuff"`
|
||||
Document `bson:",inline" json:",inline" coll:"nested_stuff"`
|
||||
Chapters []chapter `bson:"chapters" json:"chapters"`
|
||||
NestedText string `json:"text" bson:"-" gridfs:"nested_text,/nested/{{.ID}}.txt"`
|
||||
}
|
||||
@ -88,7 +88,7 @@ func (s *user) Id() any {
|
||||
|
||||
func (s *story) SetId(id any) {
|
||||
s.ID = id.(int64)
|
||||
//var t IModel = s
|
||||
//var t IDocument =s
|
||||
}
|
||||
|
||||
func (s *band) SetId(id any) {
|
||||
@ -245,12 +245,12 @@ var bodom = band{
|
||||
},
|
||||
}
|
||||
|
||||
func saveDoc(t *testing.T, doc IModel) {
|
||||
func saveDoc(t *testing.T, doc IDocument) {
|
||||
err := doc.Save()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func createAndSave(t *testing.T, doc IModel) {
|
||||
mdl := Create(doc).(IModel)
|
||||
func createAndSave(t *testing.T, doc IDocument) {
|
||||
mdl := Create(doc).(IDocument)
|
||||
saveDoc(t, mdl)
|
||||
}
|
||||
|
44
util.go
44
util.go
@ -167,3 +167,47 @@ func checkSlice(ref reflect.Value) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertSlice[In, Out any](in []In) []Out {
|
||||
out := make([]Out, 0)
|
||||
for _, i := range in {
|
||||
ii, ok := any(i).(Out)
|
||||
if ok {
|
||||
out = append(out, ii)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func normalizeSliceToDocumentSlice(in any) *DocumentSlice {
|
||||
ret := make(DocumentSlice, 0)
|
||||
val := reflect.ValueOf(in)
|
||||
if val.Kind() == reflect.Pointer {
|
||||
val = val.Elem()
|
||||
}
|
||||
if val.Kind() == reflect.Slice {
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
if idoc, ok := val.Index(i).Interface().(IDocument); ok {
|
||||
ret = append(ret, idoc)
|
||||
}
|
||||
}
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
/*func normalizeDocSlice(iput reflect.Value) reflect.Value {
|
||||
idslice, idsliceOk := iput.Interface().(IDocumentSlice)
|
||||
dslice, dsliceOk := resV.Interface().(DocumentSlice)
|
||||
switch {
|
||||
case idsliceOk:
|
||||
|
||||
case dsliceOk:
|
||||
default:
|
||||
return iput
|
||||
}
|
||||
//if {
|
||||
// resV.Set(trvo.Elem())
|
||||
//} else {
|
||||
//
|
||||
//}
|
||||
}*/
|
||||
|
Loading…
Reference in New Issue
Block a user