Como atualizo/upsert um documento em Mangusto?

Talvez seja a altura, talvez seja eu a afogar-me em documentação escassa e não ser capaz de envolver a minha cabeça no conceito de actualização em Mangusto. É o seguinte:

tenho um esquema e modelo de contacto (propriedades encurtadas):

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

recebo um pedido do cliente, contendo os campos de que preciso e usando assim o meu modelo:

mongoose.connect(connectionString);
var contact = new Contact({
    phone: request.phone,
    status: request.status
});
E agora chegamos ao problema.
  1. se eu ligar contact.save(function(err){...}) vou receber um erro se o o contacto com o mesmo número de telefone já existe (como esperado - único)
  2. não posso ligar update() em contacto, uma vez que esse método não existe num documento
  3. Se eu ligar para o update sobre o modelo:
    Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
    Eu entro em um loop infinito de alguns tipos, uma vez que a implementação Mongoose update claramente não quer um objeto como o segundo parâmetro.
  4. Se eu fizer o mesmo, mas no segundo parâmetro eu passo uma matriz associativa das propriedades do pedido {[[5]} ele funciona-mas então não tenho nenhuma referência ao contato específico e não consigo descobrir suas propriedades createdAt e updatedAt.

então a conclusão, depois de tudo o que eu tentei: dado um documento contact, Como eu atualizá-lo se ele existe, ou adicioná-lo se ele não existe?

Obrigado pelo seu tempo.

Author: antzshrek, 2011-09-01

23 answers

A Mangusto agora suporta isto nativamente com findOneAndUpdate (chamadas MongoDB findAndModify ).

A opção upsert = true cria o objecto se este não existir. o valor por omissão é 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: a Mangusto não suporta estes ganchos com este método:

  • defaults
  • setters
  • validadores
  • Middleware
 318
Author: Pascalius, 2018-02-14 09:48:09
Queimei 3 horas a tentar resolver o mesmo problema. Especificamente, eu queria "substituir" todo o documento se ele existe, ou inseri-lo de outra forma. Eis a solução:
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{...});

Eu criei um problema na página do projecto Mangusto pedindo que essa informação sobre isto fosse adicionada aos documentos.

 171
Author: Clint Harris, 2011-10-21 21:07:02

Eras próximo de

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

Mas o seu segundo parâmetro deve ser um objecto com um operador de modificação, por exemplo

Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})
 85
Author: chrixian, 2014-08-05 11:47:10
Bem, esperei tempo suficiente e não atendi. Finalmente desistiu de toda a abordagem update/upert e foi com:
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? Sim. Estou feliz com isto? Provavelmente não. 2 DB chama em vez de um.
Esperemos que uma futura implementação do mangusto venha a surgir com uma função Model.upsert.
 63
Author: Traveling Tech Guy, 2011-09-05 03:09:10

Uma solução muito elegante que pode conseguir usando uma cadeia de promessas:

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, 2017-01-31 11:35:02
Criei uma conta StackOverflow só para responder a esta pergunta. Depois de procurar na internet, escrevi uma coisa. Foi assim que o fiz para que pudesse ser aplicado a qualquer modelo de mangusto. Ou importa esta função ou adiciona-a directamente ao seu código, onde está a fazer a actualização.
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;
}

Então para upsert um documento mangusto faça o seguinte,

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

Esta solução pode necessitar de 2 DB chamadas no entanto, você tem o benefício de,

  • esquema validação contra o seu modelo porque você está usando .save ()
  • pode upsert objectos profundamente aninhados sem enumeração manual na sua chamada de actualização, por isso, se o seu modelo mudar, não terá de se preocupar com a actualização do seu código

lembre-se apenas que o objeto de destino irá sempre sobrepor a fonte, mesmo que a fonte tenha um valor existente

Além disso, para arrays, se o objeto existente tem um array mais longo do que o que o substitui, então os valores em o fim da antiga Matriz permanecerá. Uma maneira fácil de uptert o array inteiro é definir o array antigo para ser um array vazio antes do uptert, se é isso que você pretende fazer.

UPDATE-01/16/2016 Eu adicionei uma condição extra para que se houver um array de valores primitivos, Mangusto não percebe que o array se torna atualizado sem usar a função "set".

 14
Author: Aaron Mast, 2017-01-16 15:18:42

Eu precisava atualizar / upsert um documento em uma única coleção, o que eu fiz foi criar um novo objeto literal como este:

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

Este é o uput que eu recebo depois de executar o script pela primeira vez:

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

E esta é a saída quando eu executar o script pela segunda vez:

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

Estou a usar a versão mangusto 3.6.16

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

        });
    });
Aqui está uma melhor abordagem para resolver o método de atualização em mangusto, você pode verificar Scotch.io para mais detalhes. Isto funcionou comigo!!!
 9
Author: Eyo Okon Eyo, 2015-07-22 02:46:19

Há um bug introduzido em 2.6, e afeta a 2.7 também

O upsert funcionava correctamente em 2.4

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

Dê uma olhada, ele contém algumas informações importantes

Actualizado:

Não significa que a upert não funcione. Aqui está um bom exemplo de como usá-lo:
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, 2014-05-16 01:38:36
Isto funcionou comigo.

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, 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, 2011-09-13 16:45:46
Para qualquer pessoa que chegue aqui ainda à procura de uma boa solução para "upserting" com suporte de ganchos, isto é o que eu testei e trabalhei. Ele ainda requer 2 DB chamadas, mas é muito mais estável do que qualquer coisa que eu tentei em uma única chamada.
// 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, 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, 2014-11-07 08:05:05

Se os geradores estiverem disponíveis, torna-se ainda mais 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, 2015-05-10 15:01:20

Pode simplesmente udpate o registo com isto e obter os dados actualizados em resposta

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, 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, 2017-08-31 07:42:19
Nenhuma outra solução funcionou comigo. Eu estou usando um pedido de post e atualização de dados se encontrado mais inseri-lo, também _id é enviado com o corpo do pedido que precisa ser removido.
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, 2017-09-21 12:19:00
[[[2]} acabei de voltar a este problema depois de um tempo, e decidi publicar um plugin baseado na resposta de Aaron Mast.

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

Usa-o como um 'plugin' de mangusto. Ele configura um método estático que irá fundir recursivamente o objeto passado.

Model.upsert({unique: 'value'}, updateObject});
 1
Author: Richard G, 2018-06-09 03:59:38
Aqui está a maneira mais simples de criar/actualizar, ao mesmo tempo que chama o middleware e os 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, 2018-06-28 01:33:30

Este coffeescript funciona para mim com o Node - o truque é que o _id get foi despojado da sua capa ObjectID quando enviado e devolvido do cliente e, por isso, isto precisa ser substituído para actualizações (quando nenhum _id é fornecido, save irá reverter para inserir e adicionar um).

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, 2015-07-15 18:56:22
A partir do que Martin Kuzdowicz postou acima. Eu uso o seguinte para fazer uma atualização usando mangusto e uma fusão profunda de objetos json. Juntamente com o modelo.a função save () em mangusto permite que mangusto faça uma validação completa mesmo que dependa de outros valores no json. ele requer o pacote de de deepmerge https://www.npmjs.com/package/deepmerge mas isso é um pacote muito leve.
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, 2017-04-17 19:10:17

Seguindo a resposta de Traveling Tech Guy , que já é incrível, podemos criar um plugin e anexá-lo ao mangusto uma vez que inicializá-lo para que .upsert() estará disponível em todos os 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
Então podes fazer algo como User.upsert({ _id: 1 }, { foo: 'bar' }) ou YouModel.upsert({ bar: 'foo' }, { value: 1 }) quando quiseres.
 0
Author: spondbob, 2018-05-07 06:58:18
Depois de ler os posts acima, decidi 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);
        }
    });

Se o r é nulo, criamos um novo item. Caso contrário, use o upsert em update porque o update não cria um novo item.

 -3
Author: Grant Li, 2013-12-11 19:23:35