Your Question

In Gorm v1, Selecting an ignored field used to work:

type user struct {
    ID     int64,
    Name   string,
    Amount  float64 `gorm:"-"`  // Amount column doesn't exist in users table.
}

var user models.User
s.db.Select(`users.*, SUM(values.amount) as amount`).
       Joins("join values on values.user_id = users.id").
       Where("users.id = ?", userID).
       First(&user)

But it's not the case anymore in Gorm V2 (No error, but the user.Amount value will be equal to 0).

How can I achieve the same thing in V2 ?

I do not want to set the permission for the Amount field to read Amount float64 `gorm:"->" because I have other endpoints that do a simple fetch like this:

var user models.User
db.Where("id = ?", userID).First(&user)

and changing the permission to read, will throw me this error: ERROR: column users.amount does not exist

Comment From: a631807682

You need to read the field amount in the first case but not in the second case, and there's no way to do that with field permissions.

You can use other struct like this

type UserExt struct {
    User
    Amount float64
}

...

var userExt models.UserExt
s.db.Select(`users.*, SUM(values.amount) as amount`).
       Joins("join values on values.user_id = users.id").
       Where("users.id = ?", userID).
       First(&userExt)

Comment From: Fakerr

The problem is that the field doesn't exist in the database. It's a custom one that we calculate during Select (in the example above, it's the SUM of another column in another table).

This used to work fine in v1 and it was such a helpful feature, I don't know why it changed in V2.

The proposed solutions to use a wrapper struct (like above) or to make the field read only gorm:"->" and Omit the column when it's needed are not very good solutions for our use case. It should remain simple as it used to be. @jinzhu thoughts on this ?

Comment From: github-actions[bot]

This issue has been automatically marked as stale because it has been open 360 days with no activity. Remove stale label or comment or this will be closed in 180 days

Comment From: viktorstaikov

I have the same case. Commenting to re-open the issue and hopefully get some resolution 🤞

Comment From: reantg

+1. Same problem. Nobody found a solution?

Comment From: honmaple

If anyone want to select some ignored fields, but wouldn't like to create column in database or write other struct that include unknown fields

type DynamicModel[T any] struct {
    newModel any
    oldModel T
    isset    bool
    values   map[string]reflect.Value
}

func (m *DynamicModel[T]) Model() T {
    if m.isset {
        return m.oldModel
    }
    v := reflect.ValueOf(m.newModel)
    for v.Kind() == reflect.Ptr {
        v = v.Elem()
    }

    t := v.Type()
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        if !field.IsExported() {
            continue
        }
        if oldVal, ok := m.values[field.Name]; ok {
            oldVal.Set(v.Field(i))
        }
    }
    m.isset = true
    return m.oldModel
}

func (m *DynamicModel[T]) NewModel() any {
    return m.newModel
}

func dynamicModel[T any](oldModel T, tagName string) *DynamicModel[T] {
    m := &DynamicModel[T]{oldModel: oldModel, values: make(map[string]reflect.Value)}

    v := reflect.ValueOf(oldModel)
    for v.Kind() == reflect.Ptr {
        v = v.Elem()
    }
    fields := make([]reflect.StructField, 0)

    t := v.Type()
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)

        tag := field.Tag.Get(tagName)
        if tag == "-" {
            field.Tag = reflect.StructTag(strings.ReplaceAll(string(field.Tag), fmt.Sprintf(`%s:"-"`, tagName), ""))
        }
        fields = append(fields, field)

        m.values[field.Name] = v.Field(i)
    }
    m.newModel = reflect.New(reflect.StructOf(fields)).Interface()
    return m
}

Then using

tmp := dynamicModel(&model.User{}, "gorm")
s.db.Select(`users.*, SUM(values.amount) as amount`).
     Joins("join values on values.user_id = users.id").
     Where("users.id = ?", userID).
     First(tmp.NewModel())
user := tmp.Model()

If want to use Find(&users),just using Rows instead of Find

rows, err := s.db.Select(`users.*, SUM(values.amount) as amount`).
     Joins("join values on values.user_id = users.id").
     Rows()
if err != nil {
    return nil, err
}
defer rows.Close()

users := make([]*model.User, 0)
for rows.Next() {
    tmp := dynamicModel(&model.User{}, "gorm")
    if err := s.db.ScanRows(rows, tmp.NewModel()); err != nil {
        return nil, err
    }
    users = append(users, tmp.Model())
}