2

Original Question

When using the Update method in GORM the new data does not get saved. i.e. I want to set a bool from true to false, but it stays true even after the Update method.

In the description of the method there is a warning: "WARNING when update with struct, GORM will not update fields that with zero value"

Since I am using a struct to update and false is the zero value of bool, this seems expected behaviour, but I don't see any reason why to do so and how to overcome this.

func UpdateData(c *fiber.Ctx) error {
    db := database.DBConn

    data := new([]entities.Data)

    if err := c.BodyParser(&data); err != nil {
        return err
    }

    db.Update(&data)
    return c.JSON(data)
}

Solution Summary

First, as suggested I left out the new keyword when instantiating the structs. Then, I used a helper function (from here) for converting a struct to map while keeping the json alias as keys:

// StructToMap Converts a struct to a map while maintaining the json alias as keys
func StructToMap(obj interface{}) (newMap map[string]interface{}, err error) {
    data, err := json.Marshal(obj)

    if err != nil {
        return
    }

    err = json.Unmarshal(data, &newMap) // Convert to a map
    return
}

Then I loop over each element in the data slice in order to convert it and update it one by one:

func UpdateData(c *fiber.Ctx) error {
    db := database.DBConn

    data := []entities.Dard{}

    if err := c.BodyParser(&data); err != nil {
        return err
    }

    for _, record := range data {
        mappedData, _ := StructToMap(record)
        db.Model(&entities.Data{}).Update(mappedData)
    }

    return c.JSON(data)
}

*Error handling is obviously reduced in this example.

Heikkisorsa
  • 740
  • 9
  • 31
  • Downvotes are intentionally anonymous. Asking for an explanation is inappropriate. – Jonathan Hall Oct 13 '20 at 07:56
  • Downvotes are self-explanatory. They mean, by definition, "This question does not show any research effort; it is unclear or not useful" (hover over the downvote arrow to see this). And the issue of explaining downvotes has been covered on meta thousands of times. It's boring. – Jonathan Hall Oct 13 '20 at 07:57
  • Does this answer your question? https://stackoverflow.com/q/54523765/13860 – Jonathan Hall Oct 13 '20 at 08:05
  • Unfortunately not, since it involves changing the structs which has effects on the whole project. As stated in the question, I don't see a reason why this is the expected behavior of the `Update` method. – Heikkisorsa Oct 13 '20 at 08:23
  • 1
    The question of _why_ it's the behavior is something to ask the GORM author(s). If changing the type to a pointer isn't suitable for you, then I'm out of ideas. But I'm not a GORM user, so hopefully someone else will come along with something better. – Jonathan Hall Oct 13 '20 at 08:24
  • How can I improve the question to get better answers? – Heikkisorsa Oct 13 '20 at 08:24
  • 1
    "How can I improve the question to get better answers?" <-- Now _that_ is a good way to ask for feedback. I don't have any suggestions. The question is also only an hour old, so I wouldn't expect great answers yet. – Jonathan Hall Oct 13 '20 at 08:25
  • 1
    Avoid using `new` as much as possible. `data` is of type `*[]entities.Data` (`new` is a builtin that looks sort of like: `new(T) *T`). You therefore are passing `**[]entities.Data` to the `BodyParser` and `Update` functions. If your aim is to create a new slice, either write: `data := []entities.Data{}`, or `data := make([]entities.Data, len, cap)`. Check all errors functions may return, too – Elias Van Ootegem Oct 13 '20 at 09:50
  • @EliasVanOotegem Makes sense, thank you for pointing this out. – Heikkisorsa Oct 13 '20 at 11:49

2 Answers2

8

From official doc

NOTE When update with struct, GORM will only update non-zero fields, you might want to use map to update attributes or use Select to specify fields to update

So use map[string]interface{} to update non-zero fields also. Example:

db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})

As you have struct already you can convert struct into map[string]interface{} (See details about conversion) then update. Another way is change the type of field as pointer.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Eklavya
  • 17,618
  • 4
  • 28
  • 57
  • thank you for your response. I tried this, but I am not sure how to use it with the `data` variable which holds the incoming data and is a slice of structs (hence `new([]entities.Data)`). Using `map[string]interface{}{structs.Map(&data)}` does not work. – Heikkisorsa Oct 13 '20 at 11:19
  • If `entities.Data` is your model then you need to convert every `entities.Data` into `map[string]interface{}` and call `Updates()` – Eklavya Oct 13 '20 at 11:30
0

As for me, all fields must be saved by default, also with empty values (because, how to delete an field value from the model instance?).

Actual documentation has an example, how to enable all fields for update (regarding the field permissions, e.g. it will not update createonly fields).

// Select all fields (select all fields include zero value fields)
db.Moodel(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
Pavel Patrin
  • 1,630
  • 1
  • 19
  • 33