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/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/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 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() } } 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 }