¿Cómo actualizo / upsert un documento en Mangosta?


Tal vez es el momento, tal vez soy yo ahogándome en la escasa documentación y no siendo capaz de envolver mi cabeza en torno al concepto de actualización en Mangosta:)

Este es el trato:

Tengo un esquema de contacto y un modelo (propiedades acortadas):

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

Recibo una solicitud del cliente, que contiene los campos que necesito y uso mi modelo de esta manera:

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

Y ahora llegamos al problema:

  1. Si llamo contact.save(function(err){...}) recibiré un error si el contacto con el mismo número de teléfono ya existe (como se esperaba - único)
  2. No puedo llamar a update() en contacto, ya que ese método no existe en un documento
  3. Si llamo a update en el modelo:
    Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
    Me meto en un bucle infinito de algún tipo, ya que la implementación de la actualización de Mangosta claramente no quiere un objeto como segundo parámetro.
  4. Si hago lo mismo, pero en el segundo parámetro paso una matriz asociativa de las propiedades de la solicitud {status: request.status, phone: request.phone ...} funciona-pero entonces no tengo referencia al contacto específico y no puede encontrar sus propiedades createdAt y updatedAt.

Así que la línea de fondo, después de todo lo que intenté: dado un documento contact, ¿cómo lo actualizo si existe, o lo agrego si no lo hace?

Gracias por su tiempo.

Author: antzshrek, 2011-09-01

23 answers

Mongoose ahora soporta esto de forma nativa con findOneAndUpdate (llama a MongoDB findAndModify).

La opción upsert = true crea el objeto si no existe. el valor predeterminado es 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");
});

Editar: Mangosta no soporta estos hooks con este método:

  • valores predeterminados
  • setters
  • validadores
  • middleware
 318
Author: Pascalius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-14 09:48:09

Acabo de quemar un sólido 3 horas tratando de resolver el mismo problema. Específicamente, quería "reemplazar" todo el documento si existe, o insertarlo de otra manera. Aquí está la solución:

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

He creado un problema en la página del proyecto Mangosta solicitando que se agregue información sobre esto a los documentos.

 171
Author: Clint Harris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-21 21:07:02

Estabas cerca de

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

Pero su segundo parámetro debe ser un objeto con un operador de modificación por ejemplo

Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})
 85
Author: chrixian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-05 11:47:10

Bueno, esperé lo suficiente y no contesté. Finalmente renunció a todo el enfoque update / upsert y se fue con:

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

Funciona? Sip. ¿Estoy contento con esto? Probablemente no. 2 Llamadas DB en lugar de una.
Esperemos que una futura implementación de Mangosta llegue con una función Model.upsert.

 63
Author: Traveling Tech Guy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-05 03:09:10

Solución muy elegante que se puede lograr mediante el uso de la cadena de Promesas:

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);
    });
});
 20
Author: Martin Kuzdowicz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-31 11:35:02

He creado una cuenta de StackOverflow SOLO para responder a esta pregunta. Después de buscar infructuosamente las interwebs acabo de escribir algo yo mismo. Así es como lo hice para que se pueda aplicar a cualquier modelo de mangosta. Importa esta función o agrégala directamente a tu código donde estás haciendo la actualización.

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

Luego para publicar un documento de mangosta haga lo siguiente,

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

Esta solución puede requerir llamadas de 2 DB, sin embargo, obtiene el beneficio de,

  • Esquema validación contra su modelo porque está utilizando .guardar()
  • Puede insertar objetos profundamente anidados sin enumeración manual en su llamada de actualización, por lo que si su modelo cambia, no tiene que preocuparse por actualizar su código

Solo recuerde que el objeto de destino siempre sobrescribirá la fuente incluso si la fuente tiene un valor existente

Además, para matrices, si el objeto existente tiene una matriz más larga que la que lo reemplaza, entonces los valores en el final de la vieja matriz permanecerá. Una manera fácil de upsert toda la matriz es establecer la antigua matriz sea una matriz vacía antes de la upsert si eso es lo que estás intentando hacer.

ACTUALIZACIÓN-16/01/2016 He añadido una condición extra para que si hay una matriz de valores primitivos, Mongoose no se da cuenta de que la matriz se actualiza sin usar la función "set".

 14
Author: Aaron Mast,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-16 15:18:42

Necesitaba actualizar/upsert un documento en una colección, lo que hice fue crear un nuevo objeto literal como este:

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

Compuesto de datos que obtengo de otro lugar en mi base de datos y luego llamo a update en el Modelo

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

Esta es la salida que obtengo después de ejecutar el script por primera vez:

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

Y esta es la salida cuando corro el script por segunda vez:

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

Estoy usando la versión de mangosta 3.6.16

 12
Author: andres_gcarmona,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-27 16:36:01
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!' });
            });

        });
    });

Aquí hay un mejor enfoque para resolver el método de actualización en mangosta, puede verificar Scotch.io para más detalles. Esto definitivamente funcionó para mí!!!

 9
Author: Eyo Okon Eyo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-22 02:46:19

Hay un error introducido en 2.6, y afecta a 2.7 también

El upsert solía funcionar correctamente en 2.4

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

Echa un vistazo, contiene información importante

ACTUALIZADO:

No significa que upsert no funcione. Aquí hay un buen ejemplo de cómo 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);
    });
 8
Author: helpse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-16 01:38:36

Esto funcionó para mí.

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

});
 3
Author: Emmanuel Ndukwe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-06 09:14:27
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
Author: Lun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-13 16:45:46

Para cualquiera que llegue aquí todavía buscando una buena solución para "upserting" con soporte de ganchos, esto es lo que he probado y trabajando. Todavía requiere llamadas de 2 DB, pero es mucho más estable que cualquier cosa que he probado en una sola llamada.

// 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);
    });
  });
}
 2
Author: Terry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-27 18:22:14
//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
Author: Ron Belson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-07 08:05:05

Si hay generadores disponibles, se vuelve aún más fácil:

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
Author: Blacksonic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-10 15:01:20

Simplemente puede udpate el registro con esto y obtener los datos actualizados en respuesta

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"
               })
       }
     }); 
   }); 
 2
Author: Muhammad Awais,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-07 14:00:10
User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => {
    if(err) return res.json(err);

    res.json({ success: true });
});
 1
Author: Zeeshan Ahmad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-31 07:42:19

Ninguna otra solución funcionó para mí. Estoy usando una solicitud post y actualizando los datos si se encuentra insértelo, también se envía _id con el cuerpo de la solicitud que debe eliminarse.

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
Author: Priyanshu Chauhan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-21 12:19:00

Acabo de volver a este tema después de un tiempo, y decidió publicar un plugin basado en la respuesta de Aaron Mast.

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

Úsalo como un plugin de mangosta. Establece un método estático que fusionará recursivamente el objeto pasado.

Model.upsert({unique: 'value'}, updateObject});
 1
Author: Richard G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-09 03:59:38

Esta es la forma más sencilla de crear/actualizar mientras se llama al middleware y a los validadores.

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);
    });
})
 1
Author: Wei-Ming,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-28 01:33:30

Este coffeescript funciona para mí con Node - el truco es que el _id get es despojado de su contenedor ObjectId cuando se envía y devuelve desde el cliente y por lo que esto necesita ser reemplazado para las actualizaciones (cuando no se proporciona _id, save volverá a insertar y agregar 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
 0
Author: Simon H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-15 18:56:22

Para construir sobre lo que Martin Kuzdowicz publicó anteriormente. Utilizo lo siguiente para hacer una actualización usando mangosta y una fusión profunda de objetos json. Junto con el modelo.función save () en mongoose esto permite a mongoose hacer una validación completa incluso una que dependa de otros valores en el json. requiere el paquete deepmerge https://www.npmjs.com/package/deepmerge. Pero ese es un paquete muy ligero.

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
Author: Chris Deleo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-17 19:10:17

Siguiendo la respuesta de Traveling Tech Guy, que ya es impresionante, podemos crear un plugin y adjuntarlo a mangosta una vez que lo inicialicemos para que .upsert() esté disponible en todos los modelos.

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

Entonces puedes hacer algo como User.upsert({ _id: 1 }, { foo: 'bar' }) o YouModel.upsert({ bar: 'foo' }, { value: 1 }) cuando quieras.

 0
Author: spondbob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-07 06:58:18

Después de leer los posts anteriores, decidí usar este código:

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

Si r es null, creamos un nuevo elemento. De lo contrario, use upsert en update porque update no crea un nuevo elemento.

 -3
Author: Grant Li,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-11 19:23:35