javascript - updatemany - mongoose документация




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

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

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

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


Mongoose теперь поддерживает это с помощью 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");
});

Изменить: Mongoose не поддерживает эти крючки с помощью этого метода:

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

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

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

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

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

Для тех, кто прибыл сюда, ища хорошее решение для «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);
    });
  });
}

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

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


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

});

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

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

После ответа .upsert() 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' }) или YouModel.upsert({ bar: 'foo' }, { value: 1 }) когда захотите.


Существует ошибка, введенная в 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);
    });

Этот coffeescript работает для меня с Node - трюк заключается в том, что _id get лишился своей оболочки 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

Я создал учетную запись 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 ()
  • Вы можете взломать глубоко вложенные объекты без ручного перечисления в своем вызове обновления, поэтому, если ваша модель изменена, вам не нужно беспокоиться об обновлении кода

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

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

UPDATE - 01/16/2016 Я добавил дополнительное условие, если массив массивов примитивных значений, Mongoose не понимает, что массив обновляется без использования функции «set».


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

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

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

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

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

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

});


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


--

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

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

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 для получения более подробной информации. Это определенно сработало для меня !!!





mongoose