Как обновить/обновить документ в Mongoose?

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

Здесь сделка:

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

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

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

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{...})
    Я попадаю в бесконечный цикл некоторых видов, поскольку реализация обновления Mongoose явно не хочет, чтобы объект был вторым параметром.
  4. Если я делаю то же самое, но во втором параметре я передаю ассоциативный массив свойств запроса {status: request.status, phone: request.phone...} это работает - но тогда у меня нет ссылки на конкретный контакт и не может узнать его createdAt и updatedAt свойства.

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

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

Ответ 1

Хорошо, я ждал достаточно долго и не отвечал. Наконец, отказался от всего подхода update/upsert и пошел с:

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 вызова БД вместо одного.
Надеемся, что в будущей реализации Mongoose появится функция Model.upsert.

Ответ 2

Mongoose теперь поддерживает это изначально с помощью findOneAndUpdate (вызывает MongoDB findAndModify).

Опция upsert = true создает объект, если он не существует. по умолчанию ложно.

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

В старых версиях Mongoose не поддерживает эти ловушки с помощью этого метода:

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

Ответ 3

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

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

// Convert the Model instance to a simple object using Model '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{...});

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

Ответ 4

Вы были рядом с

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

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

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

Ответ 5

Очень элегантное решение, которое вы можете достичь, используя цепочку 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);
    });
});

Ответ 6

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

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

Для этого решения могут потребоваться два вызова БД, но вы получаете преимущество,

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

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

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

ОБНОВЛЕНИЕ - 01/16/2016 Я добавил дополнительное условие, если имеется массив примитивных значений, Mongoose не понимает, что массив обновляется без использования функции "set".

Ответ 7

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

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

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

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

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

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

И это результат, когда я запускаю script во второй раз:

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

Я использую версию мангуста 3.6.16

Ответ 8

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

        });
    });

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

Ответ 9

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

Upsert используется для корректной работы 2.4.

https://groups.google.com/forum/#!topic/mongodb-user/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);
    });

Ответ 10

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

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

Ответ 11

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

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

});

Ответ 12

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

Ответ 13

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

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

Ответ 14

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

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

Ответ 15

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


--

Ответ 16

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

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

Ответ 17

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

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

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

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

Ответ 18

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

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

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

Ответ 19

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

    res.json({ success: true });
});

Ответ 20

Никакое другое решение не работало для меня. Я использую почтовый запрос и обновляю данные, если найденный else вставляет его, также _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');

});

Ответ 21

Этот файл coffeescript работает для меня с Node - трюк заключается в том, что _id лишился своей оболочки ObjectID при отправке и возврате от клиента, и поэтому это нужно заменить для обновлений (когда no_id не предоставляется, сохранение будет верните, чтобы вставить и добавить один).

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

Ответ 22

чтобы опираться на то, что Мартин Куздович опубликовал выше. Я использую следующее, чтобы сделать обновление, используя mongoose и глубокое слияние json-объектов. Наряду с функцией model.save() в мангусте это позволяет мангусте выполнять полную проверку даже на той, которая полагается на другие значения в json. для этого требуется пакет глубины 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);
    });
});

Ответ 23

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

    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 в обновлении, потому что обновление не создает новый элемент.