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