Juicy
Juicy

Reputation: 12530

Upsert document while pushing to array

I have the following Schema:

var TestSchema = db.Schema({
    name: { type: String, required: true, unique: true }
    data: []
},
{ strict: false });

var Test = db.model('Test', TestSchema);

Notice it is strict: false. I would like to be able to upsert new documents. The thing is, I don't want the data array to be overwritten, but rather for new documents to be pushed into it.

For example, assuming this is an existing document:

{ name: "hello world",
  data: 
   [ { one: '123',
       two: '456' } ]
}

And I wish to upsert this one:

{ name: "hello world",
  new_field: "to be updated"
  data: 
   [ { one: 'pushed to the array',
       two: 'xyz' } ]
}

The expected result would be:

{ name: "hello world",
  new_field: "to be updated"
  data: 
   [ { one: 'abc',
       two: 'xyz' },
     { one: 'pushed to the array',
       two: 'xyz'} ]
}

To explicitly explain: the document already exists, so it should be updated. The new field new_field is updated. However instead of overwriting the existing data array (as a regular update would), we push the new documents to the array.

I have a very ugly working version that uses three calls to achieve this, which is completely unusable (asynchronous -> duplicates get inserted instead of updated when you throw many queries at the same time).

Can this actually be achieved in Mongoose?

Upvotes: 2

Views: 293

Answers (2)

Juicy
Juicy

Reputation: 12530

I've solved this in a nice and clean way:

// Let 'test' be a Test object who's values we want to upsert
test = { 
  name: "hello world",
  new_field: "to be updated"
  data: 
   [ { one: 'pushed to the array',
       two: 'xyz' } ]
}

// We do this to 'test'
test['$addToSet'] = {
  'data': { '$each': test.data }
};
delete test.data

// 'test' now looks like this
// test = { 
//   'name': 'hello world',
//   'new_field': 'to be updated'
//   '$addToSet': { data: { '$each': [Object] } }
// }

// And this is how we upsert 'test'
// while also upserting values into the data array
Test.findOneAndUpdate({
    name: test.name,
}, test, { upsert: true, new: true }, function(error, doc) {
    console.log(doc);
});

I've been looking how to do this for a while, and I didn't find it straightforwardly explained anywhere, but it works and it's useful.

Upvotes: 0

Hsm Sharique Hasan
Hsm Sharique Hasan

Reputation: 203

You can use $push like this:

{$set:{ new_field: "to be updated" },$push:{data:{ one: 'pushed to the array',two: 'xyz' }}}

Update query would be:

db.test.update({name: "hello world"}, {
$set:{ new_field: "to be updated" } 
$push:{data:{ one: 'pushed to the array',two: 'xyz' }}});

Upvotes: 2

Related Questions