overhaul models to be more sensible by separating "instance" logic into a separate Document type 🪷

This commit is contained in:
parent bcf3985360
commit fcd7cb2013
Signed by: tablet
GPG Key ID: 924A5F6AF051E87C
10 changed files with 403 additions and 285 deletions

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 { func doSave(c *mongo.Collection, isNew bool, arg interface{}) error {
var err error var err error
m, ok := arg.(IModel) d, ok := arg.(IDocument)
if !ok { if !ok {
return fmt.Errorf(errFmtNotAModel, nameOf(arg)) return fmt.Errorf(errFmtNotAModel, nameOf(arg))
} }
m.setSelf(m) d.setSelf(d)
now := time.Now() now := time.Now()
selfo := reflect.ValueOf(m) selfo := reflect.ValueOf(d)
vp := selfo vp := selfo
if vp.Kind() != reflect.Ptr { if vp.Kind() != reflect.Ptr {
vp = reflect.New(selfo.Type()) vp = reflect.New(selfo.Type())
vp.Elem().Set(selfo) vp.Elem().Set(selfo)
} }
var asHasId = vp.Interface().(HasID) var asHasId = vp.Interface().(HasID)
var asModel = vp.Interface().(IModel) var asModel = vp.Interface().(IDocument)
if isNew { if isNew {
m.setCreated(now) d.setCreated(now)
} }
m.setModified(now) d.setModified(now)
idxs := m.getIdxs() idxs := d.getModel().getIdxs()
for _, i := range idxs { for _, i := range idxs {
_, err = c.Indexes().CreateOne(context.TODO(), *i) _, err = c.Indexes().CreateOne(context.TODO(), *i)
if err != nil { if err != nil {
@ -157,28 +157,28 @@ func doSave(c *mongo.Collection, isNew bool, arg interface{}) error {
(asHasId).SetId(pnid) (asHasId).SetId(pnid)
} }
incrementAll(asHasId) incrementAll(asHasId)
_, im, _ := ModelRegistry.HasByName(asModel.getTypeName()) _, im, _ := ModelRegistry.HasByName(asModel.getModel().getTypeName())
_ = gridFsSave(asHasId, *im) _ = gridFsSave(asHasId, *im)
_, err = c.InsertOne(context.TODO(), m.serializeToStore()) _, err = c.InsertOne(context.TODO(), d.serializeToStore())
if err == nil { if err == nil {
m.setExists(true) d.setExists(true)
} }
} else { } 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 return err
} }
func doDelete(m *Model, arg interface{}) error { func doDelete(d *Document, arg interface{}) error {
self, ok := arg.(HasID) self, ok := arg.(HasID)
if !ok { if !ok {
return fmt.Errorf(errFmtNotHasID, nameOf(arg)) return fmt.Errorf(errFmtNotHasID, nameOf(arg))
} }
c := m.getColl() c := d.model.getColl()
_, err := c.DeleteOne(context.TODO(), bson.M{"_id": self.Id()}) _, err := c.DeleteOne(context.TODO(), bson.M{"_id": self.Id()})
if err == nil { if err == nil {
m.exists = false d.exists = false
} }
return err return err
} }

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() return doc.Interface()
} }
func gridFsSave(val any, imodel InternalModel) error { func gridFsSave(val any, imodel Model) error {
var rerr error var rerr error
v := reflect.ValueOf(val) v := reflect.ValueOf(val)
el := v el := v

250
model.go

@ -3,39 +3,22 @@ package orm
import ( import (
"context" "context"
"fmt" "fmt"
"reflect"
"time"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
"reflect"
"unsafe"
) )
// Model - "base" struct for all queryable models // Model - "base" struct for all queryable models
type Model struct { type Model struct {
// Created time. updated/added automatically. typeName string `bson:"-"`
Created time.Time `bson:"createdAt" json:"createdAt"` Idx int
// Modified time. updated/added automatically. Type reflect.Type
Modified time.Time `bson:"updatedAt" json:"updatedAt"` Collection string
typeName string `bson:"-"` References map[string]Reference
self any `bson:"-"` Indexes map[string][]InternalIndex
exists bool `bson:"-"` GridFSReferences map[string]GridFSReference
}
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
} }
// HasID is a simple interface that you must implement // HasID is a simple interface that you must implement
@ -52,29 +35,17 @@ type HasID interface {
type HasIDSlice []HasID type HasIDSlice []HasID
type IModel interface { type IModel interface {
Append(field string, a ...interface{}) error
Delete() error
FindRaw(query interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error) FindRaw(query interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error)
Find(query interface{}, opts ...*options.FindOptions) (*Query, error) Find(query interface{}, opts ...*options.FindOptions) (*Query, error)
FindByID(id interface{}) (*Query, error) FindByID(id interface{}) (*Query, error)
FindOne(query interface{}, options ...*options.FindOneOptions) (*Query, error) FindOne(query interface{}, options ...*options.FindOneOptions) (*Query, error)
FindPaged(query interface{}, page int64, perPage int64, options ...*options.FindOptions) (*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 getColl() *mongo.Collection
getIdxs() []*mongo.IndexModel getIdxs() []*mongo.IndexModel
getParsedIdxs() map[string][]InternalIndex getParsedIdxs() map[string][]InternalIndex
serializeToStore() any
getTypeName() string getTypeName() string
setTypeName(str 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 { func (m *Model) getTypeName() string {
@ -85,17 +56,6 @@ func (m *Model) setTypeName(str string) {
m.typeName = str 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 { func (m *Model) getColl() *mongo.Collection {
_, ri, ok := ModelRegistry.HasByName(m.typeName) _, ri, ok := ModelRegistry.HasByName(m.typeName)
if !ok { if !ok {
@ -146,12 +106,12 @@ func (m *Model) Find(query interface{}, opts ...*options.FindOptions) (*Query, e
op: OP_FIND_ALL, op: OP_FIND_ALL,
} }
q, err := m.FindRaw(query, opts...) q, err := m.FindRaw(query, opts...)
idoc := (*DocumentSlice)(unsafe.Pointer(qqv.Elem().UnsafeAddr()))
if err == nil { if err == nil {
rawRes := bson.A{} rawRes := bson.A{}
err = q.All(context.TODO(), &rawRes) err = q.All(context.TODO(), &rawRes)
if err == nil { if err == nil {
m.exists = true idoc.setExists(true)
} }
qq.rawDoc = rawRes qq.rawDoc = rawRes
err = q.All(context.TODO(), &qq.doc) err = q.All(context.TODO(), &qq.doc)
@ -195,14 +155,16 @@ func (m *Model) FindOne(query interface{}, options ...*options.FindOneOptions) (
raw := bson.M{} raw := bson.M{}
err := rip.Decode(&raw) err := rip.Decode(&raw)
panik(err) panik(err)
m.exists = true
qqn := ModelRegistry.new_(m.typeName) qqn := ModelRegistry.new_(m.typeName)
v := reflect.New(reflect.TypeOf(qqn)) idoc, ok := qqn.(IDocument)
v.Elem().Set(reflect.ValueOf(qqn)) if ok {
idoc.setExists(true)
}
qq := &Query{ qq := &Query{
collection: m.getColl(), collection: m.getColl(),
rawDoc: raw, rawDoc: raw,
doc: v.Elem().Interface(), doc: idoc,
op: OP_FIND_ONE, op: OP_FIND_ONE,
model: m, model: m,
} }
@ -212,166 +174,13 @@ func (m *Model) FindOne(query interface{}, options ...*options.FindOneOptions) (
qq.reOrganize() qq.reOrganize()
err = nil err = nil
} }
m.self = qq.doc idoc.setSelf(idoc)
return qq, err 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) { func createBase(d any) (reflect.Value, int, string) {
var n string var n string
var ri *InternalModel var ri *Model
var ok bool var ok bool
n, ri, ok = ModelRegistry.HasByName(nameOf(d)) n, ri, ok = ModelRegistry.HasByName(nameOf(d))
@ -393,20 +202,23 @@ func createBase(d any) (reflect.Value, int, string) {
} else { } else {
r.Elem().Set(reflect.ValueOf(d)) r.Elem().Set(reflect.ValueOf(d))
} }
ri.setTypeName(n)
r.Interface().(IDocument).setModel(*ri)
return r, i, n return r, i, n
} }
// Create creates a new instance of a given model // Create creates a new instance of a given Document
// and returns a pointer to it. // type and returns a pointer to it.
func Create(d any) any { func Create(d any) any {
r, i, n := createBase(d) r, _, n := createBase(d)
df := r.Elem().Field(i) //df := r.Elem().Field(i)
dm := df.Interface().(Model) dm := r.Interface().(IDocument)
dm.typeName = n dm.getModel().setTypeName(n)
what := r.Interface() what := r.Interface()
dm.self = what
df.Set(reflect.ValueOf(dm)) dm.setSelf(what)
//df.Set(reflect.ValueOf(dm))
return what return what
} }

@ -11,9 +11,10 @@ import (
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
initTest() initTest()
doc := Create(iti_single()).(*story) is := iti_single()
assert.Equal(t, iti_single().Title, doc.Title) doc := Create(is).(*story)
assert.Equal(t, iti_single().Chapters[0].Summary, doc.Chapters[0].Summary) assert.Equal(t, is.Title, doc.Title)
assert.Equal(t, is.Chapters[0].Summary, doc.Chapters[0].Summary)
} }
func TestSave(t *testing.T) { func TestSave(t *testing.T) {
@ -46,7 +47,7 @@ func TestPopulate(t *testing.T) {
saveDoc(t, storyDoc) saveDoc(t, storyDoc)
assert.Greater(t, storyDoc.ID, int64(0)) assert.Greater(t, storyDoc.ID, int64(0))
smodel := Create(story{}).(*story) smodel := ModelRegistry["story"]
q, err := smodel.FindByID(storyDoc.ID) q, err := smodel.FindByID(storyDoc.ID)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.NotPanics(t, func() { assert.NotPanics(t, func() {
@ -67,7 +68,7 @@ func TestUpdate(t *testing.T) {
nb.Locked = true nb.Locked = true
saveDoc(t, nb) saveDoc(t, nb)
foundM := Create(band{}).(*band) foundM := ModelRegistry["band"]
q, err := foundM.FindByID(int64(1)) q, err := foundM.FindByID(int64(1))
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
found := &band{} found := &band{}
@ -81,7 +82,7 @@ func TestModel_FindAll(t *testing.T) {
initTest() initTest()
im := iti_multi() im := iti_multi()
createAndSave(t, &im) createAndSave(t, &im)
smodel := Create(story{}).(*story) smodel := ModelRegistry["story"]
query, err := smodel.Find(bson.M{}, options.Find()) query, err := smodel.Find(bson.M{}, options.Find())
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
final := CreateSlice(story{}) final := CreateSlice(story{})
@ -98,7 +99,7 @@ func TestModel_PopulateMulti(t *testing.T) {
im := iti_multi() im := iti_multi()
im.Author = mauthor im.Author = mauthor
createAndSave(t, &im) createAndSave(t, &im)
smodel := Create(story{}).(*story) smodel := ModelRegistry["story"]
query, err := smodel.Find(bson.M{}, options.Find()) query, err := smodel.Find(bson.M{}, options.Find())
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
final := CreateSlice(story{}) final := CreateSlice(story{})
@ -118,7 +119,7 @@ func TestModel_PopulateChained_Multi(t *testing.T) {
saveDoc(t, mauthor) saveDoc(t, mauthor)
im.Author = mauthor im.Author = mauthor
createAndSave(t, &im) createAndSave(t, &im)
smodel := Create(story{}).(*story) smodel := ModelRegistry["story"]
query, err := smodel.Find(bson.M{}, options.Find()) query, err := smodel.Find(bson.M{}, options.Find())
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
final := CreateSlice(story{}) final := CreateSlice(story{})
@ -141,7 +142,7 @@ func TestPopulate_Chained(t *testing.T) {
saveDoc(t, storyDoc) saveDoc(t, storyDoc)
assert.Greater(t, storyDoc.ID, int64(0)) assert.Greater(t, storyDoc.ID, int64(0))
smodel := Create(story{}).(*story) smodel := ModelRegistry["story"]
q, err := smodel.FindByID(storyDoc.ID) q, err := smodel.FindByID(storyDoc.ID)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.NotPanics(t, func() { assert.NotPanics(t, func() {
@ -159,15 +160,15 @@ func TestModel_Append(t *testing.T) {
initTest() initTest()
bandDoc := Create(metallica).(*band) bandDoc := Create(metallica).(*band)
saveDoc(t, bandDoc) saveDoc(t, bandDoc)
bmodel := Create(band{}).(*band) bmodel := ModelRegistry["band"]
query, err := bmodel.FindByID(int64(1)) query, err := bmodel.FindByID(int64(1))
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
fin := &band{} fin := &band{}
query.Exec(fin) query.Exec(fin)
assert.Greater(t, fin.ID, int64(0)) assert.Greater(t, fin.ID, int64(0))
err = bmodel.Append("Characters", "Robert Trujillo") err = fin.Append("Characters", "Robert Trujillo")
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
saveDoc(t, bmodel) saveDoc(t, fin)
fin = &band{} fin = &band{}
query, _ = bmodel.FindByID(int64(1)) query, _ = bmodel.FindByID(int64(1))
query.Exec(fin) query.Exec(fin)
@ -185,7 +186,7 @@ func TestModel_Delete(t *testing.T) {
func TestModel_Pull(t *testing.T) { func TestModel_Pull(t *testing.T) {
initTest() initTest()
storyDoc := Create(iti_multi()).(*story) storyDoc := Create(iti_multi()).(*story)
smodel := Create(story{}).(*story) smodel := ModelRegistry["story"]
saveDoc(t, storyDoc) saveDoc(t, storyDoc)
err := storyDoc.Pull("Chapters", storyDoc.Chapters[4]) err := storyDoc.Pull("Chapters", storyDoc.Chapters[4])
assert.Nil(t, err) assert.Nil(t, err)
@ -217,7 +218,7 @@ func TestModel_Swap(t *testing.T) {
func TestModel_GridFSLoad(t *testing.T) { func TestModel_GridFSLoad(t *testing.T) {
initTest() initTest()
ModelRegistry.Model(somethingWithNestedChapters{}) ModelRegistry.Model(somethingWithNestedChapters{})
model := Create(somethingWithNestedChapters{}).(*somethingWithNestedChapters) model := ModelRegistry["somethingWithNestedChapters"]
thingDoc := Create(doSomethingWithNested()).(*somethingWithNestedChapters) thingDoc := Create(doSomethingWithNested()).(*somethingWithNestedChapters)
found := &somethingWithNestedChapters{} found := &somethingWithNestedChapters{}
@ -236,7 +237,7 @@ func TestModel_GridFSLoad(t *testing.T) {
func TestModel_GridFSLoad_Chained(t *testing.T) { func TestModel_GridFSLoad_Chained(t *testing.T) {
initTest() initTest()
ModelRegistry.Model(somethingWithNestedChapters{}) ModelRegistry.Model(somethingWithNestedChapters{})
model := Create(somethingWithNestedChapters{}).(*somethingWithNestedChapters) model := ModelRegistry["somethingWithNestedChapters"]
thingDoc := Create(doSomethingWithNested()).(*somethingWithNestedChapters) thingDoc := Create(doSomethingWithNested()).(*somethingWithNestedChapters)
found := &somethingWithNestedChapters{} found := &somethingWithNestedChapters{}
@ -254,7 +255,7 @@ func TestModel_GridFSLoad_Chained(t *testing.T) {
func TestModel_GridFSLoad_Complex(t *testing.T) { func TestModel_GridFSLoad_Complex(t *testing.T) {
initTest() initTest()
model := Create(story{}).(*story) model := ModelRegistry["story"]
bandDoc := Create(iti_single().Chapters[0].Bands[0]).(*band) bandDoc := Create(iti_single().Chapters[0].Bands[0]).(*band)
thingDoc := Create(iti_multi()).(*story) thingDoc := Create(iti_multi()).(*story)
mauthor := Create(author).(*user) mauthor := Create(author).(*user)

@ -156,7 +156,7 @@ func populate(r Reference, rcoll string, rawDoc interface{}, d string, src inter
} }
rawt := ModelRegistry.new_(tto.Name()) rawt := ModelRegistry.new_(tto.Name())
t := rawt.(IModel) t := rawt.(IDocument)
q := bson.M{"_id": rawDoc} q := bson.M{"_id": rawDoc}
reso := DB.Collection(rcoll).FindOne(context.TODO(), q) reso := DB.Collection(rcoll).FindOne(context.TODO(), q)
reso.Decode(t) reso.Decode(t)
@ -516,7 +516,7 @@ func (q *Query) Exec(result interface{}) {
if doc.Elem().Kind() == reflect.Slice { if doc.Elem().Kind() == reflect.Slice {
for i := 0; i < doc.Elem().Len(); i++ { for i := 0; i < doc.Elem().Len(); i++ {
cur := doc.Elem().Index(i) cur := doc.Elem().Index(i)
imodel, ok := cur.Interface().(IModel) imodel, ok := cur.Interface().(IDocument)
if ok { if ok {
imodel.setExists(true) imodel.setExists(true)
doc.Elem().Index(i).Set(reflect.ValueOf(imodel)) 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()) reflect.ValueOf(result).Elem().Set(reflect.ValueOf(q.doc).Elem())
q.model.self = q.doc
q.done = true q.done = true
} }

@ -16,17 +16,6 @@ import (
"golang.org/x/net/context" "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 // Reference stores a typed document reference
type Reference struct { type Reference struct {
// owning model name // owning model name
@ -51,10 +40,10 @@ type GridFSReference struct {
Idx int Idx int
} }
type TModelRegistry map[string]*InternalModel type TModelRegistry map[string]*Model
// ModelRegistry - the ModelRegistry stores a map containing // 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 // model name
var ModelRegistry = make(TModelRegistry, 0) var ModelRegistry = make(TModelRegistry, 0)
@ -182,7 +171,7 @@ func parseTags(t reflect.Type, v reflect.Value) (map[string][]InternalIndex, map
ft = ft.Elem() ft = ft.Elem()
fallthrough fallthrough
case reflect.Struct: case reflect.Struct:
if ft.ConvertibleTo(reflect.TypeOf(Model{})) { if ft.ConvertibleTo(reflect.TypeOf(Document{})) {
collTag, err := tags.Get("coll") collTag, err := tags.Get("coll")
panik(err) panik(err)
coll = collTag.Name coll = collTag.Name
@ -223,10 +212,10 @@ func parseTags(t reflect.Type, v reflect.Value) (map[string][]InternalIndex, map
return idcs, refs, gfsRefs, coll 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 // to the argument passed, as well as a boolean indicating whether it
// was found. otherwise returns `"", nil, false` // 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) t := reflect.TypeOf(i)
if t.Kind() == reflect.Ptr { if t.Kind() == reflect.Ptr {
t = t.Elem() t = t.Elem()
@ -240,14 +229,14 @@ func (r TModelRegistry) Has(i interface{}) (string, *InternalModel, bool) {
// HasByName functions almost identically to Has, // HasByName functions almost identically to Has,
// except that it takes a string as its argument. // 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 { if t, ok := ModelRegistry[n]; ok {
return n, t, true return n, t, true
} }
return "", nil, false 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 { func (r TModelRegistry) Index(n string) int {
if v, ok := ModelRegistry[n]; ok { if v, ok := ModelRegistry[n]; ok {
return v.Idx return v.Idx
@ -259,9 +248,12 @@ func (r TModelRegistry) new_(n string) interface{} {
if name, m, ok := ModelRegistry.HasByName(n); ok { if name, m, ok := ModelRegistry.HasByName(n); ok {
v := reflect.New(m.Type) v := reflect.New(m.Type)
df := v.Elem().Field(m.Idx) df := v.Elem().Field(m.Idx)
d := df.Interface().(Model) do := reflect.New(df.Type())
d.typeName = name d := do.Interface().(IDocument)
df.Set(reflect.ValueOf(d)) //d := df.Interface().(IDocument)
d.setModel(*m)
d.getModel().typeName = name
df.Set(reflect.ValueOf(d).Elem())
return v.Interface() return v.Interface()
} }
return nil return nil
@ -303,19 +295,19 @@ func (r TModelRegistry) Model(mdl ...any) {
idx := -1 idx := -1
for i := 0; i < v.NumField(); i++ { for i := 0; i < v.NumField(); i++ {
ft := t.Field(i) ft := t.Field(i)
if (ft.Type.ConvertibleTo(reflect.TypeOf(Model{}))) { if (ft.Type.ConvertibleTo(reflect.TypeOf(Document{}))) {
idx = i idx = i
break break
} }
} }
if idx < 0 { 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) inds, refs, gfs, coll := parseTags(t, v)
if coll == "" { if coll == "" {
panic(fmt.Sprintf("a model needs to be given a collection name! (passed type: %s)", n)) panic(fmt.Sprintf("a model needs to be given a collection name! (passed type: %s)", n))
} }
ModelRegistry[n] = &InternalModel{ ModelRegistry[n] = &Model{
Idx: idx, Idx: idx,
Type: t, Type: t,
Collection: coll, Collection: coll,

@ -37,19 +37,19 @@ type chapter struct {
type band struct { type band struct {
ID int64 `bson:"_id" json:"_id"` 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"` Name string `bson:"name" json:"name" form:"name"`
Locked bool `bson:"locked" json:"locked" form:"locked"` Locked bool `bson:"locked" json:"locked" form:"locked"`
Characters []string `bson:"characters" json:"characters" form:"characters"` Characters []string `bson:"characters" json:"characters" form:"characters"`
} }
type user struct { type user struct {
ID int64 `bson:"_id" json:"_id"` 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"` Username string `bson:"username" json:"username"`
} }
type story struct { type story struct {
ID int64 `bson:"_id" json:"_id"` 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"` Title string `bson:"title" json:"title" form:"title"`
Author *user `bson:"author" json:"author" ref:"user"` Author *user `bson:"author" json:"author" ref:"user"`
CoAuthor *user `bson:"coAuthor" json:"coAuthor" ref:"user"` CoAuthor *user `bson:"coAuthor" json:"coAuthor" ref:"user"`
@ -62,7 +62,7 @@ type story struct {
} }
type somethingWithNestedChapters struct { type somethingWithNestedChapters struct {
ID int64 `bson:"_id" json:"_id"` 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"` Chapters []chapter `bson:"chapters" json:"chapters"`
NestedText string `json:"text" bson:"-" gridfs:"nested_text,/nested/{{.ID}}.txt"` 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) { func (s *story) SetId(id any) {
s.ID = id.(int64) s.ID = id.(int64)
//var t IModel = s //var t IDocument =s
} }
func (s *band) SetId(id any) { 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() err := doc.Save()
assert.Nil(t, err) assert.Nil(t, err)
} }
func createAndSave(t *testing.T, doc IModel) { func createAndSave(t *testing.T, doc IDocument) {
mdl := Create(doc).(IModel) mdl := Create(doc).(IDocument)
saveDoc(t, mdl) saveDoc(t, mdl)
} }

44
util.go

@ -167,3 +167,47 @@ func checkSlice(ref reflect.Value) error {
} }
return nil 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 {
//
//}
}*/