Reputation: 27811
Perhaps it's the time, perhaps it's me drowning in sparse documentation and not being able to wrap my head around the concept of updating in Mongoose :)
Here's the deal:
I have a contact schema and model (shortened properties):
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var mongooseTypes = require("mongoose-types"),
useTimestamps = mongooseTypes.useTimestamps;
var ContactSchema = new Schema({
phone: {
type: String,
index: {
unique: true,
dropDups: true
}
},
status: {
type: String,
lowercase: true,
trim: true,
default: 'on'
}
});
ContactSchema.plugin(useTimestamps);
var Contact = mongoose.model('Contact', ContactSchema);
I receive a request from the client, containing the fields I need and use my model thusly:
mongoose.connect(connectionString);
var contact = new Contact({
phone: request.phone,
status: request.status
});
And now we reach the problem:
contact.save(function(err){...})
I'll receive an error if the contact with the same phone number already exists (as expected - unique)update()
on contact, since that method does not exist on a documentContact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
{status: request.status, phone: request.phone ...}
it works - but then I have no reference to the specific contact and cannot find out its createdAt
and updatedAt
properties.So the bottom line, after all I tried: given a document contact
, how do I update it if it exists, or add it if it doesn't?
Thanks for your time.
Upvotes: 512
Views: 730165
Reputation: 14659
Mongoose now supports this natively with findOneAndUpdate (calls MongoDB findAndModify).
The upsert = true option creates the object if it doesn't exist. defaults to false.
var query = {'username': req.user.username};
req.newData.username = req.user.username;
MyModel.findOneAndUpdate(query, req.newData, {upsert: true}, function(err, doc) {
if (err) return res.send(500, {error: err});
return res.send('Succesfully saved.');
});
In older versions Mongoose does not support these hooks with this method:
Upvotes: 572
Reputation: 3872
I'm the maintainer of Mongoose. The more modern way to upsert a doc is to use the Model.updateOne()
function.
await Contact.updateOne({
phone: request.phone
}, { status: request.status }, { upsert: true });
If you need the upserted doc, you can use Model.findOneAndUpdate()
const doc = await Contact.findOneAndUpdate({
phone: request.phone
}, { status: request.status }, { upsert: true, useFindAndModify: false });
The key takeaway is that you need to put the unique properties in the filter
parameter to updateOne()
or findOneAndUpdate()
, and the other properties in the update
parameter.
Here's a tutorial on upserting documents with Mongoose.
Upvotes: 85
Reputation: 156
You can simply update the record with this and get the updated data in response
router.patch('/:id', (req, res, next) => {
const id = req.params.id;
Product.findByIdAndUpdate(id, req.body, {
new: true
},
function(err, model) {
if (!err) {
res.status(201).json({
data: model
});
} else {
res.status(500).json({
message: "not found any relative data"
})
}
});
});
Upvotes: 8
Reputation: 129
Here's the simplest way to create/update while also calling the middleware and validators.
Contact.findOne({ phone: request.phone }, (err, doc) => {
const contact = (doc) ? doc.set(request) : new Contact(request);
contact.save((saveErr, savedContact) => {
if (saveErr) throw saveErr;
console.log(savedContact);
});
})
Upvotes: 4
Reputation: 5683
I just came back to this issue after a while, and decided to publish a plugin based on the answer by Aaron Mast.
https://www.npmjs.com/package/mongoose-recursive-upsert
Use it as a mongoose plugin. It sets up a static method which will recursively merge the object passed in.
Model.upsert({unique: 'value'}, updateObject});
Upvotes: 2
Reputation: 1633
Following Traveling Tech Guy's answer, which already awesome, we can create a plugin and attach it to mongoose once we initialise it so that .upsert()
will be available on all models.
plugins.js
export default (schema, options) => {
schema.statics.upsert = async function(query, data) {
let record = await this.findOne(query)
if (!record) {
record = new this(data)
} else {
Object.keys(data).forEach(k => {
record[k] = data[k]
})
}
return await record.save()
}
}
db.js
import mongoose from 'mongoose'
import Plugins from './plugins'
mongoose.connect({ ... })
mongoose.plugin(Plugins)
export default mongoose
Then you can do something like User.upsert({ _id: 1 }, { foo: 'bar' })
or YouModel.upsert({ bar: 'foo' }, { value: 1 })
whenever you want.
Upvotes: 3
Reputation: 326
this worked for me.
app.put('/student/:id', (req, res) => {
Student.findByIdAndUpdate(req.params.id, req.body, (err, user) => {
if (err) {
return res
.status(500)
.send({error: "unsuccessful"})
};
res.send({success: "success"});
});
});
Upvotes: 6
Reputation: 5535
No other solution worked for me. I'm using a post request and updating data if found else insert it, also _id is sent with the request body that's needs to be removed.
router.post('/user/createOrUpdate', function(req,res){
var request_data = req.body;
var userModel = new User(request_data);
var upsertData = userModel.toObject();
delete upsertData._id;
var currentUserId;
if (request_data._id || request_data._id !== '') {
currentUserId = new mongoose.mongo.ObjectId(request_data._id);
} else {
currentUserId = new mongoose.mongo.ObjectId();
}
User.update({_id: currentUserId}, upsertData, {upsert: true},
function (err) {
if (err) throw err;
}
);
res.redirect('/home');
});
Upvotes: 3
Reputation: 5634
User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => {
if(err) return res.json(err);
res.json({ success: true });
});
Upvotes: 2
Reputation: 29
to build on what Martin Kuzdowicz posted above. I use the following to do an update using mongoose and a deep merge of json objects. Along with the model.save() function in mongoose this allows mongoose to do a full validation even one that relies on other values in the json. it does require the deepmerge package https://www.npmjs.com/package/deepmerge. But that is a very light weight package.
var merge = require('deepmerge');
app.put('url', (req, res) => {
const modelId = req.body.model_id;
MyModel.findById(modelId).then((model) => {
return Object.assign(model, merge(model.toObject(), req.body));
}).then((model) => {
return model.save();
}).then((updatedModel) => {
res.json({
msg: 'model updated',
updatedModel
});
}).catch((err) => {
res.send(err);
});
});
Upvotes: 0
Reputation: 389
Very elegant solution you can achieve by using chain of Promises:
app.put('url', (req, res) => {
const modelId = req.body.model_id;
const newName = req.body.name;
MyModel.findById(modelId).then((model) => {
return Object.assign(model, {name: newName});
}).then((model) => {
return model.save();
}).then((updatedModel) => {
res.json({
msg: 'model updated',
updatedModel
});
}).catch((err) => {
res.send(err);
});
});
Upvotes: 33
Reputation: 267
I created a StackOverflow account JUST to answer this question. After fruitlessly searching the interwebs I just wrote something myself. This is how I did it so it can be applied to any mongoose model. Either import this function or add it directly into your code where you are doing the updating.
function upsertObject (src, dest) {
function recursiveFunc (src, dest) {
_.forOwn(src, function (value, key) {
if(_.isObject(value) && _.keys(value).length !== 0) {
dest[key] = dest[key] || {};
recursiveFunc(src[key], dest[key])
} else if (_.isArray(src) && !_.isObject(src[key])) {
dest.set(key, value);
} else {
dest[key] = value;
}
});
}
recursiveFunc(src, dest);
return dest;
}
Then to upsert a mongoose document do the following,
YourModel.upsert = function (id, newData, callBack) {
this.findById(id, function (err, oldData) {
if(err) {
callBack(err);
} else {
upsertObject(newData, oldData).save(callBack);
}
});
};
This solution may require 2 DB calls however you do get the benefit of,
Just remember that the destination object will always override the source even if the source has an existing value
Also, for arrays, if the existing object has a longer array than the one replacing it then the values at the end of the old array will remain. An easy way to upsert the entire array is to set the old array to be an empty array before the upsert if that is what you are intending on doing.
UPDATE - 01/16/2016 I added an extra condition for if there is an array of primitive values, Mongoose does not realize the array becomes updated without using the "set" function.
Upvotes: 18
Reputation: 16996
app.put('url', function(req, res) {
// use our bear model to find the bear we want
Bear.findById(req.params.bear_id, function(err, bear) {
if (err)
res.send(err);
bear.name = req.body.name; // update the bears info
// save the bear
bear.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Bear updated!' });
});
});
});
Here is a better approach to solving the update method in mongoose, you can check Scotch.io for more details. This definitely worked for me!!!
Upvotes: 10
Reputation: 21005
This coffeescript works for me with Node - the trick is that the _id get's stripped of its ObjectID wrapper when sent and returned from the client and so this needs to be replaced for updates (when no _id is provided, save will revert to insert and add one).
app.post '/new', (req, res) ->
# post data becomes .query
data = req.query
coll = db.collection 'restos'
data._id = ObjectID(data._id) if data._id
coll.save data, {safe:true}, (err, result) ->
console.log("error: "+err) if err
return res.send 500, err if err
console.log(result)
return res.send 200, JSON.stringify result
Upvotes: 0
Reputation: 4174
If generators are available it becomes even more easier:
var query = {'username':this.req.user.username};
this.req.newData.username = this.req.user.username;
this.body = yield MyModel.findOneAndUpdate(query, this.req.newData).exec();
Upvotes: 3
Reputation: 31
//Here is my code to it... work like ninj
router.param('contractor', function(req, res, next, id) {
var query = Contractors.findById(id);
query.exec(function (err, contractor){
if (err) { return next(err); }
if (!contractor) { return next(new Error("can't find contractor")); }
req.contractor = contractor;
return next();
});
});
router.get('/contractors/:contractor/save', function(req, res, next) {
contractor = req.contractor ;
contractor.update({'_id':contractor._id},{upsert: true},function(err,contractor){
if(err){
res.json(err);
return next();
}
return res.json(contractor);
});
});
--
Upvotes: 2
Reputation: 14219
For anyone arriving here still looking for good a solution for "upserting" with hooks support, this is what I have tested and working. It still requires 2 DB calls but is much more stable than anything I've tried in a single call.
// Create or update a Person by unique email.
// @param person - a new or existing Person
function savePerson(person, done) {
var fieldsToUpdate = ['name', 'phone', 'address'];
Person.findOne({
email: person.email
}, function(err, toUpdate) {
if (err) {
done(err);
}
if (toUpdate) {
// Mongoose object have extra properties, we can either omit those props
// or specify which ones we want to update. I chose to update the ones I know exist
// to avoid breaking things if Mongoose objects change in the future.
_.merge(toUpdate, _.pick(person, fieldsToUpdate));
} else {
toUpdate = person;
}
toUpdate.save(function(err, updated, numberAffected) {
if (err) {
done(err);
}
done(null, updated, numberAffected);
});
});
}
Upvotes: 3
Reputation: 131
I needed to update/upsert a document into one collection, what I did was to create a new object literal like this:
notificationObject = {
user_id: user.user_id,
feed: {
feed_id: feed.feed_id,
channel_id: feed.channel_id,
feed_title: ''
}
};
composed from data that I get from somewhere else in my database and then call update on the Model
Notification.update(notificationObject, notificationObject, {upsert: true}, function(err, num, n){
if(err){
throw err;
}
console.log(num, n);
});
this is the ouput that I get after running the script for the first time:
1 { updatedExisting: false,
upserted: 5289267a861b659b6a00c638,
n: 1,
connectionId: 11,
err: null,
ok: 1 }
And this is the output when I run the script for the second time:
1 { updatedExisting: true, n: 1, connectionId: 18, err: null, ok: 1 }
I'm using mongoose version 3.6.16
Upvotes: 13
Reputation: 2811
You were close with
Contact.update({phone:request.phone}, contact, {upsert: true}, function(err){...})
but your second parameter should be an object with a modification operator for example
Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})
Upvotes: 105
Reputation: 1548
There is a bug introduced in 2.6, and affects to 2.7 as well
The upsert used to work correctly on 2.4
https://groups.google.com/forum/#!topic/mongodb-user/UcKvx4p4hnY https://jira.mongodb.org/browse/SERVER-13843
Take a look, it contains some important info
It doesnt mean upsert does not work. Here is a nice example of how to use it:
User.findByIdAndUpdate(userId, {online: true, $setOnInsert: {username: username, friends: []}}, {upsert: true})
.populate('friends')
.exec(function (err, user) {
if (err) throw err;
console.log(user);
// Emit load event
socket.emit('load', user);
});
Upvotes: 8
Reputation: 973
After reading the posts above, I decided to use this code:
itemModel.findOne({'pid':obj.pid},function(e,r){
if(r!=null)
{
itemModel.update({'pid':obj.pid},obj,{upsert:true},cb);
}
else
{
var item=new itemModel(obj);
item.save(cb);
}
});
if r is null, we create new item. Otherwise, use upsert in update because update does not create new item.
Upvotes: -6
Reputation: 14524
I just burned a solid 3 hours trying to solve the same problem. Specifically, I wanted to "replace" the entire document if it exists, or insert it otherwise. Here's the solution:
var contact = new Contact({
phone: request.phone,
status: request.status
});
// Convert the Model instance to a simple object using Model's 'toObject' function
// to prevent weirdness like infinite looping...
var upsertData = contact.toObject();
// Delete the _id property, otherwise Mongo will return a "Mod on _id not allowed" error
delete upsertData._id;
// Do the upsert, which works like this: If no Contact document exists with
// _id = contact.id, then create a new doc using upsertData.
// Otherwise, update the existing doc with upsertData
Contact.update({_id: contact.id}, upsertData, {upsert: true}, function(err{...});
I created an issue on the Mongoose project page requesting that info about this be added to the docs.
Upvotes: 211
Reputation: 27811
Well, I waited long enough and no answer. Finally gave up the whole update/upsert approach and went with:
ContactSchema.findOne({phone: request.phone}, function(err, contact) {
if(!err) {
if(!contact) {
contact = new ContactSchema();
contact.phone = request.phone;
}
contact.status = request.status;
contact.save(function(err) {
if(!err) {
console.log("contact " + contact.phone + " created at " + contact.createdAt + " updated at " + contact.updatedAt);
}
else {
console.log("Error: could not save contact " + contact.phone);
}
});
}
});
Does it work? Yep. Am I happy with this? Probably not. 2 DB calls instead of one.
Hopefully a future Mongoose implementation would come up with a Model.upsert
function.
Upvotes: 83