{"id":13480693,"url":"https://github.com/go-bongo/bongo","last_synced_at":"2025-03-27T11:30:46.948Z","repository":{"id":23474567,"uuid":"26839307","full_name":"go-bongo/bongo","owner":"go-bongo","description":"Go ODM for MongoDB","archived":false,"fork":false,"pushed_at":"2021-01-01T22:46:04.000Z","size":161,"stargazers_count":486,"open_issues_count":14,"forks_count":40,"subscribers_count":21,"default_branch":"master","last_synced_at":"2024-10-30T14:42:25.960Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/go-bongo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-11-19T01:49:15.000Z","updated_at":"2024-10-19T18:52:42.000Z","dependencies_parsed_at":"2022-08-22T00:10:10.944Z","dependency_job_id":null,"html_url":"https://github.com/go-bongo/bongo","commit_stats":null,"previous_names":["maxwellhealth/bongo"],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-bongo%2Fbongo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-bongo%2Fbongo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-bongo%2Fbongo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-bongo%2Fbongo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/go-bongo","download_url":"https://codeload.github.com/go-bongo/bongo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245835902,"owners_count":20680289,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T17:00:43.764Z","updated_at":"2025-03-27T11:30:46.671Z","avatar_url":"https://github.com/go-bongo.png","language":"Go","funding_links":[],"categories":["Go","Libraries"],"sub_categories":["Go"],"readme":"# What's Bongo?\nWe couldn't find a good ODM for MongoDB written in Go, so we made one. Bongo is a wrapper for mgo (https://github.com/go-mgo/mgo) that adds ODM, hooks, validation, and cascade support to its raw Mongo functions.\n\nBongo is tested using the fantasic GoConvey (https://github.com/smartystreets/goconvey)\n\n[![Build Status](https://travis-ci.org/go-bongo/bongo.svg)](https://travis-ci.org/go-bongo/bongo)\n\n[![Coverage Status](https://coveralls.io/repos/go-bongo/bongo/badge.svg)](https://coveralls.io/r/go-bongo/bongo)\n\n# Stablity\n\nSince we're not yet at a major release, some things in the API might change. Here's a list:\n\n* Save - stable\n* Find/FindOne/FindById - stable\n* Delete - stable\n* Save/Delete/Find/Validation hooks - stable\n* Cascade - unstable (might need a refactor)\n* Change Tracking - stable\n* Validation methods - stable\n\n# Usage\n\n## Basic Usage\n### Import the Library\n`go get github.com/go-bongo/bongo`\n\n`import \"github.com/go-bongo/bongo\"`\n\nAnd install dependencies:\n\n`cd $GOHOME/src/github.com/go-bongo/bongo \u0026\u0026 go get .`\n\n### Connect to a Database\n\nCreate a new `bongo.Config` instance:\n\n```go\nconfig := \u0026bongo.Config{\n\tConnectionString: \"localhost\",\n\tDatabase:         \"bongotest\",\n}\n```\n\nThen just create a new instance of `bongo.Connection`, and make sure to handle any connection errors:\n\n```go\nconnection, err := bongo.Connect(config)\n\nif err != nil {\n\tlog.Fatal(err)\n}\n```\n\nIf you need to, you can access the raw `mgo` session with `connection.Session`\n\n### Create a Document\n\nAny struct can be used as a document as long as it satisfies the `Document` interface (`SetId(bson.ObjectId)`, `GetId() bson.ObjectId`). We recommend that you use the `DocumentBase` provided with Bongo, which implements that interface as well as the `NewTracker`, `TimeCreatedTracker` and `TimeModifiedTracker` interfaces (to keep track of new/existing documents and created/modified timestamps). If you use the `DocumentBase` or something similar, make sure you use `bson:\",inline\"` otherwise you will get nested behavior when the data goes to your database.\n\nFor example:\n\n```go\ntype Person struct {\n\tbongo.DocumentBase `bson:\",inline\"`\n\tFirstName string\n\tLastName string\n\tGender string\n}\n```\n\nYou can use child structs as well.\n\n```go\ntype Person struct {\n\tbongo.DocumentBase `bson:\",inline\"`\n\tFirstName string\n\tLastName string\n\tGender string\n\tHomeAddress struct {\n\t\tStreet string\n\t\tSuite string\n\t\tCity string\n\t\tState string\n\t\tZip string\n\t}\n}\n```\n\n#### Hooks\n\nYou can add special methods to your document type that will automatically get called by bongo during certain actions. Hooks get passed the current `*bongo.Collection` so you can avoid having to couple them with your actual database layer. Currently available hooks are:\n\n* `func (s *ModelStruct) Validate(*bongo.Collection) []error` (returns a slice of errors - if it is empty then it is assumed that validation succeeded)\n* `func (s *ModelStruct) BeforeSave(*bongo.Collection) error`\n* `func (s *ModelStruct) AfterSave(*bongo.Collection) error`\n* `func (s *ModelStruct) BeforeDelete(*bongo.Collection) error`\n* `func (s *ModelStruct) AfterDelete(*bongo.Collection) error`\n* `func (s *ModelStruct) AfterFind(*bongo.Collection) error`\n\n### Saving Models\n\nJust call `save` on a collection instance.\n\n```go\nmyPerson := \u0026Person{\n\tFirstName:\"Testy\",\n\tLastName:\"McGee\",\n\tGender:\"male\",\n}\nerr := connection.Collection(\"people\").Save(myPerson)\n```\n\nNow you'll have a new document in the `people` collection. If there is an error, you can check if it is a validation error using a type assertion:\n\n```go\nif vErr, ok := err.(*bongo.ValidationError); ok {\n\tfmt.Println(\"Validation errors are:\", vErr.Errors)\n} else {\n\tfmt.Println(\"Got a real error:\", err.Error())\n}\n```\n\n### Deleting Documents\n\nThere are three ways to delete a document.\n\n#### DeleteDocument\nSame thing as `Save` - just call `DeleteDocument` on the collection and pass the document instance.\n```go\nerr := connection.Collection(\"people\").DeleteDocument(person)\n```\n\nThis *will* run the `BeforeDelete` and `AfterDelete` hooks, if applicable.\n\n#### DeleteOne\nThis just delegates to `mgo.Collection.Remove`. It will *not* run the `BeforeDelete` and `AfterDelete` hooks.\n\n```go\nerr := connection.Collection(\"people\").DeleteOne(bson.M{\"FirstName\":\"Testy\"})\n```\n\n#### Delete\nThis delegates to `mgo.Collection.RemoveAll`. It will *not* run the `BeforeDelete` and `AfterDelete` hooks.\n```go\nchangeInfo, err := connection.Collection(\"people\").Delete(bson.M{\"FirstName\":\"Testy\"})\nfmt.Printf(\"Deleted %d documents\", changeInfo.Removed)\n```\n\n\n### Find by ID\n\n```go\nperson := \u0026Person{}\nerr := connection.Collection(\"people\").FindById(bson.ObjectIdHex(StringId), person)\n```\n\nThe error returned can be a `DocumentNotFoundError` or a more low-level MongoDB error. To check, use a type assertion:\n\n```go\nif dnfError, ok := err.(*bongo.DocumentNotFoundError); ok {\n\tfmt.Println(\"document not found\")\n} else {\n\tfmt.Println(\"real error \" + err.Error())\n}\n```\n\n### Find\n\nFinds will return an instance of `ResultSet`, which you can then optionally `Paginate` and iterate through to get all results.\n\n```go\n\n// *bongo.ResultSet\nresults := connection.Collection(\"people\").Find(bson.M{\"firstName\":\"Bob\"})\n\nperson := \u0026Person{}\n\ncount := 0\n\nfor results.Next(person) {\n\tfmt.Println(person.FirstName)\n}\n```\n\nTo paginate, you can run `Paginate(perPage int, currentPage int)` on the result of `connection.Find()`. That will return an instance of `bongo.PaginationInfo`, with properties like `TotalRecords`, `RecordsOnPage`, etc.\n\nTo use additional functions like `sort`, `skip`, `limit`, etc, you can access the underlying mgo `Query` via `ResultSet.Query`.\n\n### Find One\nSame as find, but it will populate the reference of the struct you provide as the second argument.\n\n\n```go\n\nperson := \u0026Person{}\n\nerr := connection.Collection(\"people\").FindOne(bson.M{\"firstName\":\"Bob\"}, person)\n\nif err != nil {\n\tfmt.Println(err.Error())\n} else {\n\tfmt.Println(\"Found user:\", person.FirstName)\n}\n```\n\n## Change Tracking\nIf your model struct implements the `Trackable` interface, it will automatically track changes to your model so you can compare the current values with the original. For example:\n\n```go\ntype MyModel struct {\n\tbongo.DocumentBase `bson:\",inline\"`\n\tStringVal string\n\tdiffTracker *bongo.DiffTracker\n}\n\n// Easy way to lazy load a diff tracker\nfunc (m *MyModel) GetDiffTracker() *DiffTracker {\n\tif m.diffTracker == nil {\n\t\tm.diffTracker = bongo.NewDiffTracker(m)\n\t}\n\n\treturn m.diffTracker\n}\n\nmyModel := \u0026MyModel{}\n```\n\nUse as follows:\n\n### Check if a field has been modified\n```go\n// Store the current state for comparison\nmyModel.GetDiffTracker().Reset()\n\n// Change a property...\nmyModel.StringVal = \"foo\"\n\n// We know it's been instantiated so no need to use GetDiffTracker()\nfmt.Println(myModel.diffTracker.Modified(\"StringVal\")) // true\nmyModel.diffTracker.Reset()\nfmt.Println(myModel.diffTracker.Modified(\"StringVal\")) // false\n```\n\n### Get all modified fields\n```go\nmyModel.StringVal = \"foo\"\n// Store the current state for comparison\nmyModel.GetDiffTracker().Reset()\n\nisNew, modifiedFields := myModel.GetModified()\n\nfmt.Println(isNew, modifiedFields) // false, [\"StringVal\"]\nmyModel.diffTracker.Reset()\n\nisNew, modifiedFields = myModel.GetModified()\nfmt.Println(isNew, modifiedFields) // false, []\n```\n\n### Diff-tracking Session\nIf you are going to be checking more than one field, you should instantiate a new `DiffTrackingSession` with `diffTracker.NewSession(useBsonTags bool)`. This will load the changed fields into the session. Otherwise with each call to `diffTracker.Modified()`, it will have to recalculate the changed fields.\n\n\n## Cascade Save/Delete\nBongo supports cascading portions of documents to related documents and the subsequent cleanup upon deletion. For example, if you have a `Team` collection, and each team has an array of `Players`, you can cascade a player's first name and last name to his or her `team.Players` array on save, and remove that element in the array if you delete the player.\n\nTo use this feature, your struct needs to have an exported method called `GetCascade`, which returns an array of `*bongo.CascadeConfig`. Additionally, if you want to make use of the `OldQuery` property to remove references from previously related documents, you should probably alsotimplement the `DiffTracker` on your model struct (see above).\n\nYou can also leave `ThroughProp` blank, in which case the properties of the document will be cascaded directly onto the related document. This is useful when you want to cascade `ObjectId` properties or other references, but it is important that you keep in mind that these properties will be nullified on the related document when the main doc is deleted or changes references.\n\nAlso note that like the above hooks, the `GetCascade` method will be passed the instance of the `bongo.Collection` so you can keep your models decoupled from your database layer.\n\n### Casade Configuration\n```go\ntype CascadeConfig struct {\n\t// The collection to cascade to\n\tCollection *mgo.Collection\n\n\t// The relation type (does the target doc have an array of these docs [REL_MANY] or just reference a single doc [REL_ONE])\n\tRelType int\n\n\t// The property on the related doc to populate\n\tThroughProp string\n\n\t// The query to find related docs\n\tQuery bson.M\n\n\t// The data that constructs the query may have changed - this is to remove self from previous relations\n\tOldQuery bson.M\n\n\t// Properties that will be cascaded/deleted. Can (should) be in dot notation for nested properties. This is used to nullify properties when there is an OldQuery or if the document is deleted.\n\tProperties []string\n\n\t// The actual data that will be cascade\n\tData interface{}\n}\n```\n\n### Example\n```go\ntype ChildRef struct {\n\tId bson.ObjectId `bson:\"_id\" json:\"_id\"`\n\tName string\n}\nfunc (c *Child) GetCascade(collection *bongo.Collection) []*bongo.CascadeConfig {\n\tconnection := collection.Connection\n\trel := \u0026ChildRef {\n\t\tId:c.Id,\n\t\tName:c.Name,\n\t}\n\tcascadeSingle := \u0026bongo.CascadeConfig{\n\t\tCollection:  connection.Collection(\"parents\").Collection(),\n\t\tProperties:  []string{\"name\"},\n\t\tData:rel,\n\t\tThroughProp: \"child\",\n\t\tRelType:     bongo.REL_ONE,\n\t\tQuery: bson.M{\n\t\t\t\"_id\": c.ParentId,\n\t\t},\n\t}\n\n\tcascadeMulti := \u0026bongo.CascadeConfig{\n\t\tCollection:  connection.Collection(\"parents\").Collection(),\n\t\tProperties:  []string{\"name\"},\n\t\tData:rel,\n\t\tThroughProp: \"children\",\n\t\tRelType:     bongo.REL_MANY,\n\t\tQuery: bson.M{\n\t\t\t\"_id\": c.ParentId,\n\t\t},\n\t}\n\n\tif c.DiffTracker.Modified(\"ParentId\") {\n\n\t\torigId, _ := c.DiffTracker.GetOriginalValue(\"ParentId\")\n\t\tif origId != nil {\n\t\t\toldQuery := bson.M{\n\t\t\t\t\"_id\": origId,\n\t\t\t}\n\t\t\tcascadeSingle.OldQuery = oldQuery\n\t\t\tcascadeMulti.OldQuery = oldQuery\n\t\t}\n\n\t}\n\n\treturn []*bongo.CascadeConfig{cascadeSingle, cascadeMulti}\n}\n```\n\nThis does the following:\n\n1. When you save a child, it will populate its parent's (defined by `cascadeSingle.Query`) `child` property with an object, consisting of one key/value pair (`name`)\n\n2. When you save a child, it will also modify its parent's (defined by `cascadeMulti.Query`) `children` array, either modifying or pushing to the array of key/value pairs, also with just `name`.\n\n3. When you delete a child, it will use `cascadeSingle.OldQuery` to remove the reference from its previous `parent.child`\n\n4. When you delete a child, it will also use `cascadeMulti.OldQuery` to remove the reference from its previous `parent.children`\n\nNote that the `ThroughProp` must be the actual field name in the database (bson tag), not the property name on the struct. If there is no `ThroughProp`, the data will be cascaded directly onto the root of the document.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgo-bongo%2Fbongo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgo-bongo%2Fbongo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgo-bongo%2Fbongo/lists"}