Skip to content

Commit 939a67f

Browse files
committed
docs: populate usage and some minor adjustments
1 parent da7b779 commit 939a67f

File tree

7 files changed

+176
-22
lines changed

7 files changed

+176
-22
lines changed

docs/auditing/the-audit-model.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@ The Audit model is a special model that is used to log changes to other models.
99
```go
1010
var AuditModel = NewModel[Audit]("Audit", NewSchema(map[string]Field{
1111
"Entity": {
12-
Type: reflect.String,
12+
Type: elemental.String,
1313
Required: true,
1414
},
1515
"Type": {
16-
Type: reflect.String,
16+
Type: elemental.String,
1717
},
1818
"Document": {
19-
Type: reflect.Map,
19+
Type: elemental.Map,
2020
Required: true,
2121
},
2222
"User": {
23-
Type: reflect.String,
23+
Type: elemental.String,
2424
},
2525
}, SchemaOptions{
2626
Collection: "audits",

docs/getting-started/create-a-schema.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,26 @@ A schema is a blueprint for your data model. It defines the structure of your da
1616
unique := true
1717
schema := elemental.NewSchema(map[string]elemental.Field{
1818
"Name": {
19-
Type: reflect.String,
19+
Type: elemental.String,
2020
Required: true,
21-
Index: options.Index().SetUnique(true),
21+
Index: options.Index().SetUnique(true),
2222
},
2323
"Age": {
24-
Type: reflect.Int,
24+
Type: elemental.Int,
2525
Default: DefaultAge,
2626
},
2727
"Occupation": {
28-
Type: reflect.String,
28+
Type: elemental.String,
2929
},
3030
"School": {
31-
Type: reflect.String,
31+
Type: elemental.String,
3232
},
3333
"Weapons": {
34-
Type: reflect.Slice,
34+
Type: elemental.Slice,
3535
Default: []string{},
3636
},
3737
"Retired": {
38-
Type: reflect.Bool,
38+
Type: elemental.Bool,
3939
Default: false,
4040
},
4141
}, elemental.SchemaOptions{
@@ -47,7 +47,7 @@ schema := elemental.NewSchema(map[string]elemental.Field{
4747

4848
Each field in a schema is defined by a `Field` struct. The `Field` struct has the following properties:
4949

50-
- **Type**: The data type of the field. This can be any of the types supported by the `reflect` package.
50+
- **Type**: The data type of the field. Can be of reflect.Kind, reflect.Type, an Elemental alias such as elemental.String or a custom reflection
5151

5252
- **Required**: A boolean value that indicates whether the field is required or not.
5353

@@ -59,9 +59,9 @@ Each field in a schema is defined by a `Field` struct. The `Field` struct has th
5959

6060
- **Length**: The length of the field.
6161

62-
- **Regex**: A regular expression pattern that the field must match.
62+
- **Regex**: A regular expression (*regexp.Regexp) that the field must match.
6363

64-
- **Index**: An `IndexOptions` struct that defines the indexing options for the field.
64+
- **Index**: A pointer to an `IndexOptions` struct that defines the indexing options for the field.
6565

6666
- **IndexOrder**: The order in which the field should be indexed.
6767

docs/getting-started/type-inference.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ That being said, manual type assertion is still a pain and we want to make it as
2020

2121
- `ExecInt()` - Executes the query and returns the result as an `int`. This method is useful for queries that return a single integer value, such as count queries or schedule queries which return a cron entry ID.
2222

23-
- `ExecSS()` - Executes the query and returns the result as a slice of strings. This method is useful for queries that return an array of strings, such as distinct queries.
23+
- `ExecSS()` - Executes the query and returns the result as a slice of strings. This method is useful for queries that return an array of strings, such as distinct queries.
24+
25+
- `ExecInto()` - Executes the query and unmarshals the result into the provided result variable. It is useful when you want to extract results into a custom struct other than the model type such as when you populate certain fields.

docs/indexing/creating-indexes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Indexes can be defined within a Schema under the `Index` field. This field accep
99
```go
1010
schema := elemental.NewSchema(map[string]elemental.Field{
1111
"Vitality": {
12-
Type: reflect.Int,
12+
Type: elemental.Int,
1313
Required: true,
1414
Index: options.Index().SetUnique(true),
1515
},

docs/middleware/lifecycle-hooks.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ sidebar_position: 1
44

55
# Lifecycle Hooks
66

7-
Elemental provides a way to inject custom logic into the lifecycle of a model. This is done by defining middleware functions that are executed at specific points in the lifecycle of a model.
7+
Elemental provides a way to inject custom logic into the lifecycle of a model. This is done by defining middleware functions that are executed at specific points in its lifecycle.
88

99
## Middleware Functions
1010

11-
Middleware functions are functions that are executed at specific points in the lifecycle of a model. They are defined as methods on a model and are executed in the order they are defined.
11+
Middleware functions are model methods which you can invoke to register custom callbacks/handlers to be executed before or after certain operations. They are executed in the order they are defined.
1212

13-
You can define any number of middleware functions on a model. Each middleware function is passed in necessary arguments related to the current operation such as the document being operated on.
13+
You can define any number of middleware functions on a single model. Each middleware function is passed in necessary arguments as pointers related to the current operation such as the document being operated on or the filters being used. **Since these arguments are pointers, you can modify them within the middleware function in any way you want.**
14+
15+
Each middleware function returns a boolean value indicating whether the operation should continue or not. If the middleware function returns `false`, the operation will be aborted and no further middleware functions will be executed.
1416

1517
There are 2 categories of middleware functions in Elemental:
1618

@@ -20,8 +22,8 @@ There are 2 categories of middleware functions in Elemental:
2022
## Example Usage
2123

2224
```go
23-
WitcherModel.PreSave(func(witcher Witcher) bool {
24-
fmt.Println("PreSave middleware executed", witcher.Name)
25+
WitcherModel.PreSave(func(witcher *bson.M) bool {
26+
fmt.Println("PreSave middleware executed", *witcher)
2527
return true
2628
})
2729
```
@@ -38,6 +40,7 @@ In the example above, we define a `PreSave` middleware function on the `WitcherM
3840
- **PreDeleteMany**: Executed before multiple documents are deleted from the database.
3941
- **PreFindOneAndUpdate**: Executed before a single document is updated in the database.
4042
- **PreFindOneAndDelete**: Executed before a single document is deleted from the database.
43+
- **PreFindOneAndReplace**: Executed before a single document is replaced in the database.
4144

4245
### Post Middleware Functions
4346

docs/querybuilder/or-fail.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 26
2+
sidebar_position: 27
33
---
44

55
# Or Fail

docs/querybuilder/populate.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
---
2+
sidebar_position: 26
3+
---
4+
5+
# Populate
6+
7+
Populate is a method that allows you to retrieve related documents from other collections based on references defined in your schema. The retrieved documents are automatically embedded into the results of your query.
8+
9+
This is particularly useful for fetching associated data without having to perform multiple queries manually as well as for reducing the number of queries made to the database. This uses `$lookup` under the hood, which is a MongoDB aggregation stage that allows you to join documents from different collections.
10+
11+
## Usage
12+
13+
### Type definitions
14+
15+
Population is a bit tricky to work with in Go due to its type system. Here we utilize the power of generics to define a flexible population structure that can work with any type of documents.
16+
17+
18+
```go
19+
type Kingdom struct {
20+
ID primitive.ObjectID `json:"_id" bson:"_id"`
21+
Name string `json:"name" bson:"name"`
22+
CreatedAt time.Time `json:"created_at" bson:"created_at"`
23+
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
24+
}
25+
26+
type Monster struct {
27+
ID primitive.ObjectID `json:"_id" bson:"_id"`
28+
Name string `json:"name" bson:"name"`
29+
Category string `json:"category,omitempty" bson:"category,omitempty"`
30+
CreatedAt time.Time `json:"created_at" bson:"created_at"`
31+
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
32+
}
33+
34+
type GenericBestiary[T any, Y any] struct {
35+
ID primitive.ObjectID `json:"_id" bson:"_id"`
36+
Monster T `json:"monster" bson:"monster"`
37+
Kingdom Y `json:"kingdom" bson:"kingdom"`
38+
}
39+
40+
type Bestiary = GenericBestiary[any, any]
41+
42+
type DetailedBestiary = GenericBestiary[Monster, Kingdom]
43+
44+
type PartiallyDetailedBestiary = GenericBestiary[Monster, primitive.ObjectID]
45+
46+
```
47+
48+
### Model definition
49+
50+
```go
51+
BestiaryModel := elemental.NewModel[Bestiary]("Bestiary", elemental.NewSchema(map[string]elemental.Field{
52+
"Monster": {
53+
Type: elemental.ObjectID,
54+
Ref: "Monster",
55+
},
56+
"Kingdom": {
57+
Type: elemental.ObjectID,
58+
Ref: "Kingdom",
59+
},
60+
}))
61+
```
62+
63+
In the example above, we define a `Bestiary` model with two fields, `Monster` and `Kingdom`, which are references to other Elemental models. You can also directly provide a `Collection` attribute into a field instead of using the `Ref` attribute, which is a bit faster to work with and also allows to use a different collection name than the one registered in the referenced model.
64+
65+
66+
### Querying with Populate
67+
68+
To use the `Populate` method, you can chain it to your query. The `Populate` method accepts a list of fields to populate and you can use it in a variety of ways depending on your preference.
69+
70+
There is no limitation on the number of fields you can populate, and it can be chained alongside other Elemental query methods.
71+
72+
Note that with `Populate` we use the `ExecInto` method to retrieve the results into a custom type since the base type of the model is not sufficient to hold the populated data and may even fail to unmarshal the results correctly.
73+
74+
```go
75+
var bestiaries []DetailedBestiary
76+
77+
BestiaryModel.Find().Populate("Monster", "Kingdom").ExecInto(&bestiaries)
78+
79+
// or
80+
81+
BestiaryModel.Find().Populate("monster", "kingdom").ExecInto(&bestiaries)
82+
83+
// or
84+
85+
BestiaryModel.Find().Populate("Monster").Populate("Kingdom").ExecInto(&bestiaries)
86+
87+
// or
88+
89+
BestiaryModel.Find().Populate("monster").Populate("kingdom").ExecInto(&bestiaries)
90+
91+
// or
92+
93+
BestiaryModel.Find().Populate("monster kingdom").ExecInto(&bestiaries)
94+
95+
// or
96+
97+
BestiaryModel.Find().Populate([]string{"Monster", "Kingdom"}).ExecInto(&bestiaries)
98+
99+
// or
100+
101+
BestiaryModel.Find().Populate(primitive.M{
102+
"path": "monster",
103+
"pipeline": []primitive.M{
104+
{"$addFields": primitive.M{
105+
"category": primitive.M{"$toLower": "$category"},
106+
}},
107+
},
108+
}, "Kingdom").ExecInto(&bestiaries)
109+
110+
// or
111+
112+
BestiaryModel.Find().Populate(primitive.M{
113+
"path": "monster",
114+
"select": primitive.M{
115+
"name": 1,
116+
"category": 1,
117+
},
118+
}).Populate("Kingdom").ExecInto(&bestiaries)
119+
```
120+
121+
#### You can even just populate one field if you want to:
122+
123+
```go
124+
var bestiaries []PartiallyDetailedBestiary
125+
126+
BestiaryModel.Find().Populate("Monster").ExecInto(&bestiaries)
127+
```
128+
129+
### Creating a new document with a referenced document
130+
131+
When creating a new document with a reference to another document, you can directly pass the document as the value of the field. Elemental will automatically handle the reference and store the ObjectID of the referenced document.
132+
133+
```go
134+
monster := MonsterModel.Create(Monster{
135+
Name: "Griffin",
136+
Category: "Beast",
137+
}).Exec()
138+
139+
kingdom := KingdomModel.Create(Kingdom{
140+
Name: "Temeria",
141+
}).Exec()
142+
143+
bestiary := BestiaryModel.Create(Bestiary{
144+
Monster: monster,
145+
Kingdom: kingdom,
146+
}).ExecT()
147+
148+
fmt.Println("Bestiary created with ID:", bestiary.ID.Hex())
149+
```

0 commit comments

Comments
 (0)