Как обновить / upsert документ в Мангусте?

возможно, это время, возможно, это я тону в разреженной документации и не в состоянии обернуть голову вокруг концепции обновления в Мангусте:)

вот в чем дело:

у меня есть схема контакта и модель (сокращенные свойства):

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

Я получаю запрос от клиента, содержащий поля, которые мне нужны, и использую свою модель таким образом:

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

и теперь мы подходим к проблеме:

  1. если я называю contact.save(function(err){...}) Я получаю сообщение об ошибке, если контакт с таким же номером телефона уже существует (как и ожидалось - уникальный)
  2. я не могу назвать update() при контакте, так как этот метод не существует в документе
  3. если я вызываю обновление модели:
    Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
    Я попадаю в бесконечный цикл некоторых видов, так как реализация обновления Мангуста явно не хочет объект в качестве второго параметра.
  4. если я делаю то же самое, но во втором параметре я пройти ассоциативный массив свойств запроса {status: request.status, phone: request.phone ...} это работает-но тогда у меня нет ссылки на конкретный контакт и я не могу узнать его createdAt и updatedAt свойства.

Итак, итог, ведь я пробовал: дали документ contact, как его обновить, если он существует, или добавить, если нет?

Спасибо за ваше время.

23 ответов


Мангуст теперь поддерживает Это изначально с findOneAndUpdate (вызывает MongoDB findAndModify).

параметр upsert = true создает объект, если он не существует. по умолчанию 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");
});

Edit: Мангуст не поддерживает эти крючки с помощью этого метода:

  • по умолчанию
  • сеттеры
  • валидаторы
  • промежуточное

Я просто сжег твердые 3 часа, пытаясь решить ту же проблему. В частности, я хотел "заменить" весь документ, если он существует, или вставить его иначе. Вот решение:

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

Я создал проблема на странице проекта Мангуст запрос, чтобы информация об этом была добавлена в документы.


Вы были близки с

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

но ваш второй параметр должен быть объектом с оператором модификации, например

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

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

это работает? Да. Доволен ли я этим? Скорее всего, нет. 2 вызова DB вместо одного.
Надеюсь, будущая реализация Мангуста придумает


очень элегантное решение вы можете достигнуть путем использование цепи обещаний:

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

Я создал учетную запись StackOverflow, чтобы ответить на этот вопрос. После бесплодных поисков в паутине я просто написал что-то сам. Вот как я это сделал, чтобы его можно было применить к любой модели мангуста. Импортируйте эту функцию или добавьте ее непосредственно в код, в котором выполняется обновление.

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

затем, чтобы вставить документ мангуста, сделайте следующее:

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

это решение может потребовать вызовов 2 DB, однако вы получаете преимущество of,

  • проверка схемы против вашей модели, потому что вы используете .save ()
  • вы можете upsert глубоко вложенные объекты без ручного перечисления в вызове обновления, так что если ваша модель изменяется, вам не придется беспокоиться об обновлении кода

просто помните, что объект назначения всегда будет переопределять Источник, даже если источник имеет существующее значение

кроме того, для массивов, если существующий объект имеет более длинный массив, чем заменяющий его, тогда значения в конце старого массива останутся. Простой способ вставить весь массив-установить старый массив пустым массивом перед upsert, если это то, что вы собираетесь делать.

обновление-16.01.2016 Я добавил дополнительное условие, если есть массив примитивных значений, Мангуст не понимает, что массив обновляется без использования функции "set".


мне нужно было обновить / upsert документ в одну коллекцию, что я сделал, чтобы создать новый литерал объекта, как это:

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

состоит из данных, которые я получаю откуда-то еще в моей базе данных, а затем вызываю обновление модели

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

это выход, который я получаю после запуска сценария в первый раз:

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

и это вывод, когда я запускаю скрипт во второй раз:

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

Я использую версию мангуста 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!' });
            });

        });
    });

вот лучший подход к решению метода обновления в мангусте, вы можете проверитьскотч.Ио для получения более подробной информации. Это определенно сработало для меня!!!


существует ошибка, введенная в 2.6, и влияет на 2.7, а также

upsert используется для правильной работы на 2.4

https://groups.google.com/forum#!тема / mongodb-пользователь / UcKvx4p4hnY https://jira.mongodb.org/browse/SERVER-13843

взгляните, он содержит некоторую важную информацию

обновление:

это не означает, что upsert не работает. Вот хороший пример того, как его использовать:

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

это сработало для меня.

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


для тех, кто прибывает сюда, все еще ищет хорошее решение для" upserting " с поддержкой крючков, это то, что я тестировал и работал. Он по-прежнему требует вызовов 2 DB, но гораздо более стабилен, чем все, что я пробовал в одном вызове.

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


--

если генераторы доступны, становится еще проще:

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

вы можете просто обновлять записи с этим и получить обновленные данные в ответ

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

никакое другое решение не работает для меня. Я использую запрос post и обновляю данные, если найдено еще вставить его, также _id отправляется с телом запроса, которое необходимо удалить.

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

});

Я просто вернулся к этой проблеме через некоторое время и решил опубликовать плагин на основе ответа Аарона Маста.

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

используйте его как плагин мангуста. Он устанавливает статический метод, который рекурсивно объединит переданный объект.

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

вот самый простой способ создать / обновить, а также вызвать промежуточное ПО и валидаторы.

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

этот coffeescript работает для меня с Node-трюк заключается в том, что _id get лишен своей обертки ObjectID при отправке и возврате от клиента, и поэтому это необходимо заменить для обновлений (когда нет _id, save вернется к insert и 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

чтобы построить на том, что Мартин Куздович опубликовал выше. Я использую следующее, чтобы сделать обновление с помощью мангуста и глубокого слияния объектов json. Вместе с моделью.функция save () в мангусте это позволяет Мангусту выполнять полную проверку даже той, которая полагается на другие значения в json. для этого требуется пакет deepmerge https://www.npmjs.com/package/deepmerge. Но это очень легкий пакет.

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

после Путешествия Tech Пареньответ, который уже удивительный, мы можем создать плагин и прикрепить его к Мангусту, как только мы инициализируем его так, что .upsert() будет доступно на всех моделях.

Плагины.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

затем вы можете сделать что-то вроде User.upsert({ _id: 1 }, { foo: 'bar' }) или YouModel.upsert({ bar: 'foo' }, { value: 1 }) когда вы хотите.


прочитав сообщения выше, я решил использовать этот код:

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

если r равно null, мы создаем новый элемент. В противном случае используйте upsert в обновлении, поскольку обновление не создает новый элемент.