Mongoose: pre, post middleware ne sont pas exécutés sur findByIdAndUpdate

Créé le 15 juin 2012  ·  102Commentaires  ·  Source: Automattic/mongoose

Parce qu'une méthode findAndUpdate est présentée pour réduire le code suivant :

 Model.findById(_id, function (err, doc) {
      if (doc) {
          doc.field = 'value';
          doc.save(function (err) {
                 // do something;
          });
      }
 });

pour ça:

   .findByIdAndUpdate(_id, {$set: {field: 'value'}}, function (err, doc) {
        // do something 
    });

Nous devons utiliser le pré et le post middleware exactement de la même manière. À l'heure actuelle, les middlewares post-post ne sont pas exécutés lorsque je fais findByIdAndUpdate.

Commentaire le plus utile

Bonjour,

Je sais que le problème est clos, mais je ne suis pas entièrement satisfait de la réponse. Ne devrait-il pas y avoir au moins un middleware post-sauvegarde ou post-mise à jour pour findOneAndUpdate et d'autres opérations similaires ? Cela semble correct puisque le document est retourné. Pas faisable pour les middlewares pré ou pour Model.update, je suis d'accord.

Cela améliorerait considérablement les capacités des plugins tels que mongoosastic qui est actuellement aveugle à certaines opérations qu'il devrait pouvoir prendre en charge.

S'il n'y a pas de middleware, quelqu'un a-t-il une idée sur la façon de gérer certaines opérations de post-mise à jour dans un plugin ?

Merci

Tous les 102 commentaires

intentionnellement. il y a des docs impliqués pour appeler des hooks.

correction, il n'y a pas de docs sur lesquels appeler des crochets.

Alors? Si je veux appeler pré, post middleware, je dois utiliser la première approche ?

Oui c'est correct. Model.update,findByIdAndUpdate,findOneAndUpdate,findOneAndRemove,findByIdAndRemove sont toutes des commandes exécutées directement dans la base de données.

Cela devrait certainement être clarifié dans le guide , surtout si vous parlez de validation dans cette même page et que vous décrivez findByIdAndUpdate comme "meilleur"

si vous cliquez sur le lien "mieux" cela vous amène à la documentation complète de
la méthode où il explique la validation etc.

n'hésitez pas à envoyer une pull request pour ajouter quelque chose que vous jugez meilleur. son
assez facile à faire:
https://github.com/LearnBoost/mongoose/blob/master/CONTRIBUTING.md

Le mercredi 31 octobre 2012 à 18h03, Jesse Fulton [email protected] a écrit :

Cela devrait certainement être clarifié dans le guide http://mongoosejs.com/docs/documents.html ,
surtout si vous parlez de validation dans cette même page et
décrivant findByIdAndUpdate comme "meilleur"


Répondez directement à cet e-mail ou consultez-le sur Gi tHubhttps://github.com/LearnBoost/mongoose/issues/964#issuecomment -9967865.

Aaron
@aaronheckmann https://twitter.com/#!/aaronheckmann

Notes ajoutées à la documentation du middleware

Demande d'extraction : https://github.com/LearnBoost/mongoose/pull/1750

Bonjour,

Je sais que le problème est clos, mais je ne suis pas entièrement satisfait de la réponse. Ne devrait-il pas y avoir au moins un middleware post-sauvegarde ou post-mise à jour pour findOneAndUpdate et d'autres opérations similaires ? Cela semble correct puisque le document est retourné. Pas faisable pour les middlewares pré ou pour Model.update, je suis d'accord.

Cela améliorerait considérablement les capacités des plugins tels que mongoosastic qui est actuellement aveugle à certaines opérations qu'il devrait pouvoir prendre en charge.

S'il n'y a pas de middleware, quelqu'un a-t-il une idée sur la façon de gérer certaines opérations de post-mise à jour dans un plugin ?

Merci

@albanm certaines méthodes contournent complètement la mangouste , vous n'obtenez donc pas les crochets du middleware. AFAIK, la seule façon d'exécuter les crochets est d'utiliser des appels séparés find() et save() comme mentionné ci-dessus.

Je comprends et c'est logique. Les pré-middlewares sont hors de question car il n'y a pas de récupération avant certaines méthodes. Néanmoins, la mangouste devrait être capable d'encapsuler les opérations de mise à jour qui renvoient également les documents mis à jour et déclenchent des crochets de publication.

Je pense que @albanm a raison, les gens peuvent vouloir la même fonctionnalité lorsqu'ils utilisent la même fonction. Que diriez-vous d'envelopper ces méthodes de «mise à jour directe» avec une interception de vérification que s'il existe des crochets? Si le crochet existe, utilisez-le ou appelez les méthodes de mise à jour d'origine dans le cas contraire.

+1

+1

+1

+1

:+1:

+1

:-1:
Je reçois la demande de fonctionnalité, mais plus je travaille avec le middleware, plus je me retrouve à contourner le middleware souvent lors de l'appel de scripts de mise à jour de la base de données et d'autres éléments qui ne sont pas routiniers pour mes opérations normales. En utilisant des méthodes statiques, il devient très facile de créer un wrapper personnalisé qui implémente déjà les demandes de fonctionnalités ci-dessus. Étant donné que la mangouste s'ouvre désormais aux idées pour la version 4, il est peu probable qu'un changement d'API de cette ampleur se produise dans la v3. Je demande que cela soit déplacé vers la discussion v4.

Je le ferais cependant :+1: si je pouvais désactiver le middleware lors d'un appel de sauvegarde. Je me retrouve souvent avec un objet mangouste qui a été passé d'une autre fonction et je veux simplement effectuer un .save - dans cette situation, faire un .save est préférable à l'écriture d'une nouvelle requête. Si cela est possible, merci de le signaler

En tout cas, magnifique bibliothèque. Félicitations aux formidables mainteneurs. Je ne sais pas comment je fonctionnerais sans elle.

+1

Un moyen simple d'ajouter des crochets pour ces méthodes est d'écraser les fonctions d'intégration :

_update = UserModel.update
UserModel.update = (conditions, update) ->
  update.updatedAt or= new Date
  _update.apply this, arguments

Ensuite, chaque appel de mise à jour de mangouste corrigera la clé updatedAt des données.

Vous pouvez essayer ce modèle limbo . Il s'agit d'un simple wrapper du modèle mongoose, prenant en charge la liaison statique/méthode/écrasement à tous les schémas, et appelle les méthodes rpc pour interroger le mongodb.

+1, besoin de cette fonctionnalité...

Bien que je comprenne en quelque sorte le raisonnement, le manque de crochets sur les mises à jour atomiques à mon humble avis rend Mongoose quelque peu inutile dans la pratique. Lorsque j'utilise des mises à jour atomiques, les validations, les valeurs par défaut, etc. ne sont pas exécutées, de sorte que l'objectif de l'utilisation d'un ODM est vaincu. L'utilisation de la recherche/sauvegarde fera l'affaire, mais y a-t-il une garantie que cela soit toujours utilisé ?

De plus, j'essaierais généralement d'éviter de trouver/sauvegarder car ce n'est pas une opération atomique. MongoDB compense son manque de prise en charge des transactions en fournissant de puissantes fonctionnalités de requête et de mise à jour atomiques. J'utiliserais donc ces opérations atomiques, mais sans le support du middleware, Mongoose ne fournira pas beaucoup de valeur par rapport au MongoClient natif.

Même les exemples dans http://aaronheckmann.tumblr.com/post/48943525537/mongoose-v3-part-1-versioning utiliseraient update , contournant ainsi le middleware. Je peux utiliser correctement le versioning ou le middleware mais pas combiner les deux ? Vraiment, à quoi ça sert de l'avoir ?

Je ne comprends même pas entièrement les raisons techniques : si update & co. Enveloppez les opérations de la base de données, pourquoi ne pouvons-nous pas intercepter l'appel et transmettre les objets de requête afin que nous puissions faire une validation/personnalisation avant de faire la mise à jour ?

@joerx +1 suffirait .. :) mais votre raisonnement est sans faille.

La branche 3.9.x prend en charge les hooks pré et post pour find et findOne - il devrait être facile d'ajouter la prise en charge de findOneAndUpdate et update .

Cette fonctionnalité est-elle fusionnée ?

Ainsi, les hooks pre('findOneAndUpdate') et post('findOneAndUpdate') sont dans master, il n'y a pas encore de hook de mise à jour. Il n'y a pas encore de version contenant l'un ou l'autre de ceux-ci.

Alors, déclenche-t-il une pré-sauvegarde après .update() maintenant ?

Non. Il existe un crochet update() séparé pour Query.update() . Les crochets de sauvegarde sont distincts des crochets de requête.

@ vkarpov15 Pouvez-vous s'il vous plaît créer un lien vers la documentation de support pour le crochet de mise à jour ? Savez-vous quand pre('findOneAndUpdate') et post('findOneAndUpdate') sortiront ?

@karlstanton utilise 4.0.0-rc2, npm install mongoose@unstable :)

'use strict';

var Promise  = require('bluebird');
var mongoose = Promise.promisifyAll(require('mongoose'));

var counterSchema = new mongoose.Schema({
    total: {
        type:    Number,
        default: 0
    }
});

counterSchema.post('findOneAndUpdate', function (doc) {
    console.log(doc.total);
});

var Counter = mongoose.model('Counter', counterSchema);

Promise.coroutine(function *() {
    yield mongoose.connectAsync(process.env.MONGODB_URI);
    console.log('Connected');
    let counter = yield Counter.createAsync({});
    console.log(`${counter.total}`);
    for (let i = 0; i < 10; i++) {
        yield Counter.findOneAndUpdateAsync({ _id: counter.id }, { $inc: { total: 1} });
    }
})();
0
0
1
2
3
4
5
6
7
8
9

On dirait que c'est un pas en arrière. Qu'est-ce que je rate?

AH, le changement de rupture par défaut du drapeau new . Bien qu'il y ait quelque chose à dire sur la cohérence avec le pilote sous-jacent, je dois dire que c'est vraiment contre-intuitif, surtout si l'on considère ce nouveau crochet.

@neverfox :)

Ouais @neverfox bonne prise. Cette modification est documentée dans les notes de version et vous pouvez voir plus de détails sur les raisons pour lesquelles cela a été modifié dans #2262.

La raison en est que new est faux par défaut dans le pilote de nœud mongodb, le shell mongodb, le serveur mongodb réel, etc. et les couches wrapper définissant des valeurs par défaut non standard rendent la vie difficile pour les développeurs. Mon exemple canonique d'un module qui se trompe horriblement est gulp-uglify , qui remplace un tas de valeurs par défaut de uglify-js.

Je vois que le problème est clos, mais la fonctionnalité en place dans la version 4.0.2 est-elle toujours dans la version instable ? Il ne semble pas s'exécuter sur findOneAndUpdate avec Scema.pre('update') ou Schema.pre('findOneAndUpdate') avec la version 4.0.2. Ai-je raté quelque chose que je dois passer à la fonction?

Comment déclarez-vous le pré crochet @CaptainStaplerz ?

Le hook middleware findOneAndUpdate est-il disponible dans la version 4.0.2 ? J'ai mis à jour de 3.8 à la dernière mongoose 4.0.2 pour l'utiliser et le middlware Document.schema.post('findOneAndUpdate', la fonction (doc) ne se déclenche pas comme save() ou remove() le fait

@honitus montre moi ton code

@ vkarpov15 - Merci pour la réponse rapide, c'est parti

blog.contrôleur, js

// Updates an existing Blog in the DB, adds comment
exports.update = function(req, res) {

Blog.findOneAndUpdate(
     {"_id": req.body._id,},
      {"$push": {"comments": buildComment}},
     {safe: true, upsert: true}, function (err, workspace) {
      if (err) {
         return handleError(res, err);
       }
       return res.send(200);
      }
   );

}

blog.socket.js

/**
 * Broadcast updates to client when the model changes
 */

'use strict';

var Blog= require('./blog.model');

exports.register = function(socket) {
//SAVE WORKS
  Blog.schema.post('save', function (doc) {
    onSave(socket, doc);
  });

// IS NOT TRIGGERED :(
 Blog.schema.post('findOneAndUpdate', function (doc) {
    onComment(socket, doc);
  });

  Blog.schema.post('remove', function (doc) {
    onRemove(socket, doc);
  });
}

//SAVE WORKS when a new blog is created
function onSave(socket, doc, cb) {
  socket.emit('blog:save', doc);
}

// IS NOT TRIGGERED :(
function onComment(socket, doc, cb) {
  socket.emit('blog:findOneAndUpdate', doc);
}

function onRemove(socket, doc, cb) {
  socket.emit('blog:remove', doc);
}

@ vkarpov15 Merci pour la réouverture. Cela signifie-t-il que le crochet pour findOneAndUpdate n'existe pas dans la version 4.0.2 et que vous envisagez de l'inclure dans la version 4.0.3.

@ vkarpov15 Voici le code où je déclare les crochets :

...
var TodoSchema = new mongoose.Schema({
  name: {type: String, required: true},
  note: String,
  completed: {type: Boolean, default: false},
  updatedAt: {type: Date, default: Date.now},
  user: {
    type: mongoose.Schema.ObjectId,
    ref: 'Users'
  }
});
...
// Not executed
TodoSchema.pre('update', function() {
  console.log('------------->>>>>> update updatedAt')
  this.updatedAt = Date.now();
});
// Not executed
TodoSchema.pre('findOneAndUpdate', function() {
  console.log('------------->>>>>> update updatedAt')
  this.updatedAt = Date.now();
});

Et voici où j'appelle la mise à jour :

...
router.route('/:id')
.put(function(req, res, next) {
  TodoModel.findOneAndUpdate({_id: req.params.id, user: req.user.id}, req.body, {new: true}, function(err, post) {
    if(err) return next(err);
    if(post) {
      res.status(200).json(post);
    }
    else {
      next(newSystemError(errorCodes.TODO_NOT_FOUND, undefined, req.params.id));
    }
  });
});

Autant que je sache, le crochet findOneAndUpdate() devrait être là, s'il ne fonctionne pas, c'est un bogue

@CaptainStaplerz essayez :

TodoSchema.pre('update', function(next) {
  console.log('------------->>>>>> update updatedAt')
  this.updatedAt = Date.now();
  next();
});

En outre, l'instruction console.log est-elle en cours d'exécution ou est-ce simplement la partie Date.now() qui donne des résultats inattendus ?

@vkarpov15
J'ai changé mon code source, en ajoutant les modifications que vous avez apportées dans Ajouter l'implémentation # 964 https://github.com/Automattic/mongoose/commit/e98ef98e857965c4b2ae3339fdd7eefd2a5a9913

Cela fonctionne comme un charme maintenant. Je pense donc que le correctif n'est pas vérifié dans main

@honitus êtes-vous sûr d'utiliser mongoose 4.0.2 ? Ce changement est en fait engagé pour 4.0.0 et plus.

honitus$ npm voir la version mangouste
4.0.2

@honitus ce que vous faites de manière incorrecte, c'est que vous ajoutez des crochets au schéma après avoir compilé votre modèle. Model.schema.pre('remove'); ne devrait pas fonctionner, voir "Compiler votre premier modèle" dans la documentation du modèle . Attachez d'abord les crochets au schéma, puis les choses devraient fonctionner - c'est la seule différence que je vois entre votre code et nos tests.

@CaptainStaplerz le seul moyen que je puisse trouver pour reproduire votre code est avec une mise à jour vide. Le code ci-dessous fonctionne

var mongoose = require('mongoose');
mongoose.set('debug', true);
var util = require('util');

mongoose.connect('mongodb://localhost:27017/gh964');

var TodoSchema = new mongoose.Schema({
  name: {type: String, required: true},
  note: String,
  completed: {type: Boolean, default: false},
  updatedAt: {type: Date, default: Date.now},
  user: {
    type: mongoose.Schema.ObjectId,
    ref: 'Users'
  }
});
TodoSchema.pre('update', function() {
  console.log('------------->>>>>> update updatedAt')
  this.updatedAt = Date.now();
});
TodoSchema.pre('findOneAndUpdate', function() {
  console.log('------------->>>>>> update updatedAt')
  this.updatedAt = Date.now();
});

var Todo = mongoose.model('Todo', TodoSchema);

Todo.update({}, { note: "1" }, function(err) {
  if (err) {
    console.log(err);
  }
  console.log('Done');
  process.exit(0);
});

Mais une fois la mise à jour vide :

var mongoose = require('mongoose');
mongoose.set('debug', true);
var util = require('util');

mongoose.connect('mongodb://localhost:27017/gh964');

var TodoSchema = new mongoose.Schema({
  name: {type: String, required: true},
  note: String,
  completed: {type: Boolean, default: false},
  updatedAt: {type: Date, default: Date.now},
  user: {
    type: mongoose.Schema.ObjectId,
    ref: 'Users'
  }
});
TodoSchema.pre('update', function() {
  console.log('------------->>>>>> update updatedAt')
  this.updatedAt = Date.now();
});
TodoSchema.pre('findOneAndUpdate', function() {
  console.log('------------->>>>>> update updatedAt')
  this.updatedAt = Date.now();
});

var Todo = mongoose.model('Todo', TodoSchema);

Todo.update({}, { }, function(err) {
  if (err) {
    console.log(err);
  }
  console.log('Done');
  process.exit(0);
});

Le hook de pré-mise à jour ne s'exécute plus. Est-ce cohérent avec ce que vous voyez ?

@vkarpov15
Model.schema.pre('supprimer'); est l'un des stubs créés automatiquement par la pile angulaire complète
J'ai ajouté l'implémentation #964 e98ef98
Tout ce que j'ai fait est d'ajouter une ligne ci-dessous
this._schema.s_.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,
Voir la ligne 1526 dans 4.0.2 - this.model.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,

Remplacez la ligne 1526 par this.schema.s.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,
et il fonctionne.

C'est par conception. Mongoose n'est pas censé autoriser la modification des hooks de schéma après la compilation du modèle (c'est-à-dire l'appel mongoose.model() ), c'est pourquoi cela ne fonctionne plus.

@ vkarpov15 J'ai réussi à faire fonctionner le crochet maintenant avec ce qui suit, ça devait être une faute de frappe stupide ou autre chose:

TodoSchema.pre('findOneAndUpdate', function(next) {
  console.log('------------->>>>>> update updatedAt: ', this.updatedAt);
  this.updatedAt = Date.now();
  next();
});

Cependant, 'this' ne semble pas faire référence au modèle mis à jour (mais c'est le cas dans le crochet 'save' ?), ainsi this.updatedAt fait référence à undefined.

Comment mettre à jour le champ 'updatedAt' dans le crochet 'findOneAndUpdate' ?

Je devrais ajouter plus de documents clarifiant cela - le document qui est mis à jour peut ne pas exister en mémoire lorsque vous appelez findOneAndUpdate , donc l'objet this fait référence à la requête plutôt qu'au document dans le middleware de requête . Essayez this.update({ $set: { updatedAt: Date.now() } });

@ vkarpov15 Je n'ai pas pu faire fonctionner this.update({ $set: { updatedAt: Date.now() } }); , mais j'ai réussi à mettre à jour la doc sur le hook findOneAndUpdate avec ceci: this._update['$setOnInsert'].updatedAt=Date.now(); .

Je recommanderais fortement de ne pas dépendre de peaufiner l'état interne comme ça. Je suis assez surpris que vous n'ayez pas réussi à faire fonctionner this.update() - pouvez-vous me montrer à quoi ressemble votre crochet ?

Sûr! (Notez que j'utilise 4.0.2)

tagSchema.pre('findOneAndUpdate',function(next){
  var self = this;

  //NOTE THAT 'this' in the findOneAndUpdate hook refers to the query, not the document
  //https://github.com/Automattic/mongoose/issues/964

  geoData.country.findOne({'_id':self._update['$setOnInsert'].countryCode}).select('_id name cca2 cca3 ccn3').lean().exec(function(err,country){
    if (err){throw err;}
    if (!country){throw 'no coutnry';}
    self._update['$setOnInsert'].country=country;
    next();
  });
});

Je pourrais évidemment gérer cela lorsque j'initialise le document ailleurs dans mon application, mais c'est bien d'avoir tout contenu ici dans Mongoose. Bienvenue à toutes les pensées!

Ouais, j'étais confus et je pensais que vous utilisiez update . Le script ci-dessous

var mongoose = require('mongoose');
mongoose.set('debug', true);
var util = require('util');

mongoose.connect('mongodb://localhost:27017/gh964');

var TodoSchema = new mongoose.Schema({
  name: {type: String, required: true},
  note: String,
  completed: {type: Boolean, default: false},
  updatedAt: {type: Date, default: Date.now},
  user: {
    type: mongoose.Schema.ObjectId,
    ref: 'Users'
  }
});
TodoSchema.pre('findOneAndUpdate', function() {
  this.findOneAndUpdate({}, { updatedAt: Date.now() });
});

var Todo = mongoose.model('Todo', TodoSchema);

Todo.findOneAndUpdate({}, { note: "1" }, function(err) {
  if (err) {
    console.log(err);
  }
  console.log('Done');
  process.exit(0);
});

Fonctionne correctement et exécute la requête souhaitée :

Mongoose: todos.findAndModify({}) [] { '$set': { note: '1', updatedAt: new Date("Thu, 07 May 2015 20:36:39 GMT") } } { new: false, upsert: false }
Done

Veuillez donc utiliser

TodoSchema.pre('findOneAndUpdate', function() {
  this.findOneAndUpdate({}, { updatedAt: Date.now() });
});

Au lieu de manipuler manuellement l'état interne de mquery - c'est généralement une mauvaise idée à moins que vous ne sachiez vraiment ce que vous faites.

Est-ce que ça marche ou pas ? Le seul crochet qui fonctionne pour moi est "enregistrer", le repos est complètement ignoré. Je suis sous 4.0.6. Merci

@agjs fournissez un exemple de code s'il vous plaît.

UserController.prototype.updateAvatar = function (req, res) {
    return new Promise(function (resolve, reject) {
        CompanyDetails.update({
            _author: req.user._id
        }, {
            avatarPath: req.files.file
        }, function (error, updated) {
            if (error) {
                reject(error);
            } else {
                resolve(updated);
            }
        });
    }).then(function (resolved) {
        res.sendStatus(204).send(updated);
    }).catch(function (error) {
        next(error);
    })

};

CompanyAvatarSchema.pre('update', function (next) {
    console.log('pre save');
    let VirtualModel = this,
        parent = this.ownerDocument(),
        PATH = path.normalize('./public/images/uploads/avatars/' + parent._id);

    mkdirp(PATH, function (error) {
        if (error) {
            next(error);
        } else {
            fs.rename(VirtualModel.path, path.join(PATH, VirtualModel.name), function (error2) {
                if (error2) {
                    next(error2);
                } else {
                    next();
                }
            });
        }
    });

});

J'ai un autre pré-hook sur un autre modèle avec model.create et pre-save et cela fonctionne normalement.

Avec une mise à jour quelconque, il ne voit tout simplement pas le crochet, ne le console même pas. J'ai aussi essayé findOneAndUpdate, etc., ça ne marche pas vraiment... Chose amusante, j'ai parcouru tout ce fil et, comme je le fais habituellement, j'ai vérifié la documentation officielle et vous prétendez même que cela fonctionne.

Quelle est la relation entre CompanyAvatarSchema et CompanyDetails dans le code ci-dessus ?

Les détails de la société ont CompanyAvatar.schema comme sous-document

avatarPath: {
        type: [CompanyAvatar.schema],
        required: true
    }

De plus, ce n'est pas seulement le pré-crochet, mais la validation est également complètement ignorée. Ce sous-document est rempli mais ignore à la fois la validation et le pré-crochet. J'ai tout googlé, j'ai aussi essayé CECI mais rien ne semble fonctionner. Lorsque je modifie juste pour tester ma requête pour créer et invoquer un modèle avec new aka var parent = new Parent(), cela fonctionne.

Vous appelez CompanyDetails.update() mais le pré-hook est défini sur un schéma séparé. Le middleware de requête ne déclenche pas l'atm pre('update') du schéma imbriqué.

Veuillez également fournir un exemple de code plus complet pour votre cas "la validation est complètement ignorée".

Voici mon schéma d'avatar d'entreprise réalisé pour validation et pré-hooks pour les utilisateurs qui mettent à jour leur profil (photo d'avatar) :

'use strict';

let mongoose = require('mongoose'),
    mkdirp = require('mkdirp'),
    fs = require('fs'),
    path = require('path'),
    Schema = mongoose.Schema;

let CompanyAvatarSchema = new Schema({
    name: String,
    width: Number,
    height: Number,
    size: Number,
    type: String
});


CompanyAvatarSchema.path('type').validate(function (type) {
    return /^image\//.test(type);
}, 'Image type not allowed!');

CompanyAvatarSchema.path('size').validate(function (size) {
    return size < 5;
}, 'Image too big!');



CompanyAvatarSchema.virtual('path').set(function (path) {
    return this._path = path;
}).get(function () {
    return this._path;
});


CompanyAvatarSchema.virtual('public_path').get(function () {
    var parent = this.ownerDocument();
    var PATH = path.normalize('images/uploads/avatars/' + parent._id);
    if (this.name) {
        return path.join(PATH, this.name);
    }
});

CompanyAvatarSchema.set('toJSON', {
    getters: true
});

CompanyAvatarSchema.pre('findOneAndUpdate', function (next) {
    console.log('pre save');
    let VirtualModel = this,
        parent = this.ownerDocument(),
        PATH = path.normalize('./public/images/uploads/avatars/' + parent._id);

    mkdirp(PATH, function (error) {
        if (error) {
            next(error);
        } else {
            fs.rename(VirtualModel.path, path.join(PATH, VirtualModel.name), function (error2) {
                if (error2) {
                    next(error2);
                } else {
                    next();
                }
            });
        }
    });

});


let runValidatorsPlugin = function (schema, options) {
    schema.pre('findOneAndUpdate', function (next) {
        this.options.runValidators = true;
        next();
    });
};

CompanyAvatarSchema.plugin(runValidatorsPlugin);

let CompanyAvatar = mongoose.model('CompanyAvatar', CompanyAvatarSchema);
module.exports = CompanyAvatar;

Voici le schéma company_details où company_avatar est un sous-document :

let CompanyDetailsSchema = new mongoose.Schema({
    _author: [{
        type: Schema.Types.ObjectId,
        ref: 'CompanyAccount'
    }],
    company_name: {
        type: String,
        es_indexed: true,
        es_boost: 2.0
    },
    contact_email: {
        type: String,
        es_indexed: true
    },
    website: {
        type: String,
        es_indexed: true
    },
    country: {
        type: String,
        es_indexed: true
    },
    industry: {
        type: String,
        es_indexed: true
    },
    address: {
        type: String,
        es_indexed: true
    },
    about: {
        type: String,
        es_indexed: true
    },
    avatarPath: {
        type: [CompanyAvatar.schema],

    }
});

Et voici le contrôleur de profil de mise à jour et l'avatarPath qui doivent être validés / accrochés avant que cette mise à jour ne soit effectuée :

UserController.prototype.updateAvatar = function (req, res, next) {
    let updates = {
        $set: {
            avatarPath: req.files.file
        }
    };
    return new Promise(function (resolve, reject) {
        CompanyDetails.findOneAndUpdate({
            _author: req.user._id
        }, updates, function (error) {
            if (error) {
                reject(error);
            } else {
                resolve('done');
            }
        });
    }).then(function () {
        res.sendStatus(204);
    }).catch(function (error) {
        next(error);
    });

};

Fondamentalement, mon mongodb est rempli avec les champs de req.files.file mais à part cela, la validation est ignorée et aucun crochet ne fonctionne.

Le problème est que le middleware pre('findOneAndUpdate') est défini sur un schéma imbriqué. À l'heure actuelle, mongoose ne déclenche que l'intergiciel de requête pour les schémas de niveau supérieur, donc les intergiciels définis sur CompanyDetailsSchema seront renvoyés concernant : #3125

+1

@ vkarpov15 je comprends ça

TodoSchema.pre('findOneAndUpdate', function() {
  this.findOneAndUpdate({}, { updatedAt: Date.now() });
});

fonctionne pour définir une propriété sur une valeur matérielle, mais comment pourrais-je _lire et modifier_ une propriété, par exemple hacher un mot de passe ?

TodoSchema.pre('findOneAndUpdate', function() {
  this.findOneAndUpdate({}, { password: hashPassword(.....?....) });
});

Des pensées? C'est un cas d'utilisation assez courant, non ? Ou est-ce que les gens trouvent généralement() puis séparent save() ?

TodoSchema.pre('findOneAndUpdate', function() {
  this.findOneAndUpdate({}, { password: hashPassword(this.getUpdate().$set.password) });
});

Cela devrait fonctionner @willemmulder.

@ vkarpov15 parfait, merci ! Je vais essayer ça ce soir. Cela devrait également fonctionner pour pre('update') , n'est-ce pas ?

@ vkarpov15 Donc je viens de l'essayer en utilisant

schema.pre('update', function(next) {
this.update({}, { $set : { password: bcrypt.hashSync(this.getUpdate().$set.password) } });
next();
});

et si je console.log this je reçois

_update: { '$set': { password: '$2a$10$CjLYwXFtx0I94Ij0SImk0O32cyQwsShKnWh1248BpYsJLIHh7jb66', postalAddress: [Object], permissions: [Object], firstName: 'Willem', lastName: 'Mulder', email: '...@...', _id: 55ed4e8b6de4ff183c1f98e8 } },

ce qui semble bien (et j'ai même essayé de définir cette propriété directement avant) mais à la fin, cela n'écrit pas réellement la valeur hachée dans la base de données, mais simplement la valeur "brute". Y a-t-il quelque chose que je pourrais essayer ?

C'est étrange. Vous pouvez essayer d'activer le mode de débogage de la mangouste avec require('mongoose').set('debug', true); et voir quelle est la requête envoyée à la base de données, cela pourrait vous éclairer.

Merci pour la suggestion. Je viens de faire ça :

je lance ceci:

schema.pre('update', function(next) {
    this.update({}, { password: bcrypt.hashSync(this.getUpdate().$set.password) } );
    console.log(this.getUpdate());
    next();
});

qui renvoie ceci pour le console.log

{ '$set':
   { password: '$2a$10$I1oXet30Cl5RUcVMxm3GEOeTFOLFmPWaQvXbr6Z5368zbfpA8nFEK',
     postalAddress: { street: '', houseNumber: '', zipCode: '', city: '', country: '' },
     permissions: [ '' ],
     __v: 0,
     lastName: '',
     firstName: '',
     email: '[email protected]',
     _id: 563b0410bd07ce2030eda26d } }

et puis ceci pour le débogage de Mongoose

Mongoose: users.update({ _id: ObjectId("563b0410bd07ce2030eda26d") }) { '$set': { password: 'test', postalAddress: { street: '', houseNumber: '', zipCode: '', city: '', country: '' }, permissions: [ '\u001b[32m\'\'\u001b[39m' ], __v: 0, lastName: '', firstName: '', email: '[email protected]', _id: ObjectId("563b0410bd07ce2030eda26d") } } { overwrite: false, strict: true, multi: false, upsert: false, safe: true }
Mongoose: users.findOne({ _id: ObjectId("563b0410bd07ce2030eda26d") }) { fields: { password: 0 } }

Un indice ?

Pas sûr, a ouvert un nouveau problème à suivre.

@ vkarpov15 Merci, suivra l'autre problème.

@ vkarpov15 Je pense que la bonne façon de définir les options pour la requête en cours dans le pré-hook serait quelque chose comme:

  finishSchema.pre('findOneAndUpdate', function (next) {
    this.setOptions({
      new: true,
      runValidators: true
    });
    this.update({}, {
      lastEdited: Date.now()
    });
    next();
  });

mais la documentation, http://mongoosejs.com/docs/api.html#query_Query -setOptions ne mentionne aucune de ces options. Si cela était considéré comme une solution hackish, qu'est-ce qui serait plus approprié?

C'est un problème avec les docs, ce code que vous décrivez semble devoir fonctionner à première vue

Pouvez-vous ouvrir un problème séparé pour cela?

@ vkarpov15 Oui, ça marche. Je pense que je n'ai pas été assez clair.

setOptions applique new et runValidators correctement, je demandais simplement si la définition de ces options via setOptions devrait être préférée à this.options .

setOptions() est préférable IMO, mais les deux devraient fonctionner. Ou vous pourriez simplement faire

this.update({}, { lastEdited: Date.now() }, { new: true, runValidators: true });
schema.pre('update', function(next) {
this.update({}, { $set : { password: bcrypt.hashSync(this.getUpdate().$set.password) } });
next();
});

Cela mettra à jour le mot de passe à chaque appel de update(). Donc, si je change simplement la valeur d'autres propriétés, c'est-à-dire le nom ou l'âge, le mot de passe sera également mis à jour, ce qui n'est pas correct !?

@nlonguit Je suppose que ce sera le cas. Mais vous pouvez accéder via this aux champs qui vont être mis à jour et vous pouvez faire quelque chose comme :

if (this._fields.password) { // <- I'm sure about this one, check in debugger the properties of this 
    this.update({}, { $set : { password: bcrypt.hashSync(this.getUpdate().$set.password) } });
}

if (this._update.$set.password) { this.update({}, { $set: { password: bcrypt.hashSync(this.getUpdate().$set.password)} }); }

Ce code fonctionne bien pour moi. Merci @akoskm

Je me demande s'il serait également possible d'ajouter un pré-crochet pour findByIdAndUpdate. Ce serait bien d'avoir les deux crochets disponibles.

Je l'ai fait de cette façon et ça marche : juste findById puis enregistrez sans mettre à jour aucun champ puis utilisez la méthode findByIdAndUpdate :

dbModel.findById(barId, function (err, bar) {
        if (bar) {

            bar.save(function (err) {
                if (err) throw err;
            });
        }
    });
    dbModel.findByIdAndUpdate(barId, {$set:req.body}, function (err, bar) {
        if (err) throw err;
        res.send('Updated');
    });`

J'essaie de définir une propriété pour avoir la longueur d'un tableau.

schema.post('findOneAndUpdate', function(result) {
    console.log(result.comments.length);
    this.findOneAndUpdate({}, { totalNumberOfComments: result.comments.length });
});

La longueur correcte est consignée, bien que la requête ne définisse jamais totalNumberOfComments et que le champ reste à 0 (puisque le schéma fait référence par défaut : 0).

Quand j'ai console.log(this) à la fin du crochet, je peux voir que mon query contient ce qui suit :

_update: { '$push': { comments: [Object] }, totalNumberOfComments: 27 }

Bien que lorsque j'active le mode débogage, une requête n'est jamais enregistrée par Mongoose.

Y a-t-il quelque chose que je fais mal ou est-ce un bug ?

@zilions this.findOneAndUpdate({}, { totalNumberOfComments: result.comments.length }).exec(); doit réellement exécuter la requête :) Faites juste attention, vous allez obtenir une récursivité infinie car votre crochet de sauvegarde de publication déclenchera un autre crochet de sauvegarde de publication

@vkarpov15 Ahhhh d'accord ! Ensuite, je peux simplement utiliser this.update({} {....}).exec() la place :)
Question cependant, lors de l'utilisation de ceci, il définit parfaitement le champ totalNumberOfComments , bien qu'il effectue également la mise à jour originale du findOneAndUpdate .

Par example:

Post.findOneAndUpdate({_id: fj394hri3hfj}, {$push: {comments: myNewComment}})

Déclenchera le hook suivant :

schema.post('findOneAndUpdate', function(result) {
    this.update({}, {
        totalNumberOfComments: result.comments.length
    }).exec();
}));

Cependant, le crochet va $push à comments le myNewComment nouveau, créant ainsi une entrée en double.

Parce que vous exécutez techniquement la même requête -

schema.post('findOneAndUpdate', function(result) {
    this.update({}, {
        totalNumberOfComments: result.comments.length
    }).exec();
}));

est essentiellement le même que

var query = Post.findOneAndUpdate({_id: fj394hri3hfj}, {$push: {comments: myNewComment}});
query.update({}, {
        totalNumberOfComments: result.comments.length
    }).exec();
query.findOneAndUpdate().exec();

Si vous souhaitez créer une nouvelle requête à partir de zéro, faites simplement

schema.post('findOneAndUpdate', function(result) {
    this.model.update({}, { // <--- `this.model` gives you access to the `Post` model
        totalNumberOfComments: result.comments.length
    }).exec();
}));

ne touchez pas à votre crochet de pré-sauvegarde,

router.put('/:id', jsonParser, function(req, res, next) {

  currentCollection.findByIdAndUpdate(req.params.id, req.body, function (err, item) {
    if (err) {
        res.status(404);
        return res.json({'error': 'Server Error', 'trace': err});
    }
    item.save(); // <=== this is were you save your data again which triggers the pre hook :)
    res.status(200); 
    return res.json({'message': 'Saved successfully'});
  });
});

J'ai trouvé que l'ordre dans lequel vous définissez un modèle et définissez un hook pre est important. Permettez-moi de démontrer :

Ne marche pas:

// Create Model
let model = Database.Connection.model(`UserModel`, this._schema, `users`);

// Attach Pre Hook
this._schema.pre(`findOneAndUpdate`, function(next) {
    console.log('pre update');
    return next();
});

Fonctionne :

// Attach Pre Hook
this._schema.pre(`findOneAndUpdate`, function(next) {
    console.log('pre update');
    return next();
});

// Create Model
let model = Database.Connection.model(`UserModel`, this._schema, `users`);

J'espère que cela aide quelqu'un!

Je viens de découvrir la même chose que @nicky-lenaers.

Cela fonctionne très bien avec 'safe' . 'delete' . etc. si vous définissez les crochets après la définition du modèle.

Existe-t-il une solution de contournement pour définir un crochet 'findOneAndUpdate' après la définition du modèle ?

@albert-92 non pas pour le moment

Pour tous ceux qui essaient d'obtenir quelque chose qui ressemblait à

SCHEMA.pre('validate', function(done) {
    // and here use something like 
    this.yourNestedElement 
    // to change a value or maybe create a hashed character    
    done();
});

Cela devrait fonctionner

SCHEMA.pre('findOneAndUpdate', function(done){
    this._update.yourNestedElement
    done();
});

Je ne peux pas obtenir de crochets de publication pour mettre à jour le document dans la collection.

`module.exports = fonction (mangouste) {
var monSchéma = mangouste.Schéma({
id : { type : nombre, index : { unique : vrai } },
champ1 : { type : chaîne },
champ2 : { type : chaîne}
}, {
collection : "monSchéma",
versionKey : false
});

mySchema.post('findOneAndUpdate', function (result) {
    this.model.update({}, {
        field2: 'New Value'
    }).exec();
});
return mySchema;

}`

mySchema.findOneAndUpdate({id: 1}, {field1: 'test'}, {new: true});

Définit le champ de la collection sur { id:1, field1: 'test' ) mais doit être {id: 1, field1: 'test', field2:'New Value'}
Je ne sais pas ce que je fais mal

Je peux changer le résultat de findOneAndUpdate en faisant ceci
mySchema.post('findOneAndUpdate', function (result) { result.field2 = 'something' });

Je pense que vous essayez peut-être de mettre à jour le modèle avec un élément qui existe déjà sur le modèle. Ou peut-être que vous le sélectionnez mal. Essayez d'imprimer "ceci" dans votre mySchema.post. De plus, vous ne semblez pas avoir de done() ou next() dans votre message. Je ne connais pas très bien le sujet, mais je sais que l'imprimer vous donnera au moins une idée de ce à quoi vous avez affaire.

Le but de la mise à jour n'est-il pas de modifier un document existant dans votre modèle ?

ceci est un objet de requête

Vous n'avez pas besoin de faire ou de suivre les crochets de poste pour autant que je sache.

Eh bien, vous avez this.model.update qui est le schéma et non le modèle de l'objet. Je pense .. Ce qui signifie que vous devrez utiliser

mySchema.post('findOneAndUpdate', function (result) {
    this.model.update({}, {
        $set: { field2: 'New Value'}
    }).exec();
});
return mySchema;

Cela semble un peu en arrière pour appeler une fonction de modèle à l'intérieur du modèle. Puisque vous pouvez simplement utiliser des parties de l'objet "this" qui vous est donné. vous feriez peut-être mieux d'utiliser simplement findOneAndUpdate au lieu de l'appeler, puis d'appeler une autre fonction de modèle par-dessus.
chez un gestionnaire

var data = yourNewData;
self.findOneAndUpdate({id: something._id}, data, {safe: false, new: true})
    .exec()
    .then(resolve)
    .catch(reject);

Dans mon exemple ci-dessus, j'ai utilisé this._update car c'était l'objet de mise à jour qui devait être utilisé à partir de cela.

J'ai essayé d'utiliser $set. Cela ne change toujours pas mon document dans la collection.

Où puis-je trouver tous les crochets pré et post disponibles ?

+1

Cette page vous a été utile?
0 / 5 - 0 notes