Можно ли передать часть структуры в качестве интерфейса для метода? - PullRequest
1 голос
/ 24 апреля 2020

Я пишу простой API в Go, используя github.com/gin-gonic/gin. Он поддерживается базой данных, и я использую github.com/jinzhu/gorm для запросов и обновления базы данных.

У меня есть несколько конечных точек, которые в значительной степени используют один и тот же код для получения данных из базы данных, поэтому я пытаюсь создать один retrieve метод, который работает со всеми моими типами БД.

Так, например, у меня есть следующая структура:

type Image struct {
  gorm.Model
  Name string
  Filename string
}

Объявление API для моего маршрута: следующим образом:

func (v1 *ApiV1) getImage(c *gin.Context) {
  var images []db.Image

  httpStatus := v1.retrieve("name", c.Param("name"), &images)

  c.JSON(
    httpStatus,
    images,
  )
}

Как видно, я пытаюсь передать указатель на структуру, которую я хочу заполнить запросом. Поскольку мне понадобятся несколько различных представлений для разных типов в БД, я хочу сделать метод retrieve повторно используемым, поэтому я создал его с такой подписью:

func (v1 *ApiV1) retrieve(fieldName string, name string, returnObject *[]interface{}) int {

    // create var to hold the http status
    var httpStatus int = http.StatusOK

    // determine if a name has been passed
    if name == "" {
        v1.app.DB.Find(returnObject)
    } else {

        // look for the name in the database
        v1.app.DB.Where(fieldName+" = ?", name).First(returnObject)

        // if the name cannot be found then set the status to 404
        if len(*returnObject) == 0 {
            httpStatus = http.StatusNotFound
        }
    }

    return httpStatus
}

Чтобы попытаться сделать его 'обобщенным c 'Я использовал *[]interface{} в качестве типа для returnObject. (Имя немного неправильное, так как оно указатель, но оно позволяет легко понять, что что-то будет обновлено).

Однако, когда я запускаю это, я получаю следующую ошибку:

cannot use &images (type *[]db.Image) as type *[]interface {} in argument to v1.retrieve

Я полностью понимаю, что Go является строго типизированным языком, но, поскольку я хочу иметь возможность повторно использовать мою retrieve функцию, мне нужно иметь возможность передавать указатель на фрагмент (любого типа), который Gorm может заполняться при выполнении запроса.

Я в настоящее время рефакторинг этого из версии, которая имела отдельную функцию для извлечения для каждой из конечных точек API, но очевидно, что это неэффективно и затрудняет выполнение любых refactor.

Так что же я спрашиваю вообще возможно?

1 Ответ

0 голосов
/ 24 апреля 2020

Благодаря @mkopriva (и с перерывом на просмотр кода) я смог выяснить, как получить функцию поиска с возможностью восстановления.

Как указано в комментариях, я пытался сравнить []T с []I, который не будет работать. Так что я использовал рефакторинг кода для использования имени таблицы в цепочке GORM, что означает, что теперь результатом может быть интерфейс, и это возвращается как элемент возврата вместо указателя.

Так что теперь мой метод retrieve выглядит так:

func (v1 *ApiV1) retrieve(tableName string, fieldName string, name string) ([]interface{}, int) {

    // create var to hold the http status
    var httpStatus int = http.StatusOK
    var result []interface{}

    // determine if a name has been passed
    if name == "" {
        v1.app.DB.Find(result)
    } else {

        // look for the name in the database
        v1.app.DB.Table(tableName).Where(fieldName+" = ?", name).Order("id DESC").First(&result)

        // if the name cannot be found then set the status to 404
        if len(result) == 0 {
            httpStatus = http.StatusNotFound
        }
    }

    return result, httpStatus
}

Недостатком этого является то, что мне нужно передать имя таблицы БД для запроса, которое было выведено из объекта результата. Поскольку я вряд ли буду менять таблицы, это не будет слишком большим неудобством.

Мне также пришлось добавить ORDER в цепочку, чтобы обойти проблему MS SQL при использовании GORM.

Итак, теперь функция API выглядит следующим образом:

func (v1 *ApiV1) getImage(c *gin.Context) {

    // call method to retrieve the items from the database
    images, httpStatus := v1.retrieve("images", "name", c.Param("name"))

    c.JSON(
        httpStatus,
        images,
    )
}
...