Возможно, это время, возможно, это я утонул в разреженной документации и не смог окунуться в концепцию обновления в 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
});
И теперь мы достигаем проблемы:
contact.save(function(err){...})
, я получаю сообщение об ошибке, если контакт с тем же номером телефона уже существует (как и ожидалось - уникальным)update()
при контакте, так как этот метод не существует в документеContact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
{status: request.status, phone: request.phone ...}
, он работает, но тогда я не имею ссылки на конкретный контакт и не могу узнать его createdAt
и updatedAt
свойства.Итак, нижняя строка, после всего, что я пробовал: дал документ contact
, как мне его обновить, если он существует, или добавить его, если это не так?
Спасибо за ваше время.
Хорошо, я ждал достаточно долго и не отвечал. Наконец, отказался от всего подхода 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
.
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 часа, пытаясь решить ту же проблему. В частности, я хотел "заменить" весь документ, если он существует, или вставить его иначе. Здесь решение:
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, указав, что информация об этом будет добавлена в документы.
MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);
и MyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, fn);
Вы были рядом с
Contact.update({phone:request.phone}, contact, {upsert: true}, function(err){...})
но ваш второй параметр должен быть объектом с оператором изменения, например
Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})
{$set: ... }
здесь, поскольку она автоматически формирует мое чтение
Очень элегантное решение, которое вы можете достичь, используя цепочку 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);
});
});
Я создал учетную запись 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);
}
});
};
Для этого решения могут потребоваться два вызова БД, но вы получаете преимущество,
Просто помните, что целевой объект всегда будет переопределять исходный код, даже если источник имеет существующее значение
Кроме того, для массивов, если существующий объект имеет более длинный массив, чем тот, который его заменяет, тогда значения в конце старого массива останутся. Простой способ взломать весь массив состоит в том, чтобы установить старый массив как пустой массив перед обновлением, если это то, что вы намереваетесь делать.
ОБНОВЛЕНИЕ - 01/16/2016 Я добавил дополнительное условие, если имеется массив примитивных значений, Mongoose не понимает, что массив обновляется без использования функции "set".
Мне нужно было обновить/загрузить документ в одну коллекцию, я сделал это, чтобы создать новый литерал объекта следующим образом:
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
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 для получения более подробной информации. Это определенно сработало для меня!
Существует ошибка, введенная в 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);
});
Вы можете просто обновить запись с этим и получить обновленные данные в ответ
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"
})
}
});
});
это сработало для меня.
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"});
});
});
Если генераторы доступны, становится еще проще:
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();
//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);
});
});
--
Для тех, кто прибывает сюда, ища хорошее решение для "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);
});
});
}
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);
}
});
}
});
Никакое другое решение не работало для меня. Я использую почтовый запрос и обновляю данные, если найденный 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');
});
User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => {
if(err) return res.json(err);
res.json({ success: true });
});
Здесь самый простой способ создания/обновления, а также вызов промежуточного программного обеспечения и валидаторов.
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);
});
})
Я просто вернулся к этой проблеме через некоторое время и решил опубликовать плагин, основанный на ответе Аарона Маста.
https://www.npmjs.com/package/mongoose-recursive-upsert
Используйте его как плагин мангусты. Он устанавливает статический метод, который будет рекурсивно объединять переданный объект.
Model.upsert({unique: 'value'}, updateObject});
Следуя ответу 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 })
.
чтобы опираться на то, что Мартин Куздович опубликовал выше. Я использую следующее, чтобы сделать обновление, используя 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);
});
});
req.body
как есть, перед тестированием на внедрение NoSQL (см. Owasp.org/index.php/Testing_for_NoSQL_injection ).
Этот файл 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
Прочитав сообщения выше, я решил использовать этот код:
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 в обновлении, потому что обновление не создает новый элемент.
pre
дляsave
?