Частичное обновление поддокумента с nodejs/mongoose
Можно ли установить несколько свойств в (под) документе за один раз с помощью Mongoose? Пример того, что я пытаюсь сделать:
Скажем, у меня есть эта схема:
var subSchema = new Schema({
someField: String,
someOtherField: String
});
var parentSchema = new Schema({
fieldOne: String,
subDocs: [subSchema]
})
Затем я хотел бы сделать:
exports.updateMyDocument = function(req, res) {
var parentDoc = req.parentDoc; // The parent document. Set by parameter resolver.
var document = req.myDoc; // Sub document of parent. Set by parameter resolver.
var partialUpdate = req.body; // updated fields sent as json and parsed by body parser
// I know that the statement below doesn't work, it just an example of what I would like to do.
// Updating only the fields supplied in "partialUpdate" on the document
document.update(partialUpdate);
parentDoc.save(function(err) {
if(err) {
res.send(500);
return;
}
res.send(204);
});
};
Обычно я мог бы добиться этого с помощью оператора $set
, но моя проблема заключается в том, что document
в этом примере является поддокументом (встроенной схемой) parentDoc
. Поэтому, когда я пытался сделать
Parent.update({_id: parentDoc._id, "subDocs._id": document._id},
{$set: {"subDocs.$" : partialUpdate}},
function(err, numAffected) {});
он заменил экземпляр субдокумента, идентифицированный subDocs._id
. В настоящее время я "решил" его, установив только поля вручную, но я надеялся на лучший способ сделать это.
Ответы
Ответ 1
Создайте объект $set
программно на основе полей partialUpdate
для обновления только этих полей с использованием точечной нотации:
var set = {};
for (var field in partialUpdate) {
set['subDocs.$.' + field] = partialUpdate[field];
}
Parent.update({_id: parentDoc._id, "subDocs._id": document._id},
{$set: set},
function(err, numAffected) {});
Ответ 2
Я делал разные, в приложении REST.
Во-первых, у меня есть этот маршрут:
router.put('/:id/:resource/:resourceId', function(req, res, next) {
// this method is only for Array of resources.
updateSet(req.params.id, req.params.resource, req, res, next);
});
и updateSet()
метод
function updateSet(id, resource, req, res, next) {
var data = req.body;
var resourceId = req.params.resourceId;
Collection.findById(id, function(err, collection) {
if (err) {
rest.response(req, res, err);
} else {
var subdoc = collection[resource].id(resourceId);
// set the data for each key
_.each(data, function(d, k) {
subdoc[k] = d;
});
collection.save(function (err, docs) {
rest.response(req, res, err, docs);
});
}
});
}
Блестящая часть mongoose проверит data
, если вы определяете Schema
для этого вложенного документа. Этот код будет действителен для любого ресурса документа, который является массивом. Я не показываю все свои данные для простоты, но это хорошая практика для проверки этих ситуаций и правильной обработки реакции ответа.
Ответ 3
Вы можете назначить или расширить встроенный документ.
Doc.findOne({ _id: docId })
.then(function (doc) {
if (null === doc) {
throw new Error('Document not found');
}
return doc.embeded.id(ObjectId(embeddedId));
})
.then(function(embeddedDoc) {
if (null === embeddedDoc) {
throw new Error('Embedded document not found');
}
Object.assign(embeddedDoc, updateData));
return embeddedDoc.parent().save();
})
.catch(function (err) {
//Do something
});
И в этом случае вы должны быть уверены, что _id не назначает.
Ответ 4
Я обработал это несколько иначе, не используя $set object. Мой подход похож на Guilherme, но одно отличие заключается в том, что я завернул свой метод в функции статики, чтобы его легче было использовать во время моего приложения. Пример ниже.
В модели сервера CollectionSchema.js.
collectionSchema.statics.decrementsubdocScoreById = function decreasesubdoc (collectionId, subdocId, callback) {
this.findById(collectionId, function(err, collection) {
if (err) console.log("error finding collection");
else {
var subdoc = collection.subdocs.filter(function (subdoc) {
return subdoc._id.equals(subdocId);
})[0];
subdoc.score -= 1;
collection.save(callback);
}
});
};
В контроллере сервера
Collection.decrementsubdocScoreById(collectionId, subdocId, function (err, data) {
handleError(err);
doStuffWith(data);
});