Search code examples
javascriptnode.jsmongodbmongooseatomic

Complex addition to subdocument array with mongoose


My data model has Accounts, and an accounts has some credit transactions for it. I have designed these transactions as subdocuments:

var TransactionSchema = new Schema({
        amount: Number,
        added: Date
    }),
    AccountSchema = new Schema({
        owner: ObjectId,
        balance: Number,
        transactions: [TransactionSchema]
    });

When a Transaction is added to an Account, the following should happen:

  • transactions has the new one pushed to it
  • transactions is sorted by date (for later display)
  • balance is set to the sum of all transactions

I have put that in a Schema.methods-function for now, doing the above in JavaScript, before a save. However, I am not sure that is secure for multiple inserts at once.

How can this better be solved in Mongoose to use atomic or some kind of transactional update? In SQL, I would just do a transaction, but I cannot in MongoDB, so how to make sure that transactions and balance are always correct?


Solution

  • You can do all of that with a single update call that combines all three of those operations (which is the only way to make a combination of updates atomic). You don't sum the transactions during the update, instead you update balance with the amount of the change:

    var transaction = {
        amount: 500,
        added: new Date()
    };
    
    Account.update({owner: owner}, {
            // Adjust the balance by the amount in the transaction.
            $inc: {balance: transaction.amount},
            // Add the transaction to transactions while sorting by added.
            $push: {transactions: {
                $each: [transaction],
                $sort: {added: 1}
            }}
        }, callback);
    

    Note that this does utilize the $sort modifier for the $push which was added in 2.4 and updated in 2.6 so you need to be using a recent build.