0

I'm trying to implement a MongoDB update for a Go struct. Stripped down to essentials, it looks something like this:

type MyStruct struct {
        Id     bson.ObjectId `bson:"_id"`
        Fruit string         `bson:"fruit"`
}

func TestUpdate(t *testing.T) {
        obj1 := MyStruct{Id: bson.NewObjectId(),Fruit: "apple"}
        var obj2 MyStruct

        session, _ := mgo.Dial("whatever")
        col := session.DB("test").C("collection")
        col.Insert(&obj1)
        obj1.Fruit = "cherry"
        if err := col.Update(obj1.Id, bson.M{"$set": &obj1}); err != nil {
                t.Errorf(err.Error())
        }
        if err := col.Find(bson.M{"Id": obj1.Id}).One(&obj2); err != nil {
                t.Errorf(err.Error())
        }
        if obj1.Fruit != obj2.Fruit {
                t.Errorf("Expected %s, got %s", obj1.Fruit, obj2.Fruit)
        }
}

This generates the error message, indicating that the value wasn't updated. What am I missing?

I understand that just updating one field is possible, but given that this is in a data layer, above which the code doesn't have any knowledge of MongoDB, that would be challenging to implement in a general way. I.e. I really need to make any updates to the Go object, and then update the copy of the object in the backing store. I suppose that I could retrieve the object and do a "diff" manually, constructing a "$set" document, but that doesn't seem like adding a retrieval every time I do an update would be very efficient.

Edit: Trying a map with "_id" deleted

I've tried amending the code to the following:

package testmgo

import (
    mgo "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
    "github.com/fatih/structs"
    "testing"
)

type MyStruct struct {
    Id     bson.ObjectId `bson:"_id"`
    Fruit string         `bson:"fruit"`
}

func TestUpdate(t *testing.T) {
    obj1 := MyStruct{Id: bson.NewObjectId(),Fruit: "apple"}
    var obj2 MyStruct

    session, _ := mgo.Dial("localhost")
    col := session.DB("test").C("collection")
    col.Insert(&obj1)
    obj1.Fruit = "cherry"
    omap := structs.Map(&obj1)
    delete(omap, "_id")

    if err := col.UpdateId(obj1.Id, bson.M{"$set": bson.M(omap)}); err != nil {
        t.Errorf(err.Error())
    }
    if err := col.Find(bson.M{"Id": obj1.Id}).One(&obj2); err != nil {
        t.Errorf(err.Error())
    }
    if obj1.Fruit != obj2.Fruit {
        t.Errorf("Expected %s, got %s", obj1.Fruit, obj2.Fruit)
    }
}

and am still receiving the same results (Expected cherry, got apple). Note that the call to UpdateId() is not returning an error.

Scott Deerwester
  • 3,503
  • 4
  • 33
  • 56
  • You are trying to update the `_id` field with this statement `col.Update(obj1.Id, bson.M{"$set": &obj1})` which is not possible in MongoDB, hence the error. You need to create an object which does not have the `_id` field to use in your `$set` expression. – chridam May 06 '16 at 14:23
  • I'm not seeing how to do that with a struct. The closest I could come is marshaling it to BSON, then unmarshaling then to a map, then deleting the "_id" entry from the map. Is there a better way to do this? – Scott Deerwester May 06 '16 at 20:44

1 Answers1

0

The problem was that I was using the wrong field as the key. I had mapped "Id" to "_id", but was then asking MongoDB to find a record using the Go attribute name rather than the name. This works correctly:

package testmgo

import (
    mgo "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
    "testing"
)

type MyStruct struct {
    Id    bson.ObjectId `bson:"_id"`
    Fruit string        `bson:"fruit"`
}

func TestUpdate(t *testing.T) {
    obj1 := MyStruct{Id: bson.NewObjectId(), Fruit: "apple"}
    var obj2 MyStruct

    session, _ := mgo.Dial("localhost")
    col := session.DB("test").C("collection")
    col.Insert(&obj1)
    obj1.Fruit = "cherry"

    if err := col.UpdateId(obj1.Id, bson.M{"$set": &obj1}); err != nil {
        t.Errorf(err.Error())
    }
    if err := col.Find(bson.M{"_id": obj1.Id}).One(&obj2); err != nil {
        t.Errorf(err.Error())
    }
    if obj1.Fruit != obj2.Fruit {
        t.Errorf("Expected %s, got %s", obj1.Fruit, obj2.Fruit)
    }
}
Scott Deerwester
  • 3,503
  • 4
  • 33
  • 56