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())
}