# diamond
### a golang ORM for mongodb that rocks 🎸~♬
# usage
## installation
run the following command in your terminal...
```shell
go get rockfic.com/orm
```
...and import the package at the top of your file(s) like so:
```go
package tutorial
import "rockfic.com/orm"
```
## Connect to the database
```go
package tutorial
import "rockfic.com/orm"
func main() {
orm.Connect("mongodb://127.0.0.1", "your_database")
}
```
this will create a connection and store it in the `DB` global variable. This global variable is used internally to
interact with the underlying database.
if you need to ~~why?~~, you may also access the mongoDB client directly via the `orm.Client`
variable.
## Create a Model
to create a new model, you need to define a struct like this:
```go
package tutorial
import "rockfic.com/orm"
type User struct {
orm.Document `bson:",inline" coll:"collection"`
ID int64 `bson:"_id"`
Username string `bson:"username"`
Email string `bson:"email"`
Friends []User `bson:"friends"`
}
```
this on its own is useless. to actually do anything useful with it, you need to *register* this struct as a model:
```go
package tutorial
import "rockfic.com/orm"
func main() {
/* ~ snip ~ */
orm.ModelRegistry.Model(User{})
}
```
you can also pass multiple arguments to `orm.Model`, so long as they embed the `Document` struct.
you can access the newly created model like this:
```go
package tutorial
import "rockfic.com/orm"
func main() {
/* ~ snip ~ */
userModel := orm.ModelRegistry.Get("User")
}
```
## Documents
any struct can be used as a document, so long as it embeds the `Document` struct. the `Document` struct is special, in
that it turns any structs which embed it into `IDocument` implementors.
`Document` should be embedded with a `bson:",inline" tag, otherwise you will end up with something like this in the
database:
```bson
{
"document": {
"createdAt": ISODate('2001-09-11T05:37:18.742Z'),
"updatedAt": ISODate('2001-09-11T05:37:18.742Z')
},
_id: 1,
username: "foobar",
email: "test@testi.ng",
friends: []
}
```
a `coll` or `collection` tag is also required, to assign the model to a mongodb collection. this tag is only valid on
the embedded `Document` field, and each document can only have one collection associated with it.
obviously.
i mean seriously, who'd want to store one thing in two places?
the recommended way to create a new document instance is via the `orm.Create` method:
```go
package tutorial
import "rockfic.com/orm"
func main() {
/* ~ snip ~ */
user := orm.Create(User{}).(*User)
}
```
similarly, to create a slice of documents, call `orm.CreateSlice`:
```go
package tutorial
import "rockfic.com/orm"
func main() {
/* ~ snip ~ */
users := orm.CreateSlice[User](User{})
}
```
lastly, let's implement the `HasID` interface on our document:
```go
package tutorial
import "rockfic.com/orm"
func (u *User) GetId() any {
return u.ID
}
func (u *User) SetId(id any) {
u.ID = id.(int64)
}
```
#### but why do i need to implement this? :(
other ORMs rudely assume that you want your `_id`s to be mongodb ObjectIDs, which are **fucking ugly**, but also, more
importantly, *may not match your existing schema.*
by doing things this way, you can set your _id to be any of the following types:
- int
- int32
- int64
- uint
- uint32
- uint64
- string
- ObjectId
(if you hate yourself that much)
## finding/querying
`Find`, `FindOne`, `FindByID`, and `FindPaged` all return an `*orm.Query` object.
to get the underlying value, simply call `.Exec()`, passing to it a pointer to the variable in which you wish to store
the results.
### `Find` example
```go
package tutorial
import (
"go.mongodb.org/mongo-driver/bson"
"rockfic.com/orm"
)
func main() {
/* ~ snip ~ */
userModel := orm.ModelRegistry.Get("User")
if userModel != nil {
res := orm.CreateSlice(User{})
jq, _ := userModel.Find(bson.M{"username": "foobar"})
q.Exec(&res)
}
}
```
## saving/updating
to save a document, simply call the `Save()` method. if the document doesn't exist, the ORM will create it, otherwise it
replaces the existing one.
## referencing other documents
it's possible to store references to other documents/structs, or slices to other documents. given the `User` struct we
defined above, which contains a slice (`Friends`) referencing other `User`s, we could change the `Friends` field in the
`User` type to be populateable, like so:
```diff
type User struct {
orm.Document `bson:",inline" coll:"collection"`
ID int64 `bson:"_id"`
Username string `bson:"username"`
Email string `bson:"email"`
- Friends []User `bson:"friends"`
+ Friends []User `bson:"friends" ref:"User"`
}
```
assuming we've filled a user's `Friends` with a few friends...
```go
package tutorial
import "rockfic.com/orm"
func main() {
/* ~ snip ~ */
user := orm.Create(User{}).(*User)
for i := 0; i < 10; i++ {
friend := orm.Create(User{ID: int64(i + 2)}).(*User)
user.Friends = append(user.Friends, friend)
}
}
```
...in the database, `friends` will be stored like this:
```json5
{
// ~ snip ~ //
friends: [
2,
3,
4,
5,
6,
7,
8,
9,
10,
11
]
}
```
after retrieving a `User` from the database, you can load their `Friends` and their info by using the `Populate`function
on the returned `Query` pointer, like in the following example:
```go
package tutorial
import (
"fmt"
"go.mongodb.org/mongo-driver/bson"
"rockfic.com/orm"
)
func main() {
/* ~ snip ~ */
query, err := userModel.FindOne(bson.M{"_id": 1})
if err == nil {
result := orm.Create(User{}).(*User)
query.Populate("Friends").Exec(result)
fmt.Printf("%+v", result)
}
}
```
### Models vs Documents: what's the difference?
a **`Model`** is a static type that contains information about a given Document-like type. a **`Document`**, on the
other hand, is a full-fledged instances with its own set of methods.
if you come from an object-oriented programming background, you can envision the relationship between a `Model` and
`Document` something like this (using Java as an example):
```java
public class Document {
public void Pull(field String, elements ...Document) {
// ...
}
public void Append(field String, elements ...Document) {
// ...
}
public void Save() {
//...
}
public void Delete() {
//...
}
// model methods //
public static Document FindOne(HashMap query, MongoOptions options) {
// ...
}
public static ArrayList FindAll(HashMap query, MongoOptions options) {
// ...
}
}
```
## Struct Tags
the following tags are recognized by the ORM:
### `ref`
the syntax of the `ref` tag is:
```
`ref:"struct_name"`
```
where `struct_name` is the name of the struct you're referencing, as shown
in [Referencing other documents](#referencing-other-documents).
### `gridfs`
you can load files stored in [gridfs](https://www.mongodb.com/docs/manual/core/gridfs/) directly into your structs using
this tag, which has the following syntax:
```
`gridfs:"bucket_name,file_fmt"
```
where:
- `bucket_name` is the name of the gridfs bucket
- `file_fmt` is a valid [go template string](https://pkg.go.dev/text/template) that resolves to the unique file name.
all exported methods and fields in the surrounding struct can be referenced in this template.
currently, the supported field types for the `gridfs` tag are:
- `string`
- `[]byte`
### `idx`/`index`
indexes can be defined using this tag on the field you want to create the index for. the syntax is as follows:
`idx:{field || field1,field2,...},keyword1,keyword2,...`
supported keywords are `unique` and `sparse`.
`background` is technically allowed, but it's deprecated, and so will essentially be a no-op on mongodb 4.2+.
## acknowledgements
- [goonode/mogo](https://github.com/goonode/mogo), the project which largely inspired this one
# Further reading
see the [godocs]("https://git.tablet.sh/tablet/diamond-orm/src/branch/main/godoc.html") for more details :)