huzzah for documentation!!! 🎉🥳
This commit is contained in:
parent
53e23bb52a
commit
0e367539fd
327
README.md
Normal file
327
README.md
Normal file
@ -0,0 +1,327 @@
|
||||
# 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 <sub><sub>~~why?~~</sub></sub>, 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
|
||||
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.
|
||||
<sub>obviously.</sub>
|
||||
<sub><sub>i mean seriously, who'd want to store one thing in two places?</sub></sub>
|
||||
|
||||
the recommended way to create a new document instance is via the `orm.Create` method:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
/* ~ snip ~ */
|
||||
user := orm.Create(User{}).(*User)
|
||||
}
|
||||
```
|
||||
|
||||
similarly, to create a slice of documents, call `orm.CreateSlice`:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
/* ~ snip ~ */
|
||||
users := orm.CreateSlice[User](User{})
|
||||
}
|
||||
```
|
||||
|
||||
lastly, let's implement the `HasID` interface on our document:
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
<sub>(if you hate yourself that much)</sub>
|
||||
|
||||
## 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
|
||||
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
|
||||
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
|
||||
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<String, Object> query, MongoOptions options) {
|
||||
// ...
|
||||
}
|
||||
public static ArrayList<Document> FindAll(HashMap<String, Object> 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 :)
|
1229
godoc.html
Normal file
1229
godoc.html
Normal file
File diff suppressed because one or more lines are too long
10
model.go
10
model.go
@ -10,7 +10,8 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Model - "base" struct for all queryable models
|
||||
// Model - type which contains "static" methods like
|
||||
// Find, FindOne, etc.
|
||||
type Model struct {
|
||||
Indexes map[string][]InternalIndex
|
||||
Type reflect.Type
|
||||
@ -224,6 +225,9 @@ func Create(d any) any {
|
||||
return what
|
||||
}
|
||||
|
||||
// CreateSlice - convenience method which creates a new slice
|
||||
// of type *T (where T is a type which embeds Document) and
|
||||
// returns it
|
||||
func CreateSlice[T any](d T) []*T {
|
||||
r, _, _ := createBase(d)
|
||||
rtype := r.Type()
|
||||
@ -232,7 +236,3 @@ func CreateSlice[T any](d T) []*T {
|
||||
newItem.Elem().Set(reflect.MakeSlice(rslice, 0, 0))
|
||||
return newItem.Elem().Interface().([]*T)
|
||||
}
|
||||
|
||||
func (m *Model) PrintMe() {
|
||||
fmt.Printf("My name is %s !\n", nameOf(m))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user