Skip to content

Commit

Permalink
refactor(project): Move model and handler to mongo 🎨
Browse files Browse the repository at this point in the history
I've refactored the project, putting model and handler package with the
other things on mongo package. That makes the job of using the package
much easier.
  • Loading branch information
ddspog committed Apr 16, 2018
1 parent 163fd30 commit 08d4c38
Show file tree
Hide file tree
Showing 21 changed files with 679 additions and 1,030 deletions.
210 changes: 197 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ go get github.com/ddspog/mongo

## How to use

This package mask the connection to MongoDB using mgo package.

This is made with function Connect, that saves Session and Mongo object which will be used later from other packages. Also, I've embedded the Collection, Database and Query types, to allow mocking via interfaces. The embedded was necessary for the functions to use the interfaces as return values, that way, the code can use the original, or generate a mock of them for testing purposes.

The package can be used like this:

```go
Expand Down Expand Up @@ -47,7 +51,7 @@ variable, but when it's not defined, it uses a default URL:

mongodb://localhost:27017/test

## Mocking
### Mocking

You can mock some functions of this package, by mocking the mgo
called functions mgo.ParseURL and mgo.Dial. Use the MockMongoSetup
Expand All @@ -66,28 +70,208 @@ if err := mongo.Connect(); err != nil {
}
```

## Documenter

Mongo package also contain utility functions to help modeling documents.

The package contains a interface Documenter which contain getters for important attributes to any document on MongoDB: _id, created_on and updated_on. It also contains functions that generates correctly the created_on and updated_on attributes.

The Documenter can be used like this:

```go
// Create a type representing the Document type
type Product struct {
IDV bson.ObjectId `json:"_id,omitempty" bson:"_id,omitempty"`
CreatedOnV int64 `json:"created_on,omitempty" bson:"created_on,omitempty"`
UpdatedOnV int64 `json:"updated_on,omitempty" bson:"updated_on,omitempty"`
NameV string `json:"name" form:"name" binding:"required" bson:"name"`
PriceV float32 `json:"price" form:"price" binding:"required" bson:"price"`
}

// Implement the Documenter interface.
func (p *Product) ID() (id bson.ObjectId) {
id = p.IDV
return
}

func (p *Product) CreatedOn() (t int64) {
t = p.CreatedOnV
return
}

func (p *Product) UpdatedOn() (t int64) {
t = p.UpdatedOnV
return
}

func (p *Product) New() (doc mongo.Documenter) {
doc = &Product{}
return
}

// On these methods, you can use the functions implemented mongo
// package.
func (p *Product) Map() (out bson.M, err error) {
out, err = mongo.MapDocumenter(p)
return
}

func (p *Product) Init(in bson.M) (err error) {
var doc mongo.Documenter = p
err = mongo.InitDocumenter(in, &doc)
return
}

func (p *Product) GenerateID() {
p.IDV = mongo.NewID()
}

func (p *Product) CalculateCreatedOn() {
p.CreatedOnV = mongo.NowInMilli()
}

func (p *Product) CalculateUpdatedOn() {
p.UpdatedOnV = mongo.NowInMilli()
}

// Create a product variable, and try its methods.
p := Product{}
p.CalculateCreatedOn()
t := p.CreatedOn()
```

You can also mock some other functions of this package, by mocking some called functions time.Now and bson.NewObjectId. Use the MockModelSetup presented on this package (only in test environment), like:

```go
create, _ := mongo.NewMockModelSetup(t)
defer create.Finish()

create.Now().Returns(time.Parse("02-01-2006", "22/12/2006"))
create.NewID().Returns(bson.ObjectIdHex("anyID"))

var d mongo.Documenter
// Call any needed methods ...
d.GenerateID()
d.CalculateCreatedOn()
```

## Handle

Mongo package also enable creation of Handle, a type that connects to database collections and do some operations.

The Handle were made to be imported on embedding type, and through overriding of some methods, to implement an adequate Handler for a desired type of Document. The Handle type assumes to operate on a Documenter type, that will contain information about the operation to made with Handle.

The package should be used to create new types. Use the Handler type for creating embedding types.

```go
type ProductHandle struct {
*mongo.Handle
DocumentV *product.Product
}
```

For each new type, a constructor may be needed, and for that Handler has a basic constructor.

```go
func New() (p *ProductHandle) {
p = &ProductHandle{
Handle: mongo.NewHandle(),
DocumentV: product.New(),
}
return
}
```

All functions were made to be overridden and rewrite. First thing to do it's creating a Name function.

```go
func (p *ProductHandle) Name() (n string) {
n = "products"
return
}
```

With Name function, the creation of Link method goes as it follows:

```go
func (p *ProductHandle) Link(db mongo.Databaser) (h *ProductHandle) {
p.Handle.Link(db, p.Name())
h = p
return
}
```

The creation of Insert, Remove and RemoveAll are trivial. Call it with a Document getter function defined like:

```go
func (p *ProductHandle) Document() (d *product.Product) {
d = p.DocumentV
return
}

func (p *ProductHandle) Insert() (err error) {
err = p.Handle.Insert(p.Document())
return
}
```

The Clean function is simple and helps a lot:

```go
func (p *ProductHandle) Clean() {
p.Handle.Clean()
p.DocumentV = product.New()
}
```

The Update function uses an id as an argument:

```go
func (p *ProductHandle) Update(id bson.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)
prod = doc.(*product.Product)
return
}

func (p *ProductHandle) FindAll() (proda []*product.Product, err error) {
var da []mongo.Documenter
err = p.Handle.FindAll(p.Document(), &da)
proda = make([]*product.Product, len(da))
for i := range da {
//noinspection GoNilContainerIndexing
proda[i] = da[i].(*product.Product)
}
return
}
```

For all functions written, verification it's advisable.

## Testing

This package contains a nice coverage with the unit tests, within the
objectives of the project.
This package contains a nice coverage with the unit tests, within the objectives of the project.

The elements, embedded and mocks sub-packages have low coverage because
they fulfill a need to mock mgo elements. These packages only embedded
mgo objects to mock, and by this a lot of unused functions were created
to fulfill interface requisites.
The elements, embedded and mocks sub-packages have low coverage because they fulfill a need to mock mgo elements. These packages only embedded mgo objects to mock, and by this a lot of unused functions were created to fulfill interface requisites.

On the other hand, model, handler and mongo package have full coverage,
being the focus of this project.
On the other hand, model, handler and mongo package have full coverage, being the focus of this project.

The project also contains a set of acceptance tests. I've have set the
test-acceptance task with the commands to run it. These tests requires
a mongo test database to be available. It creates, search and remove
elements from it, being reusable without broking the database.
The project also contains a set of acceptance tests. I've have set the test-acceptance task with the commands to run it. These tests requires a mongo test database to be available. It creates, search and remove elements from it, being reusable without broking the database.

## Contribution

This package has some objectives from now:

* Being incorporate on mgo package (possible fork) on possible future.
* Incorporate any new ideas about possible improvements.

Any interest in help is much appreciated.
16 changes: 1 addition & 15 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,3 @@
test-handler:
desc: Run handler tests.
cmds:
- echo "Calling tests handler execution ..."
- go test {{.REPO_PATH}}/handler -v --cover
silent: true

test-model:
desc: Run model tests.
cmds:
- echo "Calling tests model execution ..."
- go test {{.REPO_PATH}}/model -v --cover
silent: true

test-acceptance:
desc: Run acceptance tests with a real mongo instance running.
cmds:
Expand All @@ -34,7 +20,7 @@ cover:
- go tool cover -html=coverage.out

test:
deps: [test-handler, test-model, test-mongo]
deps: [test-mongo]
desc: Run all tests.

gen-mock-collection:
Expand Down
38 changes: 35 additions & 3 deletions acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"os"
"testing"

"github.com/ddspog/mongo/model"
"github.com/ddspog/mongo/elements"
"github.com/ddspog/mspec/bdd"
"github.com/globalsign/mgo/bson"
)
Expand All @@ -20,6 +20,37 @@ const (
testid04 = "000000007465737469643034"
)

func newDBSocket() (db databaseSocketer) {
db = &databaseSocket{
db: make(chan elements.Databaser),
quit: make(chan bool),
}
return
}

type databaseSocketer interface {
DB() elements.Databaser
Close()
}

type databaseSocket struct {
db chan elements.Databaser
quit chan bool
}

func (d *databaseSocket) DB() (db elements.Databaser) {
go ConsumeDatabaseOnSession(func(db elements.Databaser) {
d.db <- db
<-d.quit
})

return <-d.db
}

func (d *databaseSocket) Close() {
d.quit <- true
}

// Feature Manipulate data on MongoDB
// - As a developer,
// - I want to be able to connect and manipulate data from MongoDB,
Expand Down Expand Up @@ -132,7 +163,7 @@ func Test_Manipulate_data_on_MongoDB(t *testing.T) {
})
})

now := model.NowInMilli()
now := NowInMilli()

when(fmt.Sprintf("using p.Update() to change created_on doc with id '%[1]s' to '%[2]v'", newId.Hex(), now), func(it bdd.It) {
p.DocumentV = newProduct()
Expand Down Expand Up @@ -219,7 +250,8 @@ func Test_Real_connection_to_MongoDB(t *testing.T) {
assert.NotNil(db)
})

p, err := newProductHandle().Link(db)
p := newProductHandle()
err = p.Link(db)

it("should link correctly with products collection", func(assert bdd.Assert) {
assert.NoError(err)
Expand Down
2 changes: 2 additions & 0 deletions connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ func Connect() (err error) {
return
}

// Disconnect undo the connection made. Preparing package for a new
// connection.
func Disconnect() {
once = *new(sync.Once)
session = new(embedded.Session)
Expand Down
4 changes: 2 additions & 2 deletions connect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func Test_Connection_with_MongoDB(t *testing.T) {

ConsumeDatabaseOnSession(func(db elements.Databaser) {
p := newProductHandle()
_, errLink = p.Link(db)
errLink = p.Link(db)
n, errCount = p.Count()
})

Expand Down Expand Up @@ -136,7 +136,7 @@ func Test_Connect_only_with_valid_URLs(t *testing.T) {
// - As a developer,
// - I want that MockMongoSetup returns an error when receiving a nil test element,
// - So that I could restrain the use of this Setup only to tests.
func Test_MockModelSetup_works_only_on_Tests(t *testing.T) {
func Test_MockMongoSetup_works_only_on_Tests(t *testing.T) {
given, _, _ := bdd.Sentences()

given(t, "the start of the test", func(when bdd.When) {
Expand Down
Loading

0 comments on commit 08d4c38

Please sign in to comment.