web-dev-qa-db-fra.com

Comment mettre à jour/transférer un document dans Mongoose?

Peut-être que le moment est venu, peut-être que c'est moi qui me noie dans la documentation clairsemée et que je ne parviens pas à comprendre le concept de la mise à jour en mangouste :)

Voici le deal:

J'ai un schéma de contact et un modèle (propriétés raccourcies):

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

Je reçois une demande du client contenant les champs dont j'ai besoin et utilise mon modèle de la manière suivante:

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

Et maintenant nous arrivons au problème:

  1. Si j'appelle contact.save(function(err){...}), je reçois une erreur si le contact avec le même numéro de téléphone existe déjà (comme prévu - unique)
  2. Je ne peux pas appeler update() sur le contact, car cette méthode n'existe pas sur un document.
  3. Si j'appelle update sur le modèle:
    Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
    J'entre dans une boucle infinie, car l'implémentation de la mise à jour Mongoose ne veut clairement pas d'objet en tant que second paramètre.
  4. Si je fais la même chose, mais dans le deuxième paramètre, je passe un tableau associatif des propriétés de la requête {status: request.status, phone: request.phone ...}, cela fonctionne - mais dans ce cas, je n'ai aucune référence au contact spécifique et je ne peux pas trouver ses propriétés createdAt et updatedAt.

Donc, au bout du compte, après tout ce que j'ai essayé: à partir d'un document contact, comment puis-je le mettre à jour s'il existe ou l'ajouter s'il ne l'est pas?

Merci pour votre temps.

307
Traveling Tech Guy

Eh bien, j'ai attendu assez longtemps et pas de réponse. A finalement abandonné toute l'approche mise à jour/upsert et est allé avec:

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

Est-ce que ça marche? Oui. Suis-je heureux avec ça? Probablement pas. 2 appels de base de données au lieu d'un.
J'espère qu'une future implémentation de Mongoose créerait une fonction Model.upsert.

66
Traveling Tech Guy

Mongoose supporte maintenant cela nativement avec findOneAndUpdate (appelle MongoDB findAndModify ). 

L'option upsert = true crée l'objet s'il n'existe pas. par défaut à 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");
});

Dans les anciennes versions, Mongoose ne supporte pas ces hooks avec cette méthode:

  • défauts
  • les setters
  • validateurs
  • middleware
338
Pascalius

Je viens de brûler 3 heures solides pour essayer de résoudre le même problème. Plus précisément, je souhaitais "remplacer" l'intégralité du document s'il existait, ou l'insérer autrement. Voici la 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{...});

J'ai créé un problème sur la page du projet Mongoose demandant que des informations à ce sujet soient ajoutées à la documentation.

175
Clint Harris

Tu étais proche avec

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

mais votre deuxième paramètre devrait être un objet avec un opérateur de modification par exemple

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

Solution très élégante que vous pouvez réaliser en utilisant la chaîne de promesses:

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);
    });
});
22
Martin Kuzdowicz

J'ai créé un compte StackOverflow JUSTE pour répondre à cette question. Après une recherche infructueuse sur les interwebs, je viens d’écrire quelque chose moi-même. Voici comment je l'ai fait pour qu'il puisse être appliqué à n'importe quel modèle de mangouste. Importez cette fonction ou ajoutez-la directement dans votre code où vous effectuez la mise à jour.

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

Ensuite, pour transférer un document sur une mangouste, procédez comme suit:

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

Cette solution peut nécessiter 2 appels de base de données, mais vous en bénéficiez.

  • Validation de schéma par rapport à votre modèle car vous utilisez .save ()
  • Vous pouvez insérer des objets profondément imbriqués sans énumération manuelle dans votre appel de mise à jour. Ainsi, si votre modèle change, vous n'avez pas à vous soucier de la mise à jour de votre code

Rappelez-vous simplement que l'objet de destination remplacera toujours la source même si la source a une valeur existante

De même, pour les tableaux, si l'objet existant a un tableau plus long que celui qui le remplace, les valeurs à la fin de l'ancien tableau resteront. Un moyen facile de convertir le tableau entier en entier consiste à définir l'ancien tableau comme étant un tableau vide avant le serveur de conversion, si c'est ce que vous avez l'intention de faire.

UPDATE - 16/01/2016 J'ai ajouté une condition supplémentaire pour s'il existe un tableau de valeurs primitives, Mongoose ne réalise pas que le tableau est mis à jour sans utiliser la fonction "set".

14
Aaron Mast

J'avais besoin de mettre à jour/insérer un document dans une collection. Ce que j'ai fait était de créer un nouvel objet littéral comme celui-ci:

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

composé à partir de données que je récupère ailleurs dans ma base de données, puis appelle la mise à jour sur le modèle

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

voici le résultat obtenu après l'exécution du script pour la première fois:

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

Et voici le résultat lorsque je lance le script pour la deuxième fois:

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

J'utilise mangouste version 3.6.16

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

        });
    });

Voici une meilleure approche pour résoudre la méthode de mise à jour dans Mangouste, vous pouvez consulter Scotch.io pour plus de détails Cela a définitivement fonctionné pour moi !!!

9
Eyo Okon Eyo

Il y a un bug introduit dans la version 2.6, et affecte également la version 2.7

L’upert fonctionnait correctement sur la version 2.4

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

Jetez un oeil, il contient des informations importantes

MIS À JOUR:

Cela ne signifie pas que upsert ne fonctionne pas. Voici un bel exemple d'utilisation:

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

Vous pouvez simplement mettre à jour l'enregistrement avec ceci et obtenir les données mises à jour en réponse

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

cela a fonctionné pour moi. 

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

});

4
Emmanuel Ndukwe

Voici le moyen le plus simple de créer/mettre à jour tout en appelant le middleware et les validateurs.

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

C’est ce que j’ai testé et mis au point pour tous ceux qui arrivent ici et qui recherchent toujours une solution efficace pour «passer» avec le support de crochets. Il nécessite toujours 2 appels de base de données, mais est beaucoup plus stable que tout ce que j'ai essayé en un seul appel.

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

Je suis revenu à cette question après un certain temps et j'ai décidé de publier un plugin basé sur la réponse d'Aaron Mast. 

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

Utilisez-le comme un plugin mangouste. Il configure une méthode statique qui fusionnera de manière récursive l'objet transmis. 

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

Suite à la réponse de Travelling Tech Guy , qui est déjà impressionnante, nous pouvons créer un plugin et l’attacher à mangouste une fois que nous l’avons initialisé afin que .upsert() soit disponible sur tous les modèles.

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

Ensuite, vous pouvez faire quelque chose comme User.upsert({ _id: 1 }, { foo: 'bar' }) ou YouModel.upsert({ bar: 'foo' }, { value: 1 }) quand vous le souhaitez.

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


--
2
Ron Belson
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);
            }
        });
    }
});
2
Lun

Si des générateurs sont disponibles, cela devient encore plus 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();
2
Blacksonic

Aucune autre solution n'a fonctionné pour moi. J'utilise une demande de publication et mets à jour les données si elles sont trouvées, puis _id est envoyé avec le corps de la demande à supprimer.

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

});
1
Priyanshu Chauhan
User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => {
    if(err) return res.json(err);

    res.json({ success: true });
});
1
Zeeshan Ahmad

Ce coffeescript fonctionne pour moi avec Node - le truc, c’est que le _id obtient dépouillé de son encapsuleur ObjectID lorsqu’il est envoyé et renvoyé par le client; il doit donc être remplacé pour les mises à jour un).

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
0
Simon H

s’appuyer sur ce que Martin Kuzdowicz a publié ci-dessus. J'utilise ce qui suit pour faire une mise à jour en utilisant mangouste et une fusion profonde des objets JSON. En plus de la fonction model.save () dans Mangouste, cela permet à Mangouste d'effectuer une validation complète, même si elle s'appuie sur d'autres valeurs du json. il nécessite le paquet deepmerge https://www.npmjs.com/package/deepmerge . Mais c'est un paquet très léger. 

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);
    });
});
0
Chris Deleo