Частичное обновление поддокумента с 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);
});