So I'm trying to create a repository for "generic types" so I don't have to copy/paste so much code but I need some help since I'm not sure if I'm going about it correctly
A generic type only has two fields
type GenericType struct {
ID *uint8
name string
}
// Here is an extension of it
type JobType struct {
GenericType
}
type PhoneType struct {
GenericType
}
Now I want to create one repository that I can simply instantiate with the type of GenericType I'm trying to persists/retrieve from the database
type GenericTypeRepository struct {
DB *pg.DB
Type *models.GenericType
}
// FindAll returns a slice of types
func (r *GenericTypeRepository) FindAll() (*[]GenericType, error) {
// Need to instantiate a slice of the repository's type
// I'm not sure if reflection is the right way
rType := reflect.ValueOf(r.Type)
rSlice := reflect.SliceOf(rType.Type())
err := r.DB.Model(&rSlice).Column("id", "name").Select()
if err != nil {
return nil, err
}
return &rSlice, nil
}
// FindTypeOfId retrieves a type of the specified id
func (r *GenericTypeRepository) FindTypeOfId(id uint8) (*GenericType, error) {
// Need to instantiate a struct of the repository's type
// I'm not sure if reflection is the right way
t := reflect.ValueOf(r.Type)
count, _ := r.DB.Model(t).
Where("id = ?", id).
SelectAndCount()
if count == 0 {
return nil, fmt.Errorf("could not find type of id `%d`", id)
}
return t, nil
}
Here is a sample of the envisioned usage:
// DB Connection
db := pg.Connect(...config...)
defer db.Close()
// Job Types Repository
jobTypesRepository := &GenericRepository{
DB: db,
Type: &JobType{},
}
jobTypes := genericRepository.FindAll()
// Phone Types Repository
phoneTypesRepository := &GenericRepository{
DB: db,
Type: &PhoneType{},
}
phoneTypes := genericRepository.FindAll()
Is it possible to do this?
I ended up figuring it out. Here's the code for anyone who would need something like this:
Sample Usage
// Some generic type
type JobType struct {
GenericType
}
// Sample generic repository
jobTypeRepository := &GenericTypeRepository{
DB: db,
Type: &JobType{},
}
// Persist
jobType := &models.JobType{}
jobType.Name = "Type 1"
_, _ = jobTypeRepository.Persist(jobType)
// Find All Types
jobTypes, _ = jobTypeRepository.FindAll()
GenericType
// IGenericType generic type interface
type IGenericType interface {
GetID() uint8
GetName() string
}
// GenericType represents the implementation of the IGenericType interface
type GenericType struct {
ID uint8 `json:"id"`
Name string `json:"name"`
}
// GetID returns the type's id
func (t *GenericType) GetID() uint8 {
return t.ID
}
// GetName returns the type's name
func (t *GenericType) GetName() string {
return t.Name
}
GenericType Repository
import (
"github.com/go-pg/pg/v9"
)
// GenericTypeRepository is used to retrieve and persists generic types in the database
type GenericTypeRepository struct {
DB *pg.DB
Type IGenericType
}
// FindAll returns a slice of generic types
func (r *GenericTypeRepository) FindAll() (*[]IGenericType, error) {
typeCopy := r.Type
query := r.DB.Model(typeCopy)
results := make([]IGenericType, 0)
err := query.ForEach(func (t GenericType) error {
results = append(results, &t)
return nil
})
if err != nil {
return nil, err
}
return &results, nil
}
// FindTypeOfId retrieves a type of the specified id
func (r *GenericTypeRepository) FindTypeOfId(id uint8) (*IGenericType, error) {
typeCopy := r.Type
err := r.DB.Model(typeCopy).
Where("id = ?", id).
Select()
if err != nil {
return nil, err
}
return &typeCopy, nil
}
// FindTypeOfName retrieves a type of the specified name
func (r *GenericTypeRepository) FindTypeOfName(name string) (*GenericType, error) {
typeCopy := r.Type
err := r.DB.Model(typeCopy).
Where("name = ?", name).
Select()
if err != nil {
return nil, err
}
return &typeCopy, nil
}
// Persist persists a type
func (r *GenericTypeRepository) Persist(t IGenericType) (*IGenericType, error) {
typeCopy := t
_, err := r.DB.Model(typeCopy).
OnConflict("(id) DO UPDATE").
Set("name = EXCLUDED.name").
Insert()
if err != nil {
return nil, err
}
return &typeCopy, nil
}
@l0gicgate, your code is simply awesome! 馃槂馃槂馃槂
I would like @vmihailenco to insert it in the amazing documentation he is writing for version 10.
@vmihailenco, maybe we can take advantage of this code to add ideas and more in a special section of the documentation, for example called "Recipes"?
@frederikhors there were slight changes to the code since. I found a bug where the reflection type was not being casted properly during concurrent operations. Here is the updated version:
package models
// IGenericType generic type interface
type IGenericType interface {
GetID() uint8
GetName() string
}
// GenericType is the implementation of the IGenericType interface
type GenericType struct {
ID uint8 `json:"id"`
Name string `json:"name"`
}
// GetID returns the type's id
func (t *GenericType) GetID() uint8 {
return t.ID
}
// GetName returns the type's name
func (t *GenericType) GetName() string {
return t.Name
}
package repositories
import (
"fmt"
"github.com/go-pg/pg/v9"
"reflect"
)
func wrapGenericTypeRepositoryError(method string, err error) error {
if err != nil {
return fmt.Errorf("[generic-type-repository::%s]: %s", method, err.Error())
}
return nil
}
// GenericTypeRepository is used to retrieve and persists generic types in the database
type GenericTypeRepository struct {
DB *pg.DB
Filters *GenericTypeRepositoryFilter
Type models.IGenericType
}
// FindAll returns a slice of generic types
func (r *GenericTypeRepository) FindAll() ([]models.IGenericType, error) {
typeCopy := reflect.New(reflect.ValueOf(r.Type).Elem().Type()).Interface().(models.IGenericType)
query := r.DB.Model(typeCopy)
results := make([]models.IGenericType, 0)
if r.Filters != nil {
query = query.Apply(r.Filters.ApplyFilters)
}
err := query.ForEach(func (t models.GenericType) error {
results = append(results, &t)
return nil
})
return results, wrapGenericTypeRepositoryError("FindAll", err)
}
// FindTypeOfID retrieves a type of the specified id
func (r *GenericTypeRepository) FindTypeOfID(id uint8) (models.IGenericType, error) {
typeCopy := reflect.New(reflect.ValueOf(r.Type).Elem().Type()).Interface().(models.IGenericType)
err := r.DB.Model(typeCopy).Where("id = ?", id).Select()
return typeCopy, wrapGenericTypeRepositoryError("FindTypeOfID", err)
}
// FindTypeOfName retrieves a type of the specified name
func (r *GenericTypeRepository) FindTypeOfName(name string) (models.IGenericType, error) {
typeCopy := reflect.New(reflect.ValueOf(r.Type).Elem().Type()).Interface().(models.IGenericType)
err := r.DB.Model(typeCopy).Where("name = ?", name).Select()
return typeCopy, wrapGenericTypeRepositoryError("FindTypeOfName", err)
}
// Persist persists a type
func (r *GenericTypeRepository) Persist(t models.IGenericType) (models.IGenericType, error) {
_, err := r.DB.Model(t).
OnConflict("(id) DO UPDATE").
Set("name = EXCLUDED.name").
Insert()
return t, wrapGenericTypeRepositoryError("Persist", err)
}
Oh wow! Thanks @l0gicgate.
What do you think about not using reflection at all?
@frederikhors I haven't really touched this code since last year so I have no particular opinion on not using reflection. If the same result can be achieved without reflection then I'm all for it! At the time, that's the solution I came up with, there may be a better way to do this.