basic implementation
This commit is contained in:
commit
dc4b1cf031
19
LICENSE
Normal file
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2024 Mya Pitzeruse
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
OR OTHER DEALINGS IN THE SOFTWARE.
|
32
Makefile
Normal file
32
Makefile
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
define HELP_TEXT
|
||||||
|
Welcome!
|
||||||
|
|
||||||
|
Targets:
|
||||||
|
help provides help text
|
||||||
|
deps download dependencies
|
||||||
|
deps/upgrade upgrade dependencies
|
||||||
|
deps/tidy tidy dependencies
|
||||||
|
test run tests
|
||||||
|
legal prepends legal header to source code
|
||||||
|
|
||||||
|
endef
|
||||||
|
export HELP_TEXT
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "$$HELP_TEXT"
|
||||||
|
|
||||||
|
deps:
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
deps/upgrade:
|
||||||
|
go get -u ./...
|
||||||
|
|
||||||
|
deps/tidy:
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
test:
|
||||||
|
@go test -v -race -covermode=atomic -coverprofile=coverage.txt -coverpkg=./... ./...
|
||||||
|
|
||||||
|
legal: .legal
|
||||||
|
.legal:
|
||||||
|
@git ls-files | xargs -I{} addlicense -f ./legal/header.txt -skip yaml -skip yml {}
|
128
README.md
Normal file
128
README.md
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# gorm-crud
|
||||||
|
|
||||||
|
Mostly developed for me. Templates basic CRUD operations using Go Generics.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go get go.pitz.tech/gorm/crud
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic
|
||||||
|
|
||||||
|
The basic usage of this library is as follows. This example demonstrates usage for a single resource type, however this
|
||||||
|
pattern can apply to multiple resources.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"go.pitz.tech/gorm/crud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resource is an annotated database model.
|
||||||
|
type Resource struct{}
|
||||||
|
|
||||||
|
// ResourceDB provides an abstraction for managing Resource data in the associated gorm.DB.
|
||||||
|
type ResourceDB struct {
|
||||||
|
List crud.ListFunc[Resource]
|
||||||
|
Create crud.CreateFunc[Resource]
|
||||||
|
Get crud.GetFunc[Resource]
|
||||||
|
Update crud.UpdateFunc[Resource]
|
||||||
|
Delete crud.DeleteFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// todo: open your database
|
||||||
|
var db *gorm.DB
|
||||||
|
|
||||||
|
resources := &ResourceDB{
|
||||||
|
List: crud.Lister[Resource](db),
|
||||||
|
Create: crud.Creator[Resource](db),
|
||||||
|
Get: crud.Getter[Resource](db),
|
||||||
|
Update: crud.Updater[Resource](db),
|
||||||
|
Delete: crud.Deleter[Resource](db),
|
||||||
|
}
|
||||||
|
|
||||||
|
// data, err := resources.List(ctx, 0, 10)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transactions
|
||||||
|
|
||||||
|
Directly managing transactions is particularly useful when you want to manage multiple resources in a single operation.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"go.pitz.tech/gorm/crud"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Resource1 struct {}
|
||||||
|
type Resource2 struct {}
|
||||||
|
|
||||||
|
type Resource1DB struct {
|
||||||
|
Create crud.CreateFunc[Resource1]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resource2DB struct {
|
||||||
|
Create crud.CreateFunc[Resource2]
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// todo: open your database
|
||||||
|
var db *gorm.DB
|
||||||
|
var resource1db *Resource1DB
|
||||||
|
var resource2db *Resource1DB
|
||||||
|
|
||||||
|
// optionally specify transaction options
|
||||||
|
txn := db.Begin()
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
ctx := crud.Transaction(context.Background(), txn)
|
||||||
|
|
||||||
|
err := resource1db.Create(ctx, &Resource1{})
|
||||||
|
if err != nil {
|
||||||
|
// resource 1 and 2 do not exist!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = resource2db.Create(ctx, &Resource2{})
|
||||||
|
if err != nil {
|
||||||
|
// resource 1 and 2 do not exist!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = txn.Commit().Error
|
||||||
|
if err != nil {
|
||||||
|
// resource 1 and 2 do not exist!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// resource 1 and 2 exist!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extending
|
||||||
|
|
||||||
|
These methods can easily be customized by providing concrete method definitions in the structure themselves. For
|
||||||
|
example, suppose a `UserDB` wanted to support a `GetByEmail` operation. Such a method might explicitly ask for the email
|
||||||
|
address and call a `Get` function with the explicit field as a filter. For instance:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (db *UserDB) GetByEmail(ctx context.Context, email string) (*User, error) {
|
||||||
|
return db.Get(ctx, map[string]interface{}{
|
||||||
|
"email": email,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
`MIT`. See [LICENSE](LICENSE) for more details.
|
29
context.go
Normal file
29
context.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright (C) 2024 Mya Pitzeruse
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package crud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
|
const transaction contextKey = "go.pitz.tech/gorm/crud/txn"
|
||||||
|
|
||||||
|
// Transaction returns the transaction associated with the context. If none is present, the db is used.
|
||||||
|
func Transaction(ctx context.Context, db *gorm.DB) *gorm.DB {
|
||||||
|
txn, ok := ctx.Value(transaction).(*gorm.DB)
|
||||||
|
if ok {
|
||||||
|
return txn
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTransaction attaches the current transaction to provided context.
|
||||||
|
func WithTransaction(ctx context.Context, db *gorm.DB) context.Context {
|
||||||
|
return context.WithValue(ctx, transaction, db)
|
||||||
|
}
|
26
creator.go
Normal file
26
creator.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (C) 2024 Mya Pitzeruse
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package crud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateFunc defines the method signature of the function returned by the Creator method. It simplifies the operation
|
||||||
|
// of creating records in the database.
|
||||||
|
type CreateFunc[T any] func(ctx context.Context, record *T) error
|
||||||
|
|
||||||
|
// Creator returns a function that can create data using gorm db.
|
||||||
|
func Creator[T any](db *gorm.DB) CreateFunc[T] {
|
||||||
|
prototype := new(T)
|
||||||
|
|
||||||
|
return func(ctx context.Context, record *T) error {
|
||||||
|
return Transaction(ctx, db).
|
||||||
|
Model(prototype).
|
||||||
|
Create(record).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
}
|
30
deleter.go
Normal file
30
deleter.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (C) 2024 Mya Pitzeruse
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package crud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeleteFunc defines the signature of the function returned by Deleter. It simplifies the interface for deleting data
|
||||||
|
// using gorm.
|
||||||
|
type DeleteFunc func(ctx context.Context, filters ...map[string]interface{}) error
|
||||||
|
|
||||||
|
// Deleter returns a function that can delete data using gorm db.
|
||||||
|
func Deleter[T any](db *gorm.DB) DeleteFunc {
|
||||||
|
prototype := new(T)
|
||||||
|
|
||||||
|
return func(ctx context.Context, filters ...map[string]interface{}) error {
|
||||||
|
q := Transaction(ctx, db).
|
||||||
|
Model(prototype)
|
||||||
|
|
||||||
|
for _, filter := range filters {
|
||||||
|
q = q.Where(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return q.Delete(prototype).Error
|
||||||
|
}
|
||||||
|
}
|
5
doc.go
Normal file
5
doc.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Copyright (C) 2024 Mya Pitzeruse
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Package crud provides handlers that streamline database operations using generics in Go.
|
||||||
|
package crud
|
37
getter.go
Normal file
37
getter.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (C) 2024 Mya Pitzeruse
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package crud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetFunc defines the signature of the function returned by Getter. It streamlines the process of reading single
|
||||||
|
// records from the database.
|
||||||
|
type GetFunc[T any] func(ctx context.Context, filters ...map[string]interface{}) (*T, error)
|
||||||
|
|
||||||
|
// Getter returns a function that can read data from a gorm database.
|
||||||
|
func Getter[T any](db *gorm.DB) GetFunc[T] {
|
||||||
|
prototype := new(T)
|
||||||
|
|
||||||
|
return func(ctx context.Context, filters ...map[string]interface{}) (*T, error) {
|
||||||
|
q := Transaction(ctx, db).
|
||||||
|
Model(prototype)
|
||||||
|
|
||||||
|
for _, filter := range filters {
|
||||||
|
q = q.Where(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := new(T)
|
||||||
|
|
||||||
|
err := q.First(body).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
}
|
10
go.mod
Normal file
10
go.mod
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module go.pitz.tech/gorm/crud
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require gorm.io/gorm v1.25.6
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
)
|
6
go.sum
Normal file
6
go.sum
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A=
|
||||||
|
gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
2
legal/header.txt
Normal file
2
legal/header.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Copyright (C) {{ .Year }} Mya Pitzeruse
|
||||||
|
SPDX-License-Identifier: MIT
|
39
lister.go
Normal file
39
lister.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (C) 2024 Mya Pitzeruse
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package crud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListFunc defines a function signature used to list data from the underlying gorm database. It ensures that size and
|
||||||
|
// scoping of queries is taken into consideration.
|
||||||
|
type ListFunc[T any] func(ctx context.Context, offset, count int, filters ...map[string]interface{}) ([]T, error)
|
||||||
|
|
||||||
|
// Lister returns a ListFunc to be used to list data from gorm.
|
||||||
|
func Lister[T any](db *gorm.DB) ListFunc[T] {
|
||||||
|
prototype := new(T)
|
||||||
|
|
||||||
|
return func(ctx context.Context, offset, count int, filters ...map[string]interface{}) ([]T, error) {
|
||||||
|
q := Transaction(ctx, db).
|
||||||
|
Model(prototype).
|
||||||
|
Offset(offset).
|
||||||
|
Limit(count)
|
||||||
|
|
||||||
|
for _, filter := range filters {
|
||||||
|
q = q.Where(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]T, 0, count)
|
||||||
|
|
||||||
|
err := q.Find(&result).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
30
updater.go
Normal file
30
updater.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (C) 2024 Mya Pitzeruse
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package crud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateFunc defines a signature that is used by Updater to return a function that can update data in the database.
|
||||||
|
type UpdateFunc[T any] func(ctx context.Context, patch T, filters ...map[string]interface{}) error
|
||||||
|
|
||||||
|
// Updater returns a function used to update information in the database.
|
||||||
|
func Updater[T any](db *gorm.DB) UpdateFunc[T] {
|
||||||
|
prototype := new(T)
|
||||||
|
|
||||||
|
return func(ctx context.Context, patch T, filters ...map[string]interface{}) error {
|
||||||
|
q := Transaction(ctx, db).
|
||||||
|
Model(prototype).
|
||||||
|
Limit(1)
|
||||||
|
|
||||||
|
for _, filter := range filters {
|
||||||
|
q = q.Where(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return q.Updates(patch).Error
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user