Reputation: 6422
I'd like to have a single method that either creates or updates a document for a policy. Searching and trying different techniques like this one, I have come up with a null _id for my document. Using findByIdAndUpdate has a similar affect.
I see a document inserted in the collection, but the _id field is null:
exports.savePolicy = function (plcy, callback) {
console.log('priority is : ' + plcy.priority)
try {
var policy = new Policy(plcy);
var query = {_id: plcy._id}; //this may be null
var update = {
name: plcy.name || defaults.policyDefaults.name,
longDescription: plcy.longDescription || defaults.policyDefaults.longDescription,
shortDescription: plcy.shortDescription || defaults.policyDefaults.shortDescription,
priority: plcy.priority, colorHex: plcy.colorHex || defaults.policyDefaults.colorHex,
settings: plcy.settings || [],
parentPolicyId: plcy.parentPolicyId || null
}
Policy.findOneAndUpdate(query, update, {upsert: true}, function (err, data) {
callback(err, data);
});
} catch (e) {
log.error('Exception while trying to save policy: ' + e.message);
callback(e, null);
}
Is there something that can be done to get the _id not to be null when its not an update?
Upvotes: 32
Views: 26903
Reputation: 111
The easiest way to update document if there's an ID or create a new document if there's no ID is:
Model.findOneAndUpdate({ _id: id ?? new mongoose.Types.ObjectId() }, updates, { upsert: true });
Note the use of the nullish coalescing operator (??) which allows 0 as a valid id. The updates
object doesn't need to contain an id
property, as mongoose will add it automatically:
if no document matches filter, MongoDB will insert one by combining filter and update as shown below.
Upvotes: 5
Reputation: 23586
This could be useful (from the docs):
If you specify an _id field in either the query parameter or replacement document, MongoDB uses that _id field in the inserted document.
So when you write:
model.findOneAndUpdate({_id: id}, data, f);
And id
is null
, it will insert a document with null
as id.
Upvotes: 1
Reputation: 3212
Since we can now add default properties while destructuring objects in JavaScript, in the case that we are checking to see if a document exists, I find this the easiest way to either query via an existing _id or create a new one in the same operation, avoiding the null id problem:
// someController.js using a POST route
async function someController(req, res) {
try {
const {
// the default property will only be used if the _id doesn't exist
_id: new mongoose.Types.ObjectId(),
otherProp,
anotherProp
} = req.body;
const createdOrUpdatedDoc = await SomeModel.findOneAndUpdate(
{
_id
},
{
otherProp,
anotherProp
},
{
new: true,
upsert: true
}
).exec();
return res.json(201).json(createdOrUpdatedDoc);
} catch (error) {
return res.json(400).send({
error,
message: "Could not do this"
})
}
}
Upvotes: 0
Reputation: 1279
Try setting 'new' parameter to true:
{ upsert: true, new: true }
More info: https://github.com/Automattic/mongoose/issues/2939
Upvotes: -2
Reputation: 914
Thanks to JohnnyHK for a helpful answer above. I came up with a one-liner since it's used so frequently:
query = args._id ? { _id: args._id } : { _id: new ObjectId() };
It relies on the following require:
const ObjectId = require('mongodb').ObjectID;
Upvotes: 3
Reputation: 117
I am not using Mongoose, but I run into similar issue with MongoDB. For Upsert action when new object was inserted the MongoDB was setting null
to _id
.
I was calling:
findOneAndUpdate({my_id:'my_unique_id'}, obj, {upsert: true})
where obj._id
was undefined
.
The problem was that _id
was present on list of keys Object.keys(obj)
. I found that I was assigning obj._id = some_variable
, where some_variable
was undefined
, and that was causing _id
to appear on a list of keys.
I applied workaround by calling right before upsert:
if (_.isUndefined(obj._id)) {
delete obj._id;
}
Upvotes: 1
Reputation: 312095
null
is a valid _id
value in MongoDB, so if you don't want it used in new docs you must ensure that a null
value is replaced with a new ObjectID
in query
:
var query = {_id: plcy._id};
if (!query._id) {
query._id = new mongoose.mongo.ObjectID();
}
// the rest stays the same...
Upvotes: 25
Reputation: 1381
I had this same problem and couldn't figure out a way to get it to work. I ended up writing my own upsert method like so....
var upsert = function(model, data, f){
if (!data._id) {
model.create(data, f);
} else {
var id = data._id;
delete data._id;
model.findOneAndUpdate({_id: id}, data, f);
}
}
That allows me to call it for any of my models with one line of code...
upsert(Team, team, f);
There might be a better way to do it, but this is working for me. I can do updates and inserts and I don't get a null _id on insert.
Upvotes: 6
Reputation: 1276
Try setting the upsert parameter to true in your call to update. From the mongoid docs: http://docs.mongodb.org/manual/reference/method/db.collection.update/#update-parameter
Optional. If set to true, creates a new document when no document matches the query criteria. The default value is false, which does not insert a new document when no match is found.
Upvotes: -3