Come posso aggiornare / upsert un documento in Mongoose?

Forse è il momento, forse sono io che affogare nella documentazione sparsa e non essere in grado di avvolgere la mia mente intorno al concetto di aggiornamento in Mongoose 🙂

Ecco l’accordo:

Ho uno schema e un modello di contatto (proprietà abbreviate):

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); mongoose.model('Contact', ContactSchema); //is this line superflous?? var Contact = mongoose.model('Contact', ContactSchema); 

Ricevo una richiesta dal cliente, contenente i campi di cui ho bisogno e utilizzo il mio modello in questo modo:

 mongoose.connect(connectionString); var contact = new Contact({ phone: request.phone, status: request.status }); 

E ora arriviamo al problema:

  1. Se chiamo contact.save(function(err){...}) ricevo un errore se il contatto con lo stesso numero di telefono esiste già (come previsto – unico)
  2. Non riesco a chiamare update() sul contatto, poiché tale metodo non esiste su un documento
  3. Se chiamo aggiornamento sul modello:
    Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
    Vado in un loop infinito di alcuni tipi, dal momento che l’implementazione dell’aggiornamento di Mongoose chiaramente non vuole un object come secondo parametro.
  4. Se faccio lo stesso, ma nel secondo parametro passo un array associativo delle proprietà della richiesta {status: request.status, phone: request.phone ...} funziona – ma poi non ho alcun riferimento al contatto specifico e non può scoprire le sue proprietà createdAt e updatedAt .

Quindi la linea di fondo, dopo tutto quello che ho provato: dato un contact un documento, come posso aggiornarlo se esiste, o aggiungerlo se non lo fa?

Grazie per il tuo tempo.

Mongoose ora supporta questo in modo nativo con findOneAndUpdate (chiama MongoDB findAndModify ).

L’opzione upsert = true crea l’object se non esiste. il valore predefinito è falso .

 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"); }); 

Modifica: Mongoose non supporta questi hook con questo metodo:

  • default
  • setter
  • validatori
  • middleware

Ho appena bruciato una solida 3 ore cercando di risolvere lo stesso problema. Nello specifico, volevo “sostituire” l’intero documento, se esistente, o inserirlo altrimenti. Ecco la soluzione:

 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{...}); 

Ho creato un problema nella pagina del progetto Mongoose richiedendo che le informazioni su questo vengano aggiunte ai documenti.

Eri vicino

 Contact.update({phone:request.phone}, contact, {upsert: true}, function(err){...}) 

ma il tuo secondo parametro dovrebbe essere un object con un operatore di modifica, ad esempio

 Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...}) 

Bene, ho aspettato abbastanza e nessuna risposta. Alla fine ha abbandonato l’intero approccio di aggiornamento / upsert e ha seguito:

 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); } }); } }); 

Funziona? Sì. Sono contento di questo? Probabilmente no. 2 chiamate DB invece di una.
Speriamo che una futura implementazione di Mongoose Model.upsert una funzione Model.upsert .

Soluzione molto elegante che puoi ottenere usando la catena di 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); }); }); 

Ho creato un account StackOverflow SOLO per rispondere a questa domanda. Dopo aver inutilmente cercato l’interwebs, ho appena scritto qualcosa io stesso. Questo è il modo in cui l’ho fatto in modo che possa essere applicato a qualsiasi modello di mangusta. Importa questa funzione o aggiungila direttamente nel tuo codice dove stai facendo l’aggiornamento.

 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; } 

Quindi, per alzare un documento di mangusta, procedi come segue,

 YourModel.upsert = function (id, newData, callBack) { this.findById(id, function (err, oldData) { if(err) { callBack(err); } else { upsertObject(newData, oldData).save(callBack); } }); }; 

Questa soluzione potrebbe richiedere 2 chiamate DB, tuttavia, si ottiene il beneficio di,

  • Convalida dello schema contro il tuo modello perché stai usando .save ()
  • È ansible spostare oggetti profondamente annidati senza enumerazione manuale nella chiamata di aggiornamento, quindi se il modello viene modificato non è necessario preoccuparsi dell’aggiornamento del codice

Ricorda che l’object di destinazione sovrascriverà sempre la fonte anche se la sorgente ha un valore esistente

Inoltre, per gli array, se l’object esistente ha una matrice più lunga di quella che lo sostituisce, i valori alla fine del vecchio array rimarranno. Un modo semplice per spostare l’intero array è quello di impostare il vecchio array come array vuoto prima dell’avvertimento, se questo è ciò che si intende fare.

AGGIORNAMENTO – 16/01/2016 Ho aggiunto una condizione in più per se c’è una matrice di valori primitivi, Mongoose non si rende conto che l’array viene aggiornato senza utilizzare la funzione “set”.

Avevo bisogno di aggiornare / inserire un documento in una raccolta, quello che ho fatto è stato creare un nuovo object letterale come questo:

 notificationObject = { user_id: user.user_id, feed: { feed_id: feed.feed_id, channel_id: feed.channel_id, feed_title: '' } }; 

composto da dati che ottengo da qualche altra parte nel mio database e poi richiamo l’aggiornamento sul modello

 Notification.update(notificationObject, notificationObject, {upsert: true}, function(err, num, n){ if(err){ throw err; } console.log(num, n); }); 

questa è l’uscita che ottengo dopo aver eseguito lo script per la prima volta:

 1 { updatedExisting: false, upserted: 5289267a861b659b6a00c638, n: 1, connectionId: 11, err: null, ok: 1 } 

E questo è l’output quando eseguo lo script per la seconda volta:

 1 { updatedExisting: true, n: 1, connectionId: 18, err: null, ok: 1 } 

Sto usando la versione di mangusta 3.6.16

 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!' }); }); }); }); 

Ecco un approccio migliore per risolvere il metodo di aggiornamento in mangusta, è ansible controllare Scotch.io per maggiori dettagli. Questo ha sicuramente funzionato per me !!!

C’è un bug introdotto in 2.6 e riguarda anche 2.7

L’upsert utilizzato per funzionare correttamente su 2.4

https://groups.google.com/forum/#!topic/mongodb-user/UcKvx4p4hnY https://jira.mongodb.org/browse/SERVER-13843

Dai un’occhiata, contiene alcune informazioni importanti

AGGIORNAMENTO:

Non significa che l’upsert non funziona. Ecco un bell’esempio di come usarlo:

 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); }); 

questo ha funzionato per 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"}); }); }); 
 ContactSchema.connection.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); } }); } });
ContactSchema.connection.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); } }); } }); 

Per chi arriva qui ancora alla ricerca di una soluzione per “upserting” con supporto per ganci, questo è quello che ho testato e funzionante. Richiede ancora 2 chiamate DB ma è molto più stabile di qualsiasi altra cosa ho provato in una singola chiamata.

 // 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); }); }); } 
 //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); }); }); -- 

Se i generatori sono disponibili diventa ancora più facile:

 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(); 

Puoi semplicemente ascoltare il record con questo e ottenere i dati aggiornati in risposta

 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" }) } }); }); 
 User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => { if(err) return res.json(err); res.json({ success: true }); }); 

Nessuna altra soluzione ha funzionato per me. Sto utilizzando una richiesta di post e l’aggiornamento dei dati se trovato altrove inserisco, anche _id viene inviato con il corpo della richiesta che deve essere rimosso.

 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'); }); 

Sono appena tornato su questo problema dopo un po ‘e ho deciso di pubblicare un plug-in basato sulla risposta di Aaron Mast.

https://www.npmjs.com/package/mongoose-recursive-upsert

Usalo come plugin di mangusta. Imposta un metodo statico che unirà in modo ricorsivo l’object passato.

 Model.upsert({unique: 'value'}, updateObject}); 

Ecco il modo più semplice per creare / aggiornare mentre si chiama anche il middleware e i validatori.

 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); }); }) 

Questo coffeescript funziona per me con Node – il trucco è che _id get viene spogliato del suo wrapper ObjectID quando viene inviato e restituito dal client e quindi questo deve essere sostituito per gli aggiornamenti (quando non viene fornito _id, salva verrà ripristinato per inserire e aggiungere uno).

 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 

per build su ciò che Martin Kuzdowicz ha pubblicato sopra. Io uso il seguente per fare un aggiornamento utilizzando mangusta e una fusione profonda di oggetti JSON. Insieme con la funzione model.save () in mangusta questo permette a mangusta di fare una validazione completa anche di uno che si basa su altri valori nel json. richiede il pacchetto deepmerge https://www.npmjs.com/package/deepmerge . Ma questo è un pacchetto molto leggero.

 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); }); }); 

Seguendo la risposta di Traveling Tech Guy , che è già fantastica, possiamo creare un plugin e collegarlo a mangusta una volta inizializzato in modo che .upsert() sia disponibile su tutti i modelli.

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 

Quindi puoi fare qualcosa come User.upsert({ _id: 1 }, { foo: 'bar' }) o YouModel.upsert({ bar: 'foo' }, { value: 1 }) ogni volta che vuoi.

Dopo aver letto i post sopra, ho deciso di usare questo codice:

  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); } }); 

se r è nullo, creiamo un nuovo elemento. Altrimenti, usa upsert in aggiornamento perché l’aggiornamento non crea nuovo elemento.