From f6e3f7c7ebba250e3b48689d891ebaac1fe7b8dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=AAnnis=20Dantas?= Date: Sat, 12 May 2018 21:52:20 -0300 Subject: [PATCH 1/3] feat(mongo): Add InternalErr to Handle :bulb: I've created a field representing an InternalErr inside Handler, to be checked for errors on every operation with the collection. I've also resumed the functions to make plenty of use of Handler, making embedding almost not needed. --- documenter.go | 1 + handle.go | 244 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 146 insertions(+), 99 deletions(-) diff --git a/documenter.go b/documenter.go index ab04194..c7a752f 100644 --- a/documenter.go +++ b/documenter.go @@ -6,6 +6,7 @@ package mongo // updated_on type Documenter interface { New() Documenter + Validate() error Map() (M, error) Init(M) error ID() ObjectId diff --git a/handle.go b/handle.go index 1749d30..60f0fd5 100644 --- a/handle.go +++ b/handle.go @@ -2,6 +2,7 @@ package mongo import ( "errors" + "reflect" "github.com/globalsign/mgo" ) @@ -9,6 +10,9 @@ import ( var ( // ErrIDNotDefined it's an error received when an ID isn't defined. ErrIDNotDefined = errors.New("ID not defined") + // DocNotDefined it's an error received when the document received + // is nil. + DocNotDefined = errors.New("Document not defined") ) // Handle it's a type implementing the Handler interface, responsible @@ -19,21 +23,27 @@ type Handle struct { collection *mgo.Collection collectionName string collectionIndexes []mgo.Index + DocumentV Documenter + InternalErr error SearchMapV M } // NewHandle creates a new Handle to be embedded onto handle for other -// types. It also accept optional indexes to be loaded onto collection. -func NewHandle(name string, indexes ...mgo.Index) (h *Handle) { +// types. It needs the name for collection to link, and a document not +// nil to perform some operations. It also accept optional indexes to +// be loaded onto collection. +func NewHandle(name string, doc Documenter, indexes ...mgo.Index) (h *Handle) { sk := NewSocket() + h = &Handle{ - collectionName: name, + safely: false, socket: sk, collection: sk.DB().C(name), - safely: false, + collectionName: name, collectionIndexes: indexes, } + h.SetDocument(doc) h.ensureIndexes() return } @@ -57,6 +67,10 @@ func (h *Handle) Safely() { func (h *Handle) Clean() { h.SearchMapV = make(map[string]interface{}) + if h.Document() != nil { + h.SetDocument(h.Document().New()) + } + h.Close() sk := NewSocket() h.socket = sk @@ -81,35 +95,31 @@ func (h *Handle) IsSearchEmpty() (result bool) { // Count returns the number of documents on collection connected to // Handle. func (h *Handle) Count() (n int, err error) { - n, err = h.collection.Count() + defer h.ifSafelyClose() - if h.safely { - h.Close() + if err = h.InternalErr; err == nil { + n, err = h.collection.Count() } + return } // Find search for a document matching the doc data on collection // connected to Handle. -func (h *Handle) Find(doc Documenter, out Documenter) (err error) { - var mapped M +func (h *Handle) Find() (out Documenter, err error) { + defer h.ifSafelyClose() - if h.IsSearchEmpty() { - mapped, err = doc.Map() - } else { - mapped = h.SearchMap() - } + if err = h.InternalErr; err == nil { + out = h.Document().New() - if err == nil { - var result interface{} - if err = h.collection.Find(mapped).One(&result); err == nil { - err = out.Init(result.(M)) + var mapped M + if mapped, err = h.mapped(); err == nil { + var result interface{} + if err = h.collection.Find(mapped).One(&result); err == nil { + err = out.Init(result.(M)) + } } } - - if h.safely { - h.Close() - } return } @@ -118,125 +128,143 @@ type QueryOptions struct { Sort []string } -// FindAll search for all documents matching the doc data on +// FindAll search for all documents matching the document data on // collection connected to Handle. Accepts options to alter result. -func (h *Handle) FindAll(doc Documenter, out *[]Documenter, opts ...QueryOptions) (err error) { - var mapped M - - if h.IsSearchEmpty() { - mapped, err = doc.Map() - } else { - mapped = h.SearchMap() - } +func (h *Handle) FindAll(opts ...QueryOptions) (out []Documenter, err error) { + defer h.ifSafelyClose() - if err == nil { - var result []interface{} - qry := h.collection.Find(mapped) + if err = h.InternalErr; err == nil { + var mapped M + if mapped, err = h.mapped(); err == nil { + var result []interface{} + qry := h.collection.Find(mapped) - if len(opts) == 1 { - if opts[0].Sort != nil { - qry = qry.Sort(opts[0].Sort...) + if len(opts) == 1 { + if opts[0].Sort != nil { + qry = qry.Sort(opts[0].Sort...) + } } - } - if err = qry.All(&result); err == nil { - tempArr := make([]Documenter, len(result)) - for i := range result { - //noinspection GoNilContainerIndexing - tempArr[i] = doc.New() - if err := tempArr[i].Init(result[i].(M)); err != nil { - break + if err = qry.All(&result); err == nil { + out = make([]Documenter, len(result)) + for i := 0; i < len(result) && err == nil; i++ { + out[i] = h.Document().New() + err = out[i].Init(result[i].(M)) } } - - *out = tempArr } } - if h.safely { - h.Close() - } return } // Insert puts a new document on collection connected to Handle, using -// doc data. -func (h *Handle) Insert(doc Documenter) (err error) { - if doc.ID().Hex() == "" { - doc.GenerateID() - } +// document data. +func (h *Handle) Insert() (err error) { + defer h.ifSafelyClose() - doc.CalculateCreatedOn() + if err = h.InternalErr; err == nil { + if h.Document().ID() == "" { + h.Document().GenerateID() + } - var mapped M - if mapped, err = doc.Map(); err == nil { - err = h.collection.Insert(mapped) - } + h.Document().CalculateCreatedOn() - if h.safely { - h.Close() + var mapped M + if mapped, err = h.mapped(); err == nil { + // Even if the new document were made with SearchFor, it + // add these attributes, since they're important. + mapped["_id"] = h.Document().ID() + mapped["created_on"] = h.Document().CreatedOn() + + err = h.collection.Insert(mapped) + } } + return } // Remove delete a document on collection connected to Handle, matching // id received. func (h *Handle) Remove(id ObjectId) (err error) { - if id == "" { - err = ErrIDNotDefined - } else { - err = h.collection.RemoveId(id) - } + defer h.ifSafelyClose() - if h.safely { - h.Close() + if err = h.InternalErr; err == nil { + if id == "" { + err = ErrIDNotDefined + } else { + err = h.collection.RemoveId(id) + } } + return } // RemoveAll delete all documents on collection connected to Handle, -// matching the doc data. -func (h *Handle) RemoveAll(doc Documenter) (info *mgo.ChangeInfo, err error) { - var mapped M +// matching the document data. +func (h *Handle) RemoveAll() (info *mgo.ChangeInfo, err error) { + defer h.ifSafelyClose() - if h.IsSearchEmpty() { - mapped, err = doc.Map() - } else { - mapped = h.SearchMap() - } - - if err == nil { - info, err = h.collection.RemoveAll(mapped) + if err = h.InternalErr; err == nil { + var mapped M + if mapped, err = h.mapped(); err == nil { + info, err = h.collection.RemoveAll(mapped) + } } - if h.safely { - h.Close() - } return } // Update updates a document on collection connected to Handle, // matching id received, updating with the information on doc. -func (h *Handle) Update(id ObjectId, doc Documenter) (err error) { - if id == "" { - err = ErrIDNotDefined - } else { - doc.CalculateUpdatedOn() +func (h *Handle) Update(id ObjectId) (err error) { + defer h.ifSafelyClose() + + if err = h.InternalErr; err == nil { + if id == "" { + err = ErrIDNotDefined + } else { + h.Document().CalculateUpdatedOn() + + var mapped M + if mapped, err = h.mapped(); err == nil { + delete(mapped, "_id") + mapped["updated_on"] = h.Document().UpdatedOn() + + idSelector := M{ + "_id": id, + } - var mapped M - if mapped, err = doc.Map(); err == nil { - delete(mapped, "_id") - idSelector := M{ - "_id": id, + err = h.collection.Update(idSelector, mapped) } - - err = h.collection.Update(idSelector, mapped) } } - if h.safely { - h.Close() + return +} + +// SetDocument sets product on Handle. +func (h *Handle) SetDocument(d Documenter) { + if reflect.ValueOf(d).IsNil() { + h.InternalErr = DocNotDefined + } else { + h.InternalErr = d.Validate() } + + h.DocumentV = d + return +} + +// Document returns the Document of Handle. +func (h *Handle) Document() (d Documenter) { + d = h.DocumentV + return +} + +// Set search map value for Handle and returns Handle for chaining +// purposes. +func (h *Handle) SearchFor(s M) { + h.SearchMapV = s return } @@ -249,7 +277,25 @@ func (h *Handle) SearchMap() (s M) { // ensureIndexes search for any loaded index on Handle, and set it on // collection. func (h *Handle) ensureIndexes() { - for _, index := range h.collectionIndexes { - h.collection.EnsureIndex(index) + for i := 0; i < len(h.collectionIndexes) && h.InternalErr == nil; i++ { + h.InternalErr = h.collection.EnsureIndex(h.collectionIndexes[i]) + } +} + +// mapped returns SearchMap if it isn't empty, or the Document mapped. +func (h *Handle) mapped() (m M, err error) { + if h.IsSearchEmpty() { + m, err = h.Document().Map() + } else { + m = h.SearchMap() + } + + return +} + +// ifSafelyClose checks if safely was activated to close socket. +func (h *Handle) ifSafelyClose() { + if h.safely { + h.Close() } } From 2edada0ca8945143dbbec46d3a1e712ac1113a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=AAnnis=20Dantas?= Date: Sat, 12 May 2018 21:59:03 -0300 Subject: [PATCH 2/3] test(mongo): Improve tests to 100% coverage :bulb: I've improved all tests, to cover 100% of coverage in project. --- acceptance_test.go | 4 +--- handle_test.go | 23 ++++++++++++++++----- helpers_test.go | 51 ++++++++++++---------------------------------- 3 files changed, 32 insertions(+), 46 deletions(-) diff --git a/acceptance_test.go b/acceptance_test.go index ea79a38..871987c 100644 --- a/acceptance_test.go +++ b/acceptance_test.go @@ -160,9 +160,7 @@ func Test_Manipulate_data_on_MongoDB(t *testing.T) { }) when(fmt.Sprintf("using p.Remove() to remove doc with id '%[1]s'", newId.Hex()), func(it bdd.It) { - errRemove := p.SetDocument(&product{ - IDV: newId, - }).Remove() + errRemove := p.Remove(newId) it("should return no errors", func(assert bdd.Assert) { assert.NoError(errRemove) diff --git a/handle_test.go b/handle_test.go index 958ad31..38829f5 100644 --- a/handle_test.go +++ b/handle_test.go @@ -66,6 +66,21 @@ func Test_Count_documents_with_Handle(t *testing.T) { }, like( s(len(fixtures)), )) + + given(t, "a ProductHandle with a nil document", func(when bdd.When) { + when("p.Count() is called", func(it bdd.It) { + n, err := newProductHandle().SetDocument(nil).Safely().Count() + + it("should an error", func(assert bdd.Assert) { + assert.Error(err) + assert.Equal(DocNotDefined.Error(), err.Error()) + }) + + it("should return 0", func(assert bdd.Assert) { + assert.Equal(0, n) + }) + }) + }) } // Feature Clean documents with Handle @@ -282,12 +297,10 @@ func Test_Remove_documents_with_Handle(t *testing.T) { given, like, s := bdd.Sentences() given(t, "a linked ProductHandle p with ID '%[1]v'", func(when bdd.When, args ...interface{}) { - p := newProductHandle().SetDocument(&product{ - IDV: args[0].(ObjectId), - }) + p := newProductHandle() - when("p.Remove() is called", func(it bdd.It) { - err := p.Safely().Remove() + when("p.Remove('%[1]v') is called", func(it bdd.It) { + err := p.Safely().Remove(args[0].(ObjectId)) if args[0].(ObjectId) != "" { it("should return no errors", func(assert bdd.Assert) { diff --git a/helpers_test.go b/helpers_test.go index f462a74..fd62b51 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -87,6 +87,12 @@ func (p *product) New() (doc Documenter) { return } +// Validate checks for initialization problems on product, and return +// error to be returned in any opration. +func (p *product) Validate() (err error) { + return +} + // Map translates a product to a M object, more easily read by mgo // methods. func (p *product) Map() (out M, err error) { @@ -142,16 +148,14 @@ func (p *product) CalculateUpdatedOn() { // of storing Products. type productHandle struct { *Handle - DocumentV *product } // newProductHandle returns a empty productHandle. func newProductHandle() (p *productHandle) { p = &productHandle{ - Handle: NewHandle("products", mgo.Index{ + Handle: NewHandle("products", newProduct(), mgo.Index{ Key: []string{"created_on"}, }), - DocumentV: newProduct(), } return } @@ -167,7 +171,6 @@ func (p *productHandle) Safely() (ph *productHandle) { // purposes. func (p *productHandle) Clean() (ph *productHandle) { p.Handle.Clean() - p.DocumentV = newProduct() ph = p return } @@ -175,8 +178,8 @@ func (p *productHandle) Clean() (ph *productHandle) { // Find search on connected collection for a document matching data // stored on productHandle and returns it. func (p *productHandle) Find() (prod *product, err error) { - var doc Documenter = newProduct() - err = p.Handle.Find(p.Document(), doc) + var doc Documenter + doc, err = p.Handle.Find() prod = doc.(*product) return } @@ -186,7 +189,7 @@ func (p *productHandle) Find() (prod *product, err error) { // query results. func (p *productHandle) FindAll(opts ...QueryOptions) (proda []*product, err error) { var da []Documenter - err = p.Handle.FindAll(p.Document(), &da, opts...) + da, err = p.Handle.FindAll(opts...) proda = make([]*product, len(da)) for i := range da { //noinspection GoNilContainerIndexing @@ -195,52 +198,24 @@ func (p *productHandle) FindAll(opts ...QueryOptions) (proda []*product, err err return } -// Insert creates a new document with data stored on productHandle -// and put on connected collection. -func (p *productHandle) Insert() (err error) { - err = p.Handle.Insert(p.Document()) - return -} - -// Remove delete a document from connected collection matching the id -// of data stored on Handle. -func (p *productHandle) Remove() (err error) { - err = p.Handle.Remove(p.Document().ID()) - return -} - -// Remove deletes all document from connected collection matching the -// data stored on Handle. -func (p *productHandle) RemoveAll() (info *mgo.ChangeInfo, err error) { - info, err = p.Handle.RemoveAll(p.Document()) - return -} - -// Update updates document from connected collection matching the id -// received, and uses document info to update. -func (p *productHandle) Update(id ObjectId) (err error) { - err = p.Handle.Update(id, p.Document()) - return -} - // SetDocument sets product on Handle and returns Handle for chaining // purposes. func (p *productHandle) SetDocument(d *product) (r *productHandle) { - p.DocumentV = d + p.Handle.SetDocument(d) r = p return } // Document returns the Document of Handle with correct type. func (p *productHandle) Document() (d *product) { - d = p.DocumentV + d = p.Handle.Document().(*product) return } // Set search map value for Handle and returns Handle for chaining // purposes. func (p *productHandle) SearchFor(s M) (r *productHandle) { - p.SearchMapV = s + p.Handle.SearchFor(s) r = p return } From dcb1efbe6c26f19f7cbc439bef24a142e158afd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=AAnnis=20Dantas?= Date: Sat, 12 May 2018 22:04:56 -0300 Subject: [PATCH 3/3] docs(mongo): Update documentation :memo: I've added information about the changes in Handler, to be used, now optionally as embedded struct. --- README.md | 49 +++++++++++-------------------------------------- doc.go | 46 ++++++++++++---------------------------------- 2 files changed, 23 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 397e0c3..fc3687a 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,10 @@ func (p *Product) New() (doc mongo.Documenter) { return } +func (p *Product) Validate() (err error) { + return +} + // On these methods, you can use the functions implemented mongo // package. func (p *Product) Map() (out M, err error) { @@ -182,7 +186,6 @@ The package should be used to create new types. Use the Handler type for creatin ```go type ProductHandle struct { *mongo.Handle - DocumentV *product.Product } ``` @@ -195,8 +198,7 @@ For each new type, a constructor may be needed, and for that Handler has a basic // } func New() (p *ProductHandle) { p = &ProductHandle{ - Handle: mongo.NewHandle(product.CollectionName, product.CollectionIndexes...), - DocumentV: product.New(), + Handle: mongo.NewHandle(product.CollectionName, product.New(), product.CollectionIndexes...), } return } @@ -213,7 +215,6 @@ func (p *ProductHandle) Safely() (ph *ProductHandle) { func (p *ProductHandle) Clean() (ph *ProductHandle) { p.Handle.Clean() - p.DocumentV = product.New() ph = p return } @@ -223,58 +224,30 @@ Create a Document, or SearchMap getter and setters functions improving use of Ha ```go func (p *ProductHandle) SetDocument(d *product.Product) (r *ProductHandle) { - p.DocumentV = d + p.Handle.SetDocument(d) r = p return } func (p *ProductHandle) Document() (d *product.Product) { - d = p.DocumentV + d = p.Handle.Document().(*product.Product) return } // Note that SearchMap, the getter is already defined on Handle. func (p *ProductHandle) SearchFor(s mongo.M) (r *ProductHandle) { - p.SearchMapV = s + p.Handle.SearchFor(s) r = p return } ``` -The creation of Insert, Remove and RemoveAll are trivial. - -```go -func (p *ProductHandle) Insert() (err error) { - err = p.Handle.Insert(p.Document()) - return -} - -func (p *ProductHandle) Remove() (err error) { - err = p.Handle.Remove(p.Document().ID()) - return -} - -func (p *ProductHandle) RemoveAll() (info *mgo.ChangeInfo, err error) { - info, err = p.Handle.RemoveAll(p.Document()) - return -} -``` - -The Update function uses an id as an argument: - -```go -func (p *ProductHandle) Update(id mongo.ObjectId) (err error) { - err = p.Handle.Update(id, p.Document()) - return -} -``` - The complicated functions are Find and FindAll which requires casting for the Document type: ```go func (p *ProductHandle) Find() (prod *product.Product, err error) { - var doc mongo.Documenter = product.New() - err = p.Handle.Find(p.Document(), doc) + var doc mongo.Documenter + doc, err = p.Handle.Find() prod = doc.(*product.Product) return } @@ -282,7 +255,7 @@ func (p *ProductHandle) Find() (prod *product.Product, err error) { // QueryOptions serve to add options on returning the query. func (p *ProductHandle) FindAll(opts ...mongo.QueryOptions) (proda []*product.Product, err error) { var da []mongo.Documenter - err = p.Handle.FindAll(p.Document(), &da, opts...) + err = p.Handle.FindAll(opts...) proda = make([]*product.Product, len(da)) for i := range da { //noinspection GoNilContainerIndexing diff --git a/doc.go b/doc.go index 31dbcd1..be7e366 100644 --- a/doc.go +++ b/doc.go @@ -131,6 +131,11 @@ The Documenter can be used like this: return } + func (p *Product) Validate() (err error) { + return + } + + // On these methods, you can use the functions implemented mongo // package. func (p *Product) Map() (out M, err error) { @@ -177,7 +182,6 @@ for creating embedding types. type ProductHandle struct { *mongo.Handle - DocumentV *product.Product } For each new type, a constructor may be needed, and for that Handler @@ -191,8 +195,7 @@ collection used on Handle, and optional indexes for collection. // } func New() (p *ProductHandle) { p = &ProductHandle{ - Handle: mongo.NewHandle(product.CollectionName, product.CollectionIndexes...), - DocumentV: product.New(), + Handle: mongo.NewHandle(product.CollectionName, product.New(), product.CollectionIndexes...), } return } @@ -209,7 +212,6 @@ optional), as it follows: func (p *ProductHandle) Clean() (ph *ProductHandle) { p.Handle.Clean() - p.DocumentV = product.New() ph = p return } @@ -218,53 +220,29 @@ Create a Document, or SearchMap getter and setters functions improving use of Handle: func (p *ProductHandle) SetDocument(d *product.Product) (r *ProductHandle) { - p.DocumentV = d + p.Handle.SetDocument(d) r = p return } func (p *ProductHandle) Document() (d *product.Product) { - d = p.DocumentV + d = p.Handle.Document().(*product.Product) return } // Note that SearchMap, the getter is already defined on Handle. func (p *ProductHandle) SearchFor(s mongo.M) (r *ProductHandle) { - p.SearchMapV = s + p.Handle.SearchFor(s) r = p return } -The creation of Insert, Remove and RemoveAll are trivial. - - func (p *ProductHandle) Insert() (err error) { - err = p.Handle.Insert(p.Document()) - return - } - - func (p *ProductHandle) Remove() (err error) { - err = p.Handle.Remove(p.Document().ID()) - return - } - - func (p *ProductHandle) RemoveAll() (info *mgo.ChangeInfo, err error) { - info, err = p.Handle.RemoveAll(p.Document()) - return - } - -The Update function uses an id as an argument: - - func (p *ProductHandle) Update(id mongo.ObjectId) (err error) { - err = p.Handle.Update(id, p.Document()) - return - } - The complicated functions are Find and FindAll which requires casting for the Document type: func (p *ProductHandle) Find() (prod *product.Product, err error) { - var doc mongo.Documenter = product.New() - err = p.Handle.Find(p.Document(), doc) + var doc mongo.Documenter + doc, err = p.Handle.Find() prod = doc.(*product.Product) return } @@ -272,7 +250,7 @@ for the Document type: // QueryOptions serve to add options on returning the query. func (p *ProductHandle) FindAll(opts ...mongo.QueryOptions) (proda []*product.Product, err error) { var da []mongo.Documenter - err = p.Handle.FindAll(p.Document(), &da, opts...) + err = p.Handle.FindAll(opts...) proda = make([]*product.Product, len(da)) for i := range da { //noinspection GoNilContainerIndexing