web-dev-qa-db-fra.com

Sous-document Mongoose find / update

J'ai les schémas suivants pour le document Dossier:

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

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

Donc, pour chaque page, je peux avoir de nombreuses autorisations. Dans mon CMS, il y a un panneau où je liste tous les dossiers et leurs autorisations. L'administrateur peut éditer une seule permission et la sauvegarder.

Je pourrais facilement sauvegarder le document entier Dossier avec son tableau d'autorisations, où une seule autorisation a été modifiée. Mais je ne veux pas sauvegarder tout le document (le schéma réel a beaucoup plus de champs) alors j'ai fait ceci:

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

mais le problème est que perm est toujours indéfini! J'ai essayé de "statiquement" chercher l'autorisation de cette manière:

var perm = data.permissions[0];

et cela fonctionne très bien, le problème est que la bibliothèque Underscore ne peut pas interroger le tableau d'autorisations. Je suppose donc qu’il existe un meilleur moyen (et une solution de rechange) d’obtenir le sous-document d’un document récupéré.

Une idée?

PS: J'ai résolu la vérification de chaque élément du tableau data.permission à l'aide d'une boucle "pour" et la vérification de data.permissions [i] ._ id == permission._id mais je voudrais un système plus intelligent. solution, je sais qu’il en existe une!

42
Darko Romanov

Ainsi, comme vous le remarquerez, le comportement par défaut dans Mangouste est que lorsque vous "incorporez" des données dans un tableau comme celui-ci, vous obtenez une valeur _id Pour chaque entrée de tableau dans le cadre de ses propres propriétés de sous-document. Vous pouvez réellement utiliser cette valeur pour déterminer l'index de l'élément que vous souhaitez mettre à jour. Pour cela, la méthode MongoDB est la variable opérateur positional $ , qui contient la position "correspondante" dans le tableau:

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

    }
);

Cette méthode .findOneAndUpdate() renvoie le document modifié ou vous pouvez simplement utiliser .update() comme méthode si vous n'avez pas besoin du document renvoyé. Les parties principales "correspondent" à l'élément du tableau à mettre à jour et "identifient", ce qui correspond à l'élément positional $ , comme mentionné précédemment.

Alors bien sûr, vous utilisez l'opérateur $set pour que seuls les éléments que vous spécifiez sont effectivement envoyés "par le fil" au serveur. Vous pouvez aller plus loin avec "notation par points" et spécifier simplement les éléments que vous souhaitez réellement mettre à jour. Un péché:

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

    }
);

C’est donc la flexibilité offerte par MongoDB, qui vous permet d’être très "ciblé" sur la façon de mettre à jour un document.

Cela permet toutefois de "contourner" toute logique que vous pourriez avoir intégrée dans votre schéma "mangouste", telle que la "validation" ou d'autres "crochets de pré-sauvegarde". En effet, le moyen "optimal" est une "fonctionnalité" MongoDB et sa conception. Mongoose lui-même tente d'être un "wrapper" de "commodité" sur cette logique. Mais si vous êtes prêt à prendre le contrôle vous-même, les mises à jour peuvent être effectuées de la manière la plus optimale.

Dans la mesure du possible, conservez vos données "intégrées" et n’utilisez pas de modèles référencés. Il permet la mise à jour atomique des éléments "parent" et "enfant" dans de simples mises à jour sans se soucier de la simultanéité. C'est probablement l'une des raisons pour lesquelles vous auriez dû choisir MongoDB en premier lieu.

93
Neil Lunn

Afin de valider les sous-documents lors de la mise à jour dans Mongoose, vous devez le "charger" en tant qu'objet Schéma, puis Mongoose déclenchera automatiquement la validation et les points d'ancrage.

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

Si vous avez un tableau de sous-documents, vous pouvez récupérer celui désiré avec la méthode id() fournie par Mongoose. Ensuite, vous pouvez mettre à jour ses champs individuellement ou, si vous souhaitez mettre à jour plusieurs champs à la fois, utilisez la méthode 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));

Notez que vous n'avez pas vraiment besoin de userId pour trouver le document utilisateur. Vous pouvez l'obtenir en recherchant celui qui a un sous-document d'adresse qui correspond à addressId comme suit:

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

N'oubliez pas que dans MongoDB, le sous-document est enregistré uniquement lorsque le document parent est enregistré.

En savoir plus sur le sujet sur le documentation officielle .

13
Arian Acosta

Si vous ne voulez pas de collection séparée, intégrez simplement permissionSchema dans folderSchema.

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

Si vous avez besoin de collections séparées, voici la meilleure approche:

Vous pourriez avoir un modèle de permission:

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

Et un modèle de dossier avec une référence au document d'autorisation. Vous pouvez référencer un autre schéma comme celui-ci:

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

Puis appelez Folder.findOne().populate('permissions') pour demander à Mangouste de renseigner les autorisations de champ.

Maintenant, ce qui suit:

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

Le champ perm ne sera pas indéfini (si permission._id est réellement dans le tableau des autorisations), car il a été rempli par Mongoose.

6
RaphDG