Pg: Need help with GenericType repository

Created on 10 Jul 2019  路  5Comments  路  Source: go-pg/pg

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?

All 5 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pahanini picture pahanini  路  4Comments

jayschwa picture jayschwa  路  4Comments

enchantner picture enchantner  路  3Comments

NinaLeven picture NinaLeven  路  6Comments

AbooJan picture AbooJan  路  5Comments