Подтвердить что ты не робот

Mongoose найти/обновить поддокумент

У меня есть следующие схемы для документа Папка:

var permissionSchema = new Schema({
    role: { type: String },
    create_folders: { type: Boolean },
    create_contents: { type: Boolean }
});

var folderSchema = new Schema({
    name: { type: string },
    permissions: [ permissionSchema ]
});

Итак, для каждой страницы у меня может быть много разрешений. В моей CMS есть панель, где я перечисляю все папки и их разрешения. Администратор может редактировать одно разрешение и сохранять его.

Я мог бы легко сохранить весь документ Папка с его массивом разрешений, где было изменено только одно разрешение. Но я не хочу сохранять весь документ (у реальной схемы больше полей), поэтому я сделал это:

savePermission: function (folderId, permission, callback) {
    Folder.findOne({ _id: folderId }, function (err, data) {
        var perm = _.findWhere(data.permissions, { _id: permission._id });                

        _.extend(perm, permission);

        data.markModified("permissions");
        data.save(callback);
    });
}

но проблема в том, что perm всегда undefined! Я попытался "статически" получить разрешение таким образом:

var perm = data.permissions[0];

и он отлично работает, поэтому проблема заключается в том, что библиотека Underscore не может запрашивать массив разрешений. Поэтому я предполагаю, что есть лучший (и рабочий) способ получить поддокумент извлеченного документа.

Любая идея?

PS: Я решил проверить каждый элемент в массиве data.permission, используя цикл "for" и проверив data.permissions [i]._ id == permission._id, но я бы хотел более разумное решение, я знаю, что там один

4b9b3361

Ответ 1

Итак, как вы отмечаете, значение по умолчанию в mongoose заключается в том, что когда вы "внедряете" данные в такой массив, вы получаете значение _id для каждой записи массива как часть его собственных свойств субдокумента. Фактически вы можете использовать это значение, чтобы определить индекс элемента, который вы собираетесь обновлять. Способ MongoDB - это операционная переменная positional $, которая содержит "сопоставленную" позицию в массиве:

Folder.findOneAndUpdate(
    { "_id": folderId, "permissions._id": permission._id },
    { 
        "$set": {
            "permissions.$": permission
        }
    },
    function(err,doc) {

    }
);

Этот метод .findOneAndUpdate() возвращает измененный документ, иначе вы можете просто использовать .update() как метод, если вам не нужен документ, который был возвращен. Основные части "сопоставляют" элемент массива с обновлением и "идентификацией", который соответствует positional $, как упоминалось ранее.

Тогда, конечно, вы используете оператор $set, чтобы только указываемые вами элементы фактически отправлено "по проводам" на сервер. Вы можете принять это с помощью "точечной нотации" и просто указать элементы, которые вы действительно хотите обновить. Как в:

Folder.findOneAndUpdate(
    { "_id": folderId, "permissions._id": permission._id },
    { 
        "$set": {
            "permissions.$.role": permission.role
        }
    },
    function(err,doc) {

    }
);

Итак, это гибкость, предоставляемая MongoDB, где вы можете быть очень "нацелены" на то, как вы действительно обновляете документ.

Однако это означает "обход" любой логики, которую вы могли бы встроить в свою схему "mongoose", например, "проверка" или другие "предварительные блокировки". Это потому, что "оптимальным" способом является "особенность" MongoDB и то, как он разработан. Сам Mongoose пытается стать "удобной" оболочкой по этой логике. Но если вы готовы взять себя под контроль самостоятельно, то обновления можно сделать наиболее оптимальным способом.

Поэтому, когда это возможно, держите свои данные "встроенными" и не используйте ссылочные модели. Он позволяет атомарное обновление как "родительских", так и "дочерних" элементов в простых обновлениях, где вам не нужно беспокоиться о concurrency. Вероятно, это одна из причин, по которой вы должны были выбрать MongoDB в первую очередь.

Ответ 2

Если вам не нужна отдельная коллекция, просто вставьте разрешениеSchema в папкуSchema.

var folderSchema = new Schema({
    name: { type: string },
    permissions: [ {
        role: { type: String },
        create_folders: { type: Boolean },
        create_contents: { type: Boolean }
    } ]
});

Если вам нужны отдельные коллекции, это лучший подход:

У вас может быть модель разрешений:

var mongoose = require('mongoose');
var PermissionSchema = new Schema({
  role: { type: String },
  create_folders: { type: Boolean },
  create_contents: { type: Boolean }
});

module.exports = mongoose.model('Permission', PermissionSchema);

И модель Папки со ссылкой на документ разрешения. Вы можете ссылаться на другую схему следующим образом:

var mongoose = require('mongoose');
var FolderSchema = new Schema({
  name: { type: string },
  permissions: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Permission' } ]
});

module.exports = mongoose.model('Folder', FolderSchema);

И затем вызовите Folder.findOne().populate('permissions'), чтобы попросить мангуста заполнить разрешения поля.

Теперь, следующее:

savePermission: function (folderId, permission, callback) {
    Folder.findOne({ _id: folderId }).populate('permissions').exec(function (err, data) {
        var perm = _.findWhere(data.permissions, { _id: permission._id });                

        _.extend(perm, permission);

        data.markModified("permissions");
        data.save(callback);
    });
}

Поле perm не будет undefined (если разрешение._id действительно находится в массиве разрешений), поскольку оно было заполнено Mongoose.

Ответ 3

Чтобы проверить поддокументы при обновлении в Mongoose, вам нужно "загрузить" его как объект Schema, а затем Mongoose автоматически активирует проверку и перехватывает.

const userSchema = new mongoose.Schema({
  // ...
  addresses: [addressSchema],
});

Если у вас есть массив поддокументов, вы можете выбрать желаемый метод id(), предоставленный Mongoose. Затем вы можете обновлять свои поля отдельно или если вы хотите обновить сразу несколько полей, используйте метод set().

User.findById(userId)
  .then((user) => {
    const address = user.addresses.id(addressId); // returns a matching subdocument
    address.set(req.body); // updates the address while keeping its schema       
    // address.zipCode = req.body.zipCode; // individual fields can be set directly

    return user.save(); // saves document with subdocuments and triggers validation
  })
  .then((user) => {
    res.send({ user });
  })
  .catch(e => res.status(400).send(e));

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

User.findOne({
  'addresses._id': addressId,
})
// .then() ... the same as the example above

Помните, что в MongoDB поддокумент сохраняется только, когда родительский документ сохраняется.

Подробнее о теме официальная документация.