refactor some things, add Delete method, improve Save method
This commit is contained in:
		
							parent
							
								
									3f8fded6e5
								
							
						
					
					
						commit
						f721b32e01
					
				
							
								
								
									
										121
									
								
								model.go
									
									
									
									
									
								
							
							
						
						
									
										121
									
								
								model.go
									
									
									
									
									
								
							| @ -24,6 +24,22 @@ type Model struct { | ||||
| 	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 | ||||
| } | ||||
| 
 | ||||
| // HasID is a simple interface that you must implement
 | ||||
| // in your models, using a pointer receiver.
 | ||||
| // This allows for more flexibility in cases where
 | ||||
| @ -34,9 +50,11 @@ type HasID interface { | ||||
| 	Id() any | ||||
| 	SetId(id any) | ||||
| } | ||||
| 
 | ||||
| type HasIDSlice []HasID | ||||
| 
 | ||||
| type IModel interface { | ||||
| 	Append(field string, a ...interface{}) error | ||||
| 	Find(query interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error) | ||||
| 	FindAll(query interface{}, opts ...*options.FindOptions) (*Query, error) | ||||
| 	FindByID(id interface{}) (*Query, error) | ||||
| @ -50,12 +68,21 @@ type IModel interface { | ||||
| 	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) setTypeName(str string) { | ||||
| 	m.typeName = str | ||||
| } | ||||
| 
 | ||||
| func (m *Model) setSelf(arg interface{}) { | ||||
| 	m.self = arg | ||||
| } | ||||
| 
 | ||||
| func (m *Model) getExists() bool { | ||||
| 	return m.exists | ||||
| } | ||||
| @ -169,22 +196,32 @@ func (m *Model) FindOne(query interface{}, options ...*options.FindOneOptions) ( | ||||
| 	m.self = qq.doc | ||||
| 	return qq, err | ||||
| } | ||||
| func doDelete(m *Model, arg interface{}) error { | ||||
| 	self, ok := arg.(HasID) | ||||
| 
 | ||||
| func (m *Model) DeleteOne() error { | ||||
| 	c := m.getColl() | ||||
| 	if valueOf(m.self).Kind() == reflect.Slice { | ||||
| 	} | ||||
| 	id, ok := m.self.(HasID) | ||||
| 	if !ok { | ||||
| 		id2, ok2 := m.self.(HasIDSlice) | ||||
| 		if !ok2 { | ||||
| 			return fmt.Errorf("model '%s' is not registered", m.typeName) | ||||
| 		return fmt.Errorf("Object '%s' does not implement HasID", nameOf(arg)) | ||||
| 	} | ||||
| 	c := m.getColl() | ||||
| 	_, err := c.DeleteOne(context.TODO(), bson.M{"_id": self.Id()}) | ||||
| 	if err == nil { | ||||
| 		m.exists = false | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 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 | ||||
| 			} | ||||
| 		} | ||||
| 		_, err := c.DeleteOne(context.TODO(), bson.M{"_id": id2[0].Id()}) | ||||
| 		return err | ||||
| 		return nil | ||||
| 	} else { | ||||
| 		_, err := c.DeleteOne(context.TODO(), bson.M{"_id": id.Id()}) | ||||
| 		return err | ||||
| 		return doDelete(m, m.self) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -194,24 +231,24 @@ func (m *Model) DeleteOne() error { | ||||
| // slice.
 | ||||
| func (m *Model) Append(field string, a ...interface{}) error { | ||||
| 	rv := reflect.ValueOf(m.self) | ||||
| 	ref := rv | ||||
| 	selfRef := rv | ||||
| 	rt := reflect.TypeOf(m.self) | ||||
| 	if ref.Kind() == reflect.Pointer { | ||||
| 		ref = ref.Elem() | ||||
| 	if selfRef.Kind() == reflect.Pointer { | ||||
| 		selfRef = selfRef.Elem() | ||||
| 		rt = rt.Elem() | ||||
| 	} | ||||
| 	if ref.Kind() == reflect.Slice { | ||||
| 	if selfRef.Kind() == reflect.Slice { | ||||
| 		return fmt.Errorf("Cannot append to multiple documents!") | ||||
| 	} | ||||
| 	if ref.Kind() != reflect.Struct { | ||||
| 	if selfRef.Kind() != reflect.Struct { | ||||
| 		return fmt.Errorf("Current object is not a struct!") | ||||
| 	} | ||||
| 	_, ofv, err := getNested(field, ref) | ||||
| 	_, origV, err := getNested(field, selfRef) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	oofv := makeSettable(*ofv, (*ofv).Interface()) | ||||
| 	fv := oofv | ||||
| 	origRef := makeSettable(*origV, (*origV).Interface()) | ||||
| 	fv := origRef | ||||
| 	if fv.Kind() == reflect.Pointer { | ||||
| 		fv = fv.Elem() | ||||
| 	} | ||||
| @ -220,29 +257,31 @@ func (m *Model) Append(field string, a ...interface{}) error { | ||||
| 	} | ||||
| 
 | ||||
| 	for _, b := range a { | ||||
| 		va := reflect.ValueOf(b) | ||||
| 		fv.Set(reflect.Append(fv, va)) | ||||
| 		val := reflect.ValueOf(b) | ||||
| 		fv.Set(reflect.Append(fv, val)) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (m *Model) Save() error { | ||||
| func doSave(c *mongo.Collection, isNew bool, arg interface{}) error { | ||||
| 	var err error | ||||
| 	c := m.getColl() | ||||
| 	m, ok := arg.(IModel) | ||||
| 	if !ok { | ||||
| 		return fmt.Errorf("type '%s' is not a model", nameOf(arg)) | ||||
| 	} | ||||
| 	m.setSelf(m) | ||||
| 	now := time.Now() | ||||
| 	selfo := reflect.ValueOf(m.self) | ||||
| 	selfo := reflect.ValueOf(m) | ||||
| 	vp := selfo | ||||
| 	if vp.Kind() != reflect.Ptr { | ||||
| 		vp = reflect.New(selfo.Type()) | ||||
| 		vp.Elem().Set(selfo) | ||||
| 	} | ||||
| 	var asHasId = vp.Interface().(HasID) | ||||
| 	(asHasId).Id() | ||||
| 	isNew := reflect.ValueOf(asHasId.Id()).IsZero() && !m.exists | ||||
| 	if isNew || !m.exists { | ||||
| 		m.Created = now | ||||
| 	if isNew { | ||||
| 		m.setCreated(now) | ||||
| 	} | ||||
| 	m.Modified = now | ||||
| 	m.setModified(now) | ||||
| 	idxs := m.getIdxs() | ||||
| 	for _, i := range idxs { | ||||
| 		_, err = c.Indexes().CreateOne(context.TODO(), *i) | ||||
| @ -250,7 +289,7 @@ func (m *Model) Save() error { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	if isNew || !m.exists { | ||||
| 	if isNew { | ||||
| 		nid := getLastInColl(c.Name(), asHasId.Id()) | ||||
| 		switch pnid := nid.(type) { | ||||
| 		case uint: | ||||
| @ -276,17 +315,31 @@ func (m *Model) Save() error { | ||||
| 			(asHasId).SetId(nid) | ||||
| 		} | ||||
| 
 | ||||
| 		m.self = asHasId | ||||
| 		_, err = c.InsertOne(context.TODO(), m.serializeToStore()) | ||||
| 		if err == nil { | ||||
| 			m.exists = true | ||||
| 			m.setExists(true) | ||||
| 		} | ||||
| 	} else { | ||||
| 		_, err = c.ReplaceOne(context.TODO(), bson.D{{Key: "_id", Value: m.self.(HasID).Id()}}, m.serializeToStore()) | ||||
| 		_, err = c.ReplaceOne(context.TODO(), bson.D{{Key: "_id", Value: m.(HasID).Id()}}, m.serializeToStore()) | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| 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() bson.M { | ||||
| 	return serializeIDs((*m).self) | ||||
| } | ||||
|  | ||||
| @ -36,13 +36,11 @@ func TestPopulate(t *testing.T) { | ||||
| 
 | ||||
| 	bandDoc := Create(iti_single.Chapters[0].Bands[0]).(*band) | ||||
| 	storyDoc := Create(iti_single).(*story) | ||||
| 	author := Create(author).(*user) | ||||
| 
 | ||||
| 	err := bandDoc.Save() | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	storyDoc.Author = author | ||||
| 	err = storyDoc.Save() | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	mauthor := Create(author).(*user) | ||||
| 	saveDoc(t, mauthor) | ||||
| 	saveDoc(t, bandDoc) | ||||
| 	storyDoc.Author = mauthor | ||||
| 	saveDoc(t, storyDoc) | ||||
| 	assert.Greater(t, storyDoc.ID, int64(0)) | ||||
| 
 | ||||
| 	smodel := Create(story{}).(*story) | ||||
| @ -54,32 +52,21 @@ func TestPopulate(t *testing.T) { | ||||
| 		j, _ := q.JSON() | ||||
| 		fmt.Printf("%s\n", j) | ||||
| 	}) | ||||
| 	assert.NotZero(t, storyDoc.Chapters[0].Bands[0].Name) | ||||
| } | ||||
| 
 | ||||
| func TestUpdate(t *testing.T) { | ||||
| 	initTest() | ||||
| 	nb := Create(band{ | ||||
| 		ID:   1, | ||||
| 		Name: "Metallica", | ||||
| 		Characters: []string{ | ||||
| 			"James Hetfield", | ||||
| 			"Lars Ulrich", | ||||
| 			"Kirk Hammett", | ||||
| 			"Cliff Burton", | ||||
| 		}, | ||||
| 		Locked: false, | ||||
| 	}).(*band) | ||||
| 	err := nb.Save() | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	nb := Create(metallica).(*band) | ||||
| 	saveDoc(t, nb) | ||||
| 	nb.Locked = true | ||||
| 	err = nb.Save() | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	saveDoc(t, nb) | ||||
| 
 | ||||
| 	foundM := Create(band{}).(*band) | ||||
| 	q, err := foundM.FindByID(int64(1)) | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	found := &band{} | ||||
| 	q.Exec(found) | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	assert.Equal(t, int64(1), found.ID) | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	assert.Equal(t, true, found.Locked) | ||||
| @ -87,6 +74,7 @@ func TestUpdate(t *testing.T) { | ||||
| 
 | ||||
| func TestModel_FindAll(t *testing.T) { | ||||
| 	initTest() | ||||
| 	createAndSave(t, &iti_multi) | ||||
| 	smodel := Create(story{}).(*story) | ||||
| 	query, err := smodel.FindAll(bson.M{}, options.Find()) | ||||
| 	assert.Equal(t, nil, err) | ||||
| @ -97,30 +85,43 @@ func TestModel_FindAll(t *testing.T) { | ||||
| 
 | ||||
| func TestModel_PopulateMulti(t *testing.T) { | ||||
| 	initTest() | ||||
| 	bandDoc := Create(iti_single.Chapters[0].Bands[0]).(*band) | ||||
| 	saveDoc(t, bandDoc) | ||||
| 	createAndSave(t, &iti_multi) | ||||
| 	smodel := Create(story{}).(*story) | ||||
| 	query, err := smodel.FindAll(bson.M{}, options.Find()) | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	final := make([]story, 0) | ||||
| 	query.Populate("Author", "Chapters.Bands").Exec(&final) | ||||
| 	assert.Greater(t, len(final), 0) | ||||
| 	for _, s := range final { | ||||
| 		assert.NotZero(t, s.Chapters[0].Bands[0].Name) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestModel_Append(t *testing.T) { | ||||
| 	initTest() | ||||
| 	bandDoc := Create(metallica).(*band) | ||||
| 	saveDoc(t, bandDoc) | ||||
| 	bmodel := Create(band{}).(*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") | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	err = bmodel.Save() | ||||
| 	assert.Equal(t, nil, err) | ||||
| 	saveDoc(t, bmodel) | ||||
| 	fin = &band{} | ||||
| 	query, _ = bmodel.FindByID(int64(1)) | ||||
| 	query.Exec(fin) | ||||
| 	assert.Greater(t, len(fin.Characters), 4) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func TestModel_Delete(t *testing.T) { | ||||
| 	initTest() | ||||
| 	bandDoc := Create(metallica).(*band) | ||||
| 	saveDoc(t, bandDoc) | ||||
| 	err := bandDoc.Delete() | ||||
| 	assert.Nil(t, err) | ||||
| } | ||||
|  | ||||
							
								
								
									
										43
									
								
								testing.go
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								testing.go
									
									
									
									
									
								
							| @ -3,6 +3,8 @@ package orm | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/go-loremipsum/loremipsum" | ||||
| @ -15,7 +17,7 @@ import ( | ||||
| type chapter struct { | ||||
| 	ID            primitive.ObjectID `bson:"_id" json:"_id"` | ||||
| 	Title         string             `bson:"chapterTitle" json:"chapterTitle" form:"chapterTitle"` | ||||
| 	ChapterID     int                `bson:"id" json:"chapterID"` | ||||
| 	ChapterID     int                `bson:"id" json:"chapterID" autoinc:"chapters"` | ||||
| 	Index         int                `bson:"index" json:"index" form:"index"` | ||||
| 	Words         int                `bson:"words" json:"words"` | ||||
| 	Notes         string             `bson:"notes" json:"notes" form:"notes"` | ||||
| @ -112,12 +114,7 @@ func genChaps(single bool) []chapter { | ||||
| 			{"Sean Harris", "Colin Kimberley", "Brian Tatler"}, | ||||
| 		}, | ||||
| 	} | ||||
| 	var b band = band{ | ||||
| 		Name:       "Diamond Head", | ||||
| 		Locked:     false, | ||||
| 		Characters: []string{"Brian Tatler", "Sean Harris", "Duncan Scott", "Colin Kimberley"}, | ||||
| 	} | ||||
| 	b.ID = 503 | ||||
| 
 | ||||
| 	for i := 0; i < ceil; i++ { | ||||
| 		spf := fmt.Sprintf("%d.md", i+1) | ||||
| 		ret = append(ret, chapter{ | ||||
| @ -126,7 +123,7 @@ func genChaps(single bool) []chapter { | ||||
| 			Words:         50, | ||||
| 			Notes:         "notenotenote !!!", | ||||
| 			Genre:         []string{"Slash"}, | ||||
| 			Bands:         []band{b}, | ||||
| 			Bands:         []band{dh}, | ||||
| 			Characters:    []string{"Sean Harris", "Brian Tatler", "Duncan Scott", "Colin Kimberley"}, | ||||
| 			Relationships: relMap[i], | ||||
| 			Adult:         true, | ||||
| @ -156,6 +153,7 @@ func initTest() { | ||||
| 	uri := "mongodb://127.0.0.1:27017" | ||||
| 	db := "rockfic_ormTest" | ||||
| 	ic, _ := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri)) | ||||
| 	ic.Database(db).Drop(context.TODO()) | ||||
| 	colls, _ := ic.Database(db).ListCollectionNames(context.TODO(), bson.M{}) | ||||
| 	if len(colls) < 1 { | ||||
| 		mdb := ic.Database(db) | ||||
| @ -172,3 +170,32 @@ func after() { | ||||
| 	err := DBClient.Disconnect(context.TODO()) | ||||
| 	panik(err) | ||||
| } | ||||
| 
 | ||||
| var metallica = band{ | ||||
| 	ID:   1, | ||||
| 	Name: "Metallica", | ||||
| 	Characters: []string{ | ||||
| 		"James Hetfield", | ||||
| 		"Lars Ulrich", | ||||
| 		"Kirk Hammett", | ||||
| 		"Cliff Burton", | ||||
| 	}, | ||||
| 	Locked: false, | ||||
| } | ||||
| 
 | ||||
| var dh = band{ | ||||
| 	ID:         503, | ||||
| 	Name:       "Diamond Head", | ||||
| 	Locked:     false, | ||||
| 	Characters: []string{"Brian Tatler", "Sean Harris", "Duncan Scott", "Colin Kimberley"}, | ||||
| } | ||||
| 
 | ||||
| func saveDoc(t *testing.T, doc IModel) { | ||||
| 	err := doc.Save() | ||||
| 	assert.Nil(t, err) | ||||
| } | ||||
| 
 | ||||
| func createAndSave(t *testing.T, doc IModel) { | ||||
| 	mdl := Create(doc).(IModel) | ||||
| 	saveDoc(t, mdl) | ||||
| } | ||||
|  | ||||
							
								
								
									
										10
									
								
								util.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								util.go
									
									
									
									
									
								
							| @ -30,15 +30,7 @@ func nameOf(i interface{}) string { | ||||
| 
 | ||||
| func valueOf(i interface{}) reflect.Value { | ||||
| 	v := reflect.ValueOf(i) | ||||
| 	if v.Type().Kind() == reflect.Slice || v.Type().Kind() == reflect.Map { | ||||
| 		in := v.Type().Elem() | ||||
| 		switch in.Kind() { | ||||
| 		case reflect.Pointer: | ||||
| 			v = reflect.New(in.Elem()).Elem() | ||||
| 		default: | ||||
| 			v = reflect.New(in).Elem() | ||||
| 		} | ||||
| 	} else if v.Type().Kind() == reflect.Pointer { | ||||
| 	if v.Type().Kind() == reflect.Pointer { | ||||
| 		v = valueOf(reflect.Indirect(v).Interface()) | ||||
| 	} | ||||
| 	return v | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user