diff --git a/schema/field.go b/schema/field.go index a6ff1a72..ff4aa7dd 100644 --- a/schema/field.go +++ b/schema/field.go @@ -457,7 +457,7 @@ func (field *Field) setupValuerAndSetter() { switch { case len(field.StructField.Index) == 1 && fieldIndex > 0: field.ValueOf = func(ctx context.Context, value reflect.Value) (interface{}, bool) { - fieldValue := reflect.Indirect(value).Field(fieldIndex) + fieldValue := reflect.Indirect(value).FieldByName(field.Name) return fieldValue.Interface(), fieldValue.IsZero() } default: diff --git a/tests/check_subset_model_change_test.go b/tests/check_subset_model_change_test.go new file mode 100644 index 00000000..69bb5ebc --- /dev/null +++ b/tests/check_subset_model_change_test.go @@ -0,0 +1,88 @@ +package tests_test + +import ( + "fmt" + "strings" + "testing" + + "gorm.io/gorm" +) + +type Man struct { + ID int + Age int + Name string + Detail string +} + +// Panic-safe BeforeUpdate hook that checks for Changed("age") +func (m *Man) BeforeUpdate(tx *gorm.DB) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic in BeforeUpdate: %v", r) + } + }() + + if !tx.Statement.Changed("age") { + return nil + } + return nil +} + +func (m *Man) update(data interface{}) error { + return DB.Set("data", data).Model(m).Where("id = ?", m.ID).Updates(data).Error +} + +func TestBeforeUpdateStatementChanged(t *testing.T) { + DB.AutoMigrate(&Man{}) + type TestCase struct { + BaseObjects Man + change interface{} + expectError bool + } + + testCases := []TestCase{ + { + BaseObjects: Man{ID: 1, Age: 18, Name: "random-name"}, + change: struct { + Age int + }{Age: 20}, + expectError: false, + }, + { + BaseObjects: Man{ID: 2, Age: 18, Name: "random-name"}, + change: struct { + Name string + }{Name: "name-only"}, + expectError: true, + }, + { + BaseObjects: Man{ID: 2, Age: 18, Name: "random-name"}, + change: struct { + Name string + Age int + }{Name: "name-only", Age: 20}, + expectError: false, + }, + } + + for _, test := range testCases { + DB.Create(&test.BaseObjects) + + // below comment is stored for future reference + // err := DB.Set("data", test.change).Model(&test.BaseObjects).Where("id = ?", test.BaseObjects.ID).Updates(test.change).Error + err := test.BaseObjects.update(test.change) + if strings.Contains(fmt.Sprint(err), "panic in BeforeUpdate") { + if !test.expectError { + t.Errorf("unexpected panic in BeforeUpdate for input: %+v\nerror: %v", test.change, err) + } + } else { + if test.expectError { + t.Errorf("expected panic did not occur for input: %+v", test.change) + } + if err != nil { + t.Errorf("unexpected GORM error: %v", err) + } + } + } +}