Skip to content

Commit 01fbce1

Browse files
Merge pull request #50 from elcengine/refactor/populate
Refactor schema type validation and population usage
2 parents aba7c0d + 4a161d4 commit 01fbce1

30 files changed

Lines changed: 523 additions & 257 deletions

.github/workflows/tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ jobs:
5757
env:
5858
DEFAULT_DATASOURCE: mongodb://127.0.0.1:27017/elemental?replicaSet=rs0
5959
SECONDARY_DATASOURCE: mongodb://127.0.0.1:27018/elemental?replicaSet=rs1
60+
GOEXPERIMENT: ${{ matrix.go-version == '1.23' && 'aliastypeparams' || '' }}
61+
GODEBUG: ${{ matrix.go-version == '1.23' && 'gotypesalias=1' || '' }}
6062

6163
- name: Upload coverage report
6264
if: github.event_name == 'pull_request' && github.base_ref == 'main' && matrix.go-version == '1.24' && matrix.mongo-version == '8.0'

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ database
66

77
.elemental
88

9-
.vscode
9+
.vscode
10+
11+
.env

Makefile

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
GO_TEST_ARGS ?= -tags=unit
21
GO_BENCH_ARGS ?= -benchtime=30s
32

43
format:
54
gofmt -w .
65
test:
76
PARALLEL_CONVEY=false make test-lightspeed
87
test-lightspeed:
9-
go test $(GO_TEST_ARGS) -v --count=1 ./tests/...
8+
go test $(GO_TEST_ARGS) --run='${run}' -v --count=1 ./tests/...
109
test-coverage:
1110
@mkdir -p ./coverage
1211
make test GO_TEST_ARGS="--cover -coverpkg=./cmd/...,./core/...,./plugins/...,./utils/... --coverprofile=./coverage/coverage.out"
@@ -30,9 +29,16 @@ install:
3029
go install github.com/evilmartians/lefthook@v1.11.12
3130
lefthook install
3231
@echo "\033[0;32mLefthook installed and configured successfully.\033[0m"
32+
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6
33+
@echo "\033[0;32mGolangCI-Lint installed successfully.\033[0m"
3334
@which npm > /dev/null && \
3435
npm install -g @commitlint/config-conventional@17.6.5 @commitlint/cli@17.6.5 && \
3536
echo "\033[0;32mCommitlint installed successfully.\033[0m" || \
3637
echo "\033[0;31mNode is not installed. Please install Node.js to use commitlint.\033[0m"
3738
go mod tidy
38-
@echo "\033[0;32mGo modules installed successfully.\033[0m"
39+
@echo "\033[0;32mGo modules installed successfully.\033[0m"
40+
tidy:
41+
@echo "\033[0;32mRunning go mod tidy...\033[0m"
42+
go mod tidy -v
43+
@echo "\033[0;32mVerifying packages...\033[0m"
44+
go mod verify

core/model.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type Model[T any] struct {
2626
Cloned bool // Indicates if this model has been cloned at least once
2727
pipeline mongo.Pipeline
2828
executor func(m Model[T], ctx context.Context) any
29+
result any // Pointer to the result of the last executed query. This is still not in full use. Only used for operations such as Populate for now.
2930
whereField string
3031
failWith *error
3132
orConditionActive bool
@@ -40,6 +41,7 @@ type Model[T any] struct {
4041
softDeleteEnabled bool
4142
deletedAtFieldName string
4243
triggerExit chan bool
44+
docReflectType reflect.Type // The reflect type of a sample document of this model
4345
}
4446

4547
var pluralizeClient = pluralize.NewClient()
@@ -64,6 +66,7 @@ func NewModel[T any](name string, schema Schema) Model[T] {
6466
middleware: &middleware,
6567
triggerExit: make(chan bool, 1),
6668
}
69+
model.preprocess()
6770
Models[name] = model
6871
onConnectionComplete := func() {
6972
model.CreateCollection()
@@ -84,12 +87,11 @@ func NewModel[T any](name string, schema Schema) Model[T] {
8487
// This method validates the document against the model schema and panics if any errors are found.
8588
func (m Model[T]) Create(doc T) Model[T] {
8689
m.executor = func(m Model[T], ctx context.Context) any {
87-
documentToInsert, detailedDocument := enforceSchema(m.Schema, &doc, nil)
88-
detailedDocumentEntity := utils.CastBSON[T](detailedDocument)
89-
m.middleware.pre.save.run(detailedDocumentEntity)
90+
documentToInsert := enforceSchema(m.Schema, &doc, nil)
91+
m.middleware.pre.save.run(&documentToInsert)
9092
lo.Must(m.Collection().InsertOne(ctx, documentToInsert))
91-
m.middleware.post.save.run(detailedDocumentEntity)
92-
return detailedDocumentEntity
93+
m.middleware.post.save.run(&documentToInsert)
94+
return utils.CastBSON[T](documentToInsert)
9395
}
9496
return m
9597
}
@@ -104,14 +106,12 @@ func (m Model[T]) CreateMany(docs []T) Model[T] {
104106
// This method validates the document against the model schema and panics if any errors are found.
105107
func (m Model[T]) InsertMany(docs []T) Model[T] {
106108
m.executor = func(m Model[T], ctx context.Context) any {
107-
var documentsToInsert, detailedDocuments []any
109+
var documentsToInsert []any
108110
for _, doc := range docs {
109-
documentToInsert, detailedDocument := enforceSchema(m.Schema, &doc, nil)
110-
documentsToInsert = append(documentsToInsert, documentToInsert)
111-
detailedDocuments = append(detailedDocuments, detailedDocument)
111+
documentsToInsert = append(documentsToInsert, enforceSchema(m.Schema, &doc, nil))
112112
}
113113
lo.Must(m.Collection().InsertMany(ctx, documentsToInsert))
114-
return utils.CastBSONSlice[T](detailedDocuments)
114+
return utils.CastBSONSlice[T](documentsToInsert)
115115
}
116116
return m
117117
}
@@ -124,8 +124,8 @@ func (m Model[T]) Find(query ...primitive.M) Model[T] {
124124
var results []T
125125
cursor := lo.Must(m.Collection().Aggregate(ctx, m.pipeline))
126126
m.checkConditionsAndPanicForErr(cursor.All(ctx, &results))
127-
m.middleware.post.find.run(results)
128127
m.checkConditionsAndPanic(results)
128+
m.middleware.post.find.run(&results)
129129
return results
130130
}
131131
q := utils.MergedQueryOrDefault(query)

core/model_actions.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package elemental
22

33
import (
44
"context"
5-
"reflect"
6-
75
"github.com/elcengine/elemental/utils"
86
"github.com/samber/lo"
97

@@ -31,8 +29,7 @@ func (m Model[T]) Ping(ctx ...context.Context) error {
3129

3230
// Creates or updates the indexes for this model. This method will only create the indexes if they do not exist.
3331
func (m Model[T]) SyncIndexes(ctx ...context.Context) {
34-
var sample [0]T
35-
m.Schema.syncIndexes(reflect.TypeOf(sample).Elem(), lo.FromPtr(m.temporaryDatabase), lo.FromPtr(m.temporaryConnection), lo.FromPtr(m.temporaryCollection), ctx...)
32+
m.Schema.syncIndexes(m.docReflectType, lo.FromPtr(m.temporaryDatabase), lo.FromPtr(m.temporaryConnection), lo.FromPtr(m.temporaryCollection), ctx...)
3633
}
3734

3835
// Drops all indexes for this model except the default `_id` index.

core/model_audit.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package elemental
22

33
import (
44
"context"
5-
"github.com/elcengine/elemental/utils"
6-
"github.com/samber/lo"
75
"reflect"
86
"time"
97

8+
"github.com/elcengine/elemental/utils"
9+
"github.com/samber/lo"
10+
"github.com/spf13/cast"
11+
12+
"go.mongodb.org/mongo-driver/bson"
1013
"go.mongodb.org/mongo-driver/bson/primitive"
1114
)
1215

@@ -20,6 +23,8 @@ const (
2023
AuditTypeDelete AuditType = "delete"
2124
)
2225

26+
const AuditUserFallback = "System"
27+
2328
type Audit struct {
2429
Entity string `json:"entity" bson:"entity"` // The name of the model that was audited.
2530
Type AuditType `json:"type" bson:"type"` // The type of operation that was performed (insert, update, delete).
@@ -62,23 +67,21 @@ func (m Model[T]) EnableAuditing(ctx ...context.Context) {
6267
q.Exec(context)
6368
}
6469

65-
userFallback := "System"
66-
6770
m.OnInsert(func(doc T) {
6871
execWithModelOpts(AuditModel.Create(Audit{
6972
Entity: m.Name,
7073
Type: AuditTypeInsert,
71-
Document: *utils.ToBSONDoc(doc),
72-
User: lo.CoalesceOrEmpty(utils.Cast[string](context.Value(CtxUser)), userFallback),
74+
Document: utils.CastBSON[bson.M](doc),
75+
User: lo.CoalesceOrEmpty(cast.ToString(context.Value(CtxUser)), AuditUserFallback),
7376
CreatedAt: time.Now(),
7477
}))
7578
}, TriggerOptions{Context: &context, Filter: &primitive.M{"ns.coll": primitive.M{"$eq": m.Collection().Name()}}})
7679
m.OnUpdate(func(doc T) {
7780
execWithModelOpts(AuditModel.Create(Audit{
7881
Entity: m.Name,
7982
Type: AuditTypeUpdate,
80-
Document: *utils.ToBSONDoc(doc),
81-
User: lo.CoalesceOrEmpty(utils.Cast[string](context.Value(CtxUser)), userFallback),
83+
Document: utils.CastBSON[bson.M](doc),
84+
User: lo.CoalesceOrEmpty(cast.ToString(context.Value(CtxUser)), AuditUserFallback),
8285
CreatedAt: time.Now(),
8386
}))
8487
}, TriggerOptions{Context: &context, Filter: &primitive.M{"ns.coll": primitive.M{"$eq": m.Collection().Name()}}})
@@ -87,7 +90,7 @@ func (m Model[T]) EnableAuditing(ctx ...context.Context) {
8790
Entity: m.Name,
8891
Type: AuditTypeDelete,
8992
Document: map[string]any{"_id": id},
90-
User: lo.CoalesceOrEmpty(utils.Cast[string](context.Value(CtxUser)), userFallback),
93+
User: lo.CoalesceOrEmpty(cast.ToString(context.Value(CtxUser)), AuditUserFallback),
9194
CreatedAt: time.Now(),
9295
}))
9396
}, TriggerOptions{Context: &context, Filter: &primitive.M{"ns.coll": primitive.M{"$eq": m.Collection().Name()}}})

core/model_middleware.go

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package elemental
33
import (
44
"github.com/elcengine/elemental/utils"
55

6+
"go.mongodb.org/mongo-driver/bson"
67
"go.mongodb.org/mongo-driver/bson/primitive"
78
"go.mongodb.org/mongo-driver/mongo"
89
)
@@ -14,12 +15,13 @@ type listener[T any] struct {
1415
}
1516

1617
type pre[T any] struct {
17-
save listener[T]
18-
updateOne listener[T]
19-
deleteOne listener[T]
20-
deleteMany listener[T]
21-
findOneAndUpdate listener[T]
22-
findOneAndDelete listener[T]
18+
save listener[T]
19+
updateOne listener[T]
20+
deleteOne listener[T]
21+
deleteMany listener[T]
22+
findOneAndUpdate listener[T]
23+
findOneAndDelete listener[T]
24+
findOneAndReplace listener[T]
2325
}
2426

2527
type post[T any] struct {
@@ -51,15 +53,15 @@ func (l listener[T]) run(args ...any) {
5153
}
5254
}
5355

54-
func (m Model[T]) PreSave(f func(doc T) bool) {
56+
func (m Model[T]) PreSave(f func(doc *bson.M) bool) {
5557
m.middleware.pre.save.functions = append(m.middleware.pre.save.functions, func(args ...any) bool {
56-
return f(args[0].(T))
58+
return f(args[0].(*bson.M))
5759
})
5860
}
5961

60-
func (m Model[T]) PostSave(f func(doc T) bool) {
62+
func (m Model[T]) PostSave(f func(doc *bson.M) bool) {
6163
m.middleware.post.save.functions = append(m.middleware.post.save.functions, func(args ...any) bool {
62-
return f(args[0].(T))
64+
return f(args[0].(*bson.M))
6365
})
6466
}
6567

@@ -75,9 +77,9 @@ func (m Model[T]) PostUpdateOne(f func(result *mongo.UpdateResult, err error) bo
7577
})
7678
}
7779

78-
func (m Model[T]) PreDeleteOne(f func(filters primitive.M) bool) {
80+
func (m Model[T]) PreDeleteOne(f func(filters *primitive.M) bool) {
7981
m.middleware.pre.deleteOne.functions = append(m.middleware.pre.deleteOne.functions, func(args ...any) bool {
80-
return f(args[0].(primitive.M))
82+
return f(args[0].(*primitive.M))
8183
})
8284
}
8385

@@ -87,9 +89,9 @@ func (m Model[T]) PostDeleteOne(f func(result *mongo.DeleteResult, err error) bo
8789
})
8890
}
8991

90-
func (m Model[T]) PreDeleteMany(f func(filters primitive.M) bool) {
92+
func (m Model[T]) PreDeleteMany(f func(filters *primitive.M) bool) {
9193
m.middleware.pre.deleteMany.functions = append(m.middleware.pre.deleteMany.functions, func(args ...any) bool {
92-
return f(args[0].(primitive.M))
94+
return f(args[0].(*primitive.M))
9395
})
9496
}
9597

@@ -99,27 +101,27 @@ func (m Model[T]) PostDeleteMany(f func(result *mongo.DeleteResult, err error) b
99101
})
100102
}
101103

102-
func (m Model[T]) PostFind(f func(doc []T) bool) {
104+
func (m Model[T]) PostFind(f func(doc *[]T) bool) {
103105
m.middleware.post.find.functions = append(m.middleware.post.find.functions, func(args ...any) bool {
104-
return f(args[0].([]T))
106+
return f(args[0].(*[]T))
105107
})
106108
}
107109

108-
func (m Model[T]) PostFindOneAndUpdate(f func(doc *T) bool) {
109-
m.middleware.post.findOneAndUpdate.functions = append(m.middleware.post.findOneAndUpdate.functions, func(args ...any) bool {
110-
return f(args[0].(*T))
110+
func (m Model[T]) PreFindOneAndUpdate(f func(filters *primitive.M, doc any) bool) {
111+
m.middleware.pre.findOneAndUpdate.functions = append(m.middleware.pre.findOneAndUpdate.functions, func(args ...any) bool {
112+
return f(args[0].(*primitive.M), args[1])
111113
})
112114
}
113115

114-
func (m Model[T]) PreFindOneAndUpdate(f func(filters primitive.M) bool) {
115-
m.middleware.pre.findOneAndUpdate.functions = append(m.middleware.pre.findOneAndUpdate.functions, func(args ...any) bool {
116-
return f(args[0].(primitive.M))
116+
func (m Model[T]) PostFindOneAndUpdate(f func(doc *T) bool) {
117+
m.middleware.post.findOneAndUpdate.functions = append(m.middleware.post.findOneAndUpdate.functions, func(args ...any) bool {
118+
return f(args[0].(*T))
117119
})
118120
}
119121

120-
func (m Model[T]) PreFindOneAndDelete(f func(filters primitive.M) bool) {
122+
func (m Model[T]) PreFindOneAndDelete(f func(filters *primitive.M) bool) {
121123
m.middleware.pre.findOneAndDelete.functions = append(m.middleware.pre.findOneAndDelete.functions, func(args ...any) bool {
122-
return f(args[0].(primitive.M))
124+
return f(args[0].(*primitive.M))
123125
})
124126
}
125127

@@ -129,6 +131,12 @@ func (m Model[T]) PostFindOneAndDelete(f func(doc *T) bool) {
129131
})
130132
}
131133

134+
func (m Model[T]) PreFindOneAndReplace(f func(filters *primitive.M, doc any) bool) {
135+
m.middleware.pre.findOneAndReplace.functions = append(m.middleware.pre.findOneAndReplace.functions, func(args ...any) bool {
136+
return f(args[0].(*primitive.M), args[1])
137+
})
138+
}
139+
132140
func (m Model[T]) PostFindOneAndReplace(f func(doc *T) bool) {
133141
m.middleware.post.findOneAndReplace.functions = append(m.middleware.post.findOneAndReplace.functions, func(args ...any) bool {
134142
return f(args[0].(*T))

0 commit comments

Comments
 (0)