Mongoose trouver/mettre à jour le sous-document

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

ainsi, pour chaque Page je peux avoir plusieurs permissions. Dans mon CMS il y a un panneau où j'énumère tous les dossiers et leurs permissions. L'administrateur peut modifier une autorisation unique et de l'enregistrer.

je pourrais facilement sauvegarder l'ensemble de l' Dossier document avec son tableau de permissions, où une seule permission a été modifiée. Mais je ne veux pas sauver tout le document (le vrai schéma a beaucoup plus de champs) j'ai donc 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 toujours Non défini! J'ai essayé de "statiquement" récupération de l'autorisation de cette façon:

var perm = data.permissions[0];

et cela fonctionne très bien, donc le problème est que la bibliothèque de Underscore n'est pas capable d'interroger le tableau de permissions. Donc je suppose qu'il y a un meilleur (et un meilleur) moyen d'obtenir le sous-document d'un document récupéré.

une idée?

P. S.: j'ai résolu vérification de chaque élément des données.tableau de permission utilisant une boucle " for " et vérifiant les données.les autorisations[i]._id == autorisation.Mais j'aimerais une solution plus intelligente, je sais qu'il y en a une!

31
demandé sur Neil Lunn 2014-10-02 12:08:31

3 réponses

comme vous le constatez, la valeur par défaut dans mongoose est que lorsque vous "intégrez" des données dans un tableau comme celui-ci, vous obtenez un _id valeur pour chaque entrée de tableau faisant partie 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 dont vous souhaitez mettre à jour. La façon MongoDB de faire ceci est le position $ la variable operator, qui maintient la position "matched" dans le tableau:

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

    }
);

.findOneAndUpdate() la méthode retournera le document modifié ou vous pouvez simplement utiliser .update() comme méthode si vous n'avez pas besoin que le document soit retourné. Les principales parties sont "matching" l'élément du tableau à mettre à jour et "identifying" qui correspondent avec le position $ comme mentionné plus haut.

alors bien sûr vous utilisez le $set opérateur de sorte que les éléments que vous indiquez sont effectivement envoyés "sur le fil" à la serveur. Vous pouvez aller plus loin avec "pointillé" et spécifiez simplement les éléments que vous voulez réellement mettre à jour. Comme dans:

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

    }
);

donc C'est la flexibilité que MongoDB fournit, où vous pouvez être très "ciblé" dans la façon dont vous mettez réellement à jour un document.

ce que cela fait cependant, c'est de" contourner "toute logique que vous auriez pu intégrer dans votre schéma" Mangoose", tel que" validation "ou autres"hooks de pré-sauvegarde". C'est parce que la voie "optimale" est un MongoDB la "fonctionnalité" et la façon dont il est conçu. Mongoose elle-même essaie d'être un enveloppeur de "convenance" sur cette logique. Mais si vous êtes prêt à prendre le contrôle vous-même, alors les mises à jour peuvent être faites de la manière la plus optimale.

donc, lorsque c'est possible de le faire, 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 articles "parent" et "enfant" dans des mises à jour simples où vous n'avez pas besoin de vous soucier de la concurrence. Est probablement l'une des raisons pour lesquelles vous devriez avoir choisi MongoDB en premier lieu.

70
répondu Neil Lunn 2014-10-02 08:57:06

pour valider les sous-documents lors de la mise à jour dans Mongoose, vous devez les "charger" comme un objet Schema, puis Mongoose déclenchera automatiquement la validation et les crochets.

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

Si vous avez un tableau de sous-documents, vous pouvez extraire souhaitée l'un avec l' id() méthode fournie par Mongoose. Vous pouvez alors mettre à jour ses champs individuellement, ou si vous voulez mettre à jour plusieurs champs à la fois, utilisez le set() méthode.

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 ne le faites pas vraiment besoin de l' userId pour trouver le document utilisateur, vous pouvez l'obtenir en cherchant 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

rappelez-vous qu'en MongoDB le sous-document est sauvé lorsque le document parent est sauvegardé.

en savoir plus sur le sujet documents officiels.

8
répondu Arian Acosta 2017-12-11 22:21:37

si vous ne voulez pas de collection séparée, il suffit d'intégrer la permissionSchema dans le 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 séparer les collections, c'est la meilleure approche:

Vous pouvez avoir un modèle d'Autorisation:

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 de permission. Vous pouvez faire référence à un autre schéma comme ceci:

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

Et ensuite appeler Folder.findOne().populate('permissions') de demander à Mangoose de remplir le champ autorisation.

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

perm le champ ne sera pas indéfini (si la permission._id est en fait dans le tableau permissions), puisqu'il a été peuplé par Mongoose.

4
répondu RaphDG 2014-10-02 08:45:01