I've a game analytics rest API which stores the average performance statistics of the players. When a new statistic arrives, I want to update the existing game record in Mongodb by merging the new delta onto the existing document. I'm storing the past analytics data as well. So that, I can return data like the player's stats are decreasing or increasing since the game's last update.
The problem is: When I want to upsert my new game data into Mongodb with mgo, it overwrites all of a player's stats array. Actually, this is expected. I know how to fix it if I can modify my document that mgo tries to upsert into Mongodb.
Question: How can I customize mgo upsert behaviour? So that I can add a $push
operator in front of Player.Stats
to prevent Mongodb erasing the stats
array inside the document.
My Real Question: It doesn't matter which Mongo commands I'm going to use. I'll figure it out somehow. What I actually want to know is: How can I customize the behaviour of mgo before upsert?
Some Solutions: I've tried some solutions myself before. Like, encoding/decoding Game
struct into bson.M
to customize it. However, I found it cumbersome and messy. If there's no other way, I'd use it.
Blocks: I don't want to hand-write all of my structs fields with bson.M
, just to use a $push
operator on one field. Because there are dozens of fields, that would be error-prone and will increase my code complexity.
Example:
// Assume that, this is an existing game in Mongodb:
existingGame := Game{
ID: 1,
Name: "Existing game",
// The game has just one player
Players: []Player{
// The player has some stats. The newest one is 2.0.
{1, "foo", []{3.5, 2.0}},
}
}
// This is a new request coming to my API
// I want to upsert this into the existing Game
newGame := Game{
ID: 1,
Players: []Player{
// As expectedly, this will reset player foo's stats to 5.0
//
// After upserting, I want it to be as:
//
// []{3.5, 2.0, 5.0}
//
// in Mongodb
{1, "foo", []{5.0}},
}
}
// Example 2:
// If new Game request like this:
newGame := Game{ID: 1, Players: []Player{{1, "foo", []{5.0},{1, "bar", []{6.7}}}}
// I'm expecting this result:
Game{ID: 1, Players: []Player{{1, "foo", []{3.5, 2.0, 5.0},{1, "bar", []{6.7}}}}
func (db *Store) Merge(newGame *Game) error {
sess := db.session.Copy()
defer sess.Close()
col := sess.DB("foo").C("games")
// I want to modify newGame here to add a $push operator
// into a new `bson.M` or `bson.D` to make mgo to upsert
// my new delta without resetting the player stats
_, err := col.UpsertId(newGame.ID, newGame)
return err
}
type Game struct {
ID int `bson:"_id"`
Name string
Players []Player `bson:",omitempty"`
// ...I omitted other details for simplicity here...
}
type Player struct {
// This connects the player to the game
GameID int `bson:"game_id"`
Name string
// I want to keep the previous values of stats
// So, that's why I'm using an array here
Stats []float64
// ...
}
I tried this Mongodb command in the console to update the specific game's player:
db.competitions.update({
_id: 1,
"players.game_id": 1
}, {
$push: {
"players.$.stats": 3
}
}, {
upsert: true
})