Mongoose: Pre-, Post-Middleware wird nicht auf findByIdAndUpdate ausgeführt

Erstellt am 15. Juni 2012  ·  102Kommentare  ·  Quelle: Automattic/mongoose

Weil eine findAndUpdate-Methode präsentiert wird, um den folgenden Code zu reduzieren:

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

dazu:

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

Wir müssen Pre- und Post-Middleware genau gleich verwenden. Zurzeit wird die Post-Middleware nicht ausgeführt, wenn ich findByIdAndUpdate mache.

Hilfreichster Kommentar

Hallo,

Ich weiß, dass das Thema geschlossen ist, aber ich bin mit der Antwort nicht ganz zufrieden. Sollte es nicht zumindest eine Post-Save- oder Post-Update-Middleware für findOneAndUpdate und andere ähnliche Operationen geben? Scheint in Ordnung zu sein, da das Dokument zurückgegeben wird. Nicht machbar für Pre-Middlewares oder für Model.update, stimme ich zu.

Es würde die Fähigkeiten von Plugins wie mongoosastic, das derzeit blind für einige Operationen ist, die es unterstützen sollte, erheblich verbessern.

Wenn keine Middleware vorhanden ist, hat jemand eine Idee, wie einige Post-Update-Vorgänge in einem Plugin verwaltet werden können?

Danke

Alle 102 Kommentare

von Entwurf. Es gibt Dokumente, an denen Hooks aufgerufen werden können.

Korrektur, es gibt keine Dokumente, auf denen Hooks aufgerufen werden können.

So? Wenn ich Pre-, Post-Middleware aufrufen möchte, muss ich den ersten Ansatz verwenden?

ja, das ist richtig. Model.update, findByIdAndUpdate, findOneAndUpdate, findOneAndRemove, findByIdAndRemove sind alle Befehle, die direkt in der Datenbank ausgeführt werden.

Dies sollte auf jeden Fall im Leitfaden geklärt werden, insbesondere wenn Sie auf derselben Seite über die Validierung sprechen und findByIdAndUpdate als "besser" beschreiben.

Wenn Sie auf den Link "Besser" klicken, gelangen Sie zur vollständigen Dokumentation von
die Methode, wo es die Validierung usw. erklärt.

Fühlen Sie sich frei, eine Pull-Anfrage zu senden, um etwas hinzuzufügen, das Sie für besser halten. es ist
geht ganz einfach:
https://github.com/LearnBoost/mongoose/blob/master/CONTRIBUTING.md

Am Mittwoch, den 31. Oktober 2012 um 18:03 Uhr schrieb Jesse Fulton [email protected] :

Dies sollte unbedingt in der Anleitung http://mongoosejs.com/docs/documents.html geklärt werden.
vor allem, wenn Sie auf derselben Seite über die Validierung sprechen und
findByIdAndUpdate als "besser" beschreiben


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHubhttps://github.com/LearnBoost/mongoose/issues/964#issuecomment -9967865 an.

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

Hinweise zum Middleware-Dokument hinzugefügt

Pull-Request: https://github.com/LearnBoost/mongoose/pull/1750

Hallo,

Ich weiß, dass das Thema geschlossen ist, aber ich bin mit der Antwort nicht ganz zufrieden. Sollte es nicht zumindest eine Post-Save- oder Post-Update-Middleware für findOneAndUpdate und andere ähnliche Operationen geben? Scheint in Ordnung zu sein, da das Dokument zurückgegeben wird. Nicht machbar für Pre-Middlewares oder für Model.update, stimme ich zu.

Es würde die Fähigkeiten von Plugins wie mongoosastic, das derzeit blind für einige Operationen ist, die es unterstützen sollte, erheblich verbessern.

Wenn keine Middleware vorhanden ist, hat jemand eine Idee, wie einige Post-Update-Vorgänge in einem Plugin verwaltet werden können?

Danke

@albanm bestimmte Methoden umgehen Mongoose vollständig , sodass Sie die Middleware-Hooks nicht erhalten. AFAIK, die einzige Möglichkeit, die Hooks zur Ausführung zu bringen, besteht darin, wie oben erwähnt, separate find() - und save() -Aufrufe zu verwenden.

Ich verstehe das und es macht Sinn. Pre-Middlewares kommen nicht in Frage, da vor bestimmten Methoden kein Holen stattfindet. Trotzdem sollte Mongoose in der Lage sein, die Aktualisierungsoperationen einzuschließen, die auch die aktualisierten Dokumente zurückgeben und einige Post-Hooks auslösen.

Ich denke, @albanm hat recht, die Leute wollen vielleicht die gleiche Funktionalität, wenn sie die gleiche Funktion verwenden. Wie wäre es, wenn Sie diese „direkt aktualisieren“-Methoden mit etwas Abfangen umschließen, um zu überprüfen, ob Hooks vorhanden sind? Wenn ein Hook vorhanden ist, verwenden Sie ihn oder rufen Sie andernfalls die ursprünglichen Aktualisierungsmethoden auf.

+1

+1

+1

+1

:+1:

+1

:-1:
Ich bekomme die Feature-Anfrage, aber je länger ich mit Middleware arbeite, muss ich Middleware oft umgehen, wenn ich db-Update-Skripte und andere Elemente aufrufe, die für meine normalen Operationen nicht routinemäßig sind. Durch die Verwendung statischer Methoden wird es sehr einfach, einen benutzerdefinierten Wrapper zu erstellen, der die oben genannten Funktionsanforderungen bereits implementiert. Da sich Mongoose jetzt Ideen für Version 4 öffnet, ist es unwahrscheinlich, dass eine API-Änderung dieser Größenordnung in v3 erfolgen wird. Ich fordere, dass dies in die v4-Diskussion verschoben wird.

Ich würde jedoch :+1: wenn ich die Middleware bei einem Save-Call deaktivieren könnte. Ich finde mich oft mit einem Mungo-Objekt wieder, das von einer anderen Funktion übergeben wurde, und möchte einfach eine .save-Datei ausführen - in dieser Situation ist eine .save-Datei besser als das Schreiben einer neuen Abfrage. Wenn dies möglich ist, weisen Sie bitte darauf hin

So oder so, tolle Bibliothek. Ein großes Lob an die großartigen Betreuer. Ich bin mir nicht sicher, wie ich ohne sie arbeiten würde.

+1

Eine einfache Möglichkeit, Hooks für diese Methode hinzuzufügen, besteht darin, die Embed-Funktionen zu überschreiben:

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

Dann wird jeder Aktualisierungsaufruf von Mongoose den aktualisierten Datenschlüssel reparieren.

Sie können dieses Modell Limbo ausprobieren. Es ist ein einfacher Wrapper des Mongoose-Modells, der bind static/method/overwrite an alle Schemas unterstützt und RPC-Methoden aufruft, um die mongodb abzufragen.

+1, brauche diese Funktion ...

Während ich die Argumentation irgendwie verstehe, macht das Fehlen von Haken bei atomaren Updates meiner Meinung nach Mongoose in der Praxis etwas sinnlos. Wenn ich atomare Updates verwende, werden keine Validierungen, Standardeinstellungen usw. ausgeführt, sodass der gesamte Zweck der Verwendung eines ODM zunichte gemacht wird. Die Verwendung von find/save wird die Arbeit erledigen, aber gibt es eine Garantie, dass dies immer verwendet wird?

Außerdem würde ich normalerweise versuchen, find/save zu vermeiden, da es sich nicht um eine atomare Operation handelt. MongoDB gleicht den Mangel an Transaktionsunterstützung aus, indem es leistungsstarke atomare Abfrage- und Aktualisierungsfunktionen bereitstellt. Also würde ich diese atomaren Operationen verwenden, aber ohne Middleware-Unterstützung wird Mongoose nicht viel Wert gegenüber dem nativen MongoClient bieten.

Sogar die Beispiele in http://aaronheckmann.tumblr.com/post/48943525537/mongoose-v3-part-1-versioning würden update verwenden und somit Middleware umgehen. Ich kann Versionierung oder Middleware richtig verwenden, aber nicht beides kombinieren? Wirklich, wo ist der Sinn, es zu haben?

Ich verstehe nicht einmal die technischen Gründe ganz: Wenn update & co. Um die Datenbankoperationen herumführen, warum können wir den Aufruf nicht abfangen und die Abfrageobjekte weitergeben, damit wir einige Validierungen/Anpassungen vornehmen können, bevor wir die Aktualisierung tatsächlich durchführen?

@joerx +1 würde ausreichen .. :) aber deine Argumentation ist einwandfrei.

Der 3.9.x-Zweig unterstützt Pre- und Post-Hooks für find und findOne - es sollte einfach sein, Unterstützung für findOneAndUpdate und update hinzuzufügen.

Ist diese Funktion zusammengeführt?

Die Hooks pre('findOneAndUpdate') und post('findOneAndUpdate') befinden sich also im Master, es gibt noch keinen Update-Hook. Es gibt noch keine Version, die beides enthält.

Löst es jetzt nach .update () Pre-Save aus?

Nein. Es gibt einen separaten update() Hook für Query.update() . Sicherungs-Hooks unterscheiden sich von Abfrage-Hooks.

@vkarpov15 Können Sie bitte auf die unterstützende Dokumentation für den Update-Hook verlinken? Irgendwelche Informationen darüber, wann pre('findOneAndUpdate') und post('findOneAndUpdate') veröffentlicht werden?

@karlstanton verwende 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

Es scheint, als wäre es ein Schritt hinterher. Was vermisse ich?

AH, das new -Flag standardmäßig Breaking Change. Während es etwas über die Konsistenz mit dem zugrunde liegenden Treiber zu sagen gibt, muss ich sagen, dass es wirklich kontraintuitiv ist, besonders wenn man diesen neuen Haken betrachtet.

@neverfox :)

Ja @neverfox guter Fang. Diese Änderung ist in den Versionshinweisen dokumentiert und in #2262 finden Sie weitere Erläuterungen dazu, warum dies geändert wurde.

Der Grund dafür ist, dass new im mongodb-Knotentreiber, der mongodb-Shell, dem tatsächlichen mongodb-Server usw. standardmäßig falsch ist, und Wrapper-Layer, die nicht standardmäßige Standardwerte festlegen, erschweren Entwicklern das Leben. Mein kanonisches Beispiel für ein Modul, das dies schrecklich falsch macht, ist gulp-uglify , das eine Reihe von Standardeinstellungen von uglify-js überschreibt.

Ich sehe, dass das Problem geschlossen ist, aber ist die Funktionalität in Version 4.0.2 vorhanden, ist sie nur noch in der instabilen Version vorhanden? Es scheint nicht auf findOneAndUpdate mit Scema.pre('update') oder Schema.pre('findOneAndUpdate') mit der Version 4.0.2 ausgeführt zu werden. Übersehe ich etwas, das ich an die Funktion übergeben muss?

Wie deklarierst du den Pre-Hook @CaptainStaplerz ?

Ist findOneAndUpdate-Middleware-Hook in 4.0.2 verfügbar? Ich habe von 3.8 auf den neuesten Mungo 4.0.2 aktualisiert, um dies zu verwenden, und die Middlware Document.schema.post('findOneAndUpdate', function (doc) wird nicht wie save() oder remove() ausgelöst

@honitus zeig mir deinen Code

@vkarpov15 - Danke für die schnelle Antwort, bitte schön

blog.controller,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 Danke für die Wiedereröffnung. Bedeutet dies, dass der Hook für findOneAndUpdate in 4.0.2 nicht vorhanden ist und Sie planen, ihn in 4.0.3 aufzunehmen.

@ vkarpov15 Hier ist der Code, in dem ich die Hooks deklariere:

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

Und hier nenne ich das Update:

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

Soweit ich weiß, sollte der Hook findOneAndUpdate() dort drin sein, wenn es nicht funktioniert, ist das ein Fehler

@CaptainStaplerz versuchen:

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

Wird auch die console.log -Anweisung ausgeführt oder liefert nur der Date.now()-Teil unerwartete Ergebnisse?

@vkarpov15
Ich habe meinen Quellcode geändert und die Änderungen hinzugefügt, die Sie in Implementierung hinzufügen #964 https://github.com/Automattic/mongoose/commit/e98ef98e857965c4b2ae3339fdd7eefd2a5a9913 vorgenommen haben

Es funktioniert jetzt wie ein Zauber. Ich denke also, dass das Update nicht in main eingecheckt ist

@honitus bist du sicher, dass du Mongoose 4.0.2 verwendest? Diese Änderung wird tatsächlich für 4.0.0 und höher übernommen.

honitus$ npm View-Mungo-Version
4.0.2

@honitus Was Sie falsch machen, ist, dass Sie dem Schema Hooks hinzufügen, nachdem Sie Ihr Modell kompiliert haben. Model.schema.pre('remove'); wird voraussichtlich nicht funktionieren, siehe „Erstes Modell kompilieren“ in der Modelldokumentation . Hängen Sie zuerst die Hooks an das Schema an und dann sollten die Dinge funktionieren - das ist der einzige Unterschied, den ich zwischen Ihrem Code und unseren Tests sehe.

@CaptainStaplerz Die einzige Möglichkeit, Ihren Code zu reproduzieren, ist ein leeres Update. Der folgende Code funktioniert

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

Aber sobald Sie das Update leer machen:

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

Der Pre-Update-Hook wird nicht mehr ausgeführt. Stimmt das mit dem überein, was Sie sehen?

@vkarpov15
Model.schema.pre('entfernen'); ist einer der Stubs, die automatisch von Angular Full Stack erstellt werden
Ich habe die Implementierung #964 e98ef98 hinzugefügt
Ich habe nur eine Zeile darunter hinzugefügt
this._schema.s_.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,
Siehe Zeile 1526 in 4.0.2 - this.model.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,

Ersetzen Sie Zeile 1526 durch this.schema.s.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,
und es funktioniert.

Das ist beabsichtigt. Mongoose soll es nicht erlauben, die Schema-Hooks zu modifizieren, nachdem das Modell kompiliert wurde (also der mongoose.model() -Aufruf), weshalb das nicht mehr funktioniert.

@vkarpov15 Ich habe es geschafft, den Haken jetzt mit dem Folgenden zum Laufen zu bringen, es muss ein dummer Tippfehler oder etwas anderes gewesen sein:

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

'this' scheint sich jedoch nicht auf das aktualisierte Modell zu beziehen (aber im 'save'-Hook?), wodurch this.updatedAt auf undefined verweist.

Wie aktualisiere ich das 'updatedAt'-Feld im 'findOneAndUpdate'-Hook?

Ich sollte weitere Dokumente hinzufügen, die dies verdeutlichen - das Dokument, das aktualisiert wird, ist möglicherweise nicht im Speicher vorhanden, wenn Sie findOneAndUpdate aufrufen, sodass sich das Objekt this auf die Abfrage und nicht auf das Dokument in der Abfrage-Middleware bezieht. Versuchen Sie es this.update({ $set: { updatedAt: Date.now() } });

@vkarpov15 Ich konnte this.update({ $set: { updatedAt: Date.now() } }); nicht zum Laufen bringen, aber ich konnte das Dokument auf dem Haken findOneAndUpdate erfolgreich aktualisieren: this._update['$setOnInsert'].updatedAt=Date.now(); .

Ich würde dringend empfehlen, sich nicht auf solche Anpassungen des internen Zustands zu verlassen. Ich bin ziemlich überrascht, dass Sie this.update() nicht zum Laufen bringen konnten - können Sie mir zeigen, wie Ihr Haken aussieht?

Sicher! (Beachten Sie, dass ich 4.0.2 verwende)

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

Ich könnte das natürlich handhaben, wenn ich das Dokument an anderer Stelle in meiner App initialisiere, aber es ist schön, dass alles direkt in Mongoose enthalten ist. Begrüßen Sie alle Gedanken!

Ja, ich war verwirrt und dachte, Sie würden update verwenden. Das folgende Skript

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

Funktioniert korrekt und führt die gewünschte Abfrage aus:

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

Also bitte verwenden

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

Anstatt den internen Zustand von mquery manuell zu manipulieren, ist das normalerweise eine schlechte Idee, es sei denn, Sie wissen wirklich, was Sie tun.

Funktioniert das oder nicht? Der einzige Haken, der für mich funktioniert, ist "Speichern", der Rest wird vollständig ignoriert. Ich laufe auf 4.0.6. Danke

@agjs geben Sie bitte ein Codebeispiel an.

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

});

Ich habe einen weiteren Pre-Hook auf einem anderen Modell mit model.create und pre-save und es funktioniert normal.

Bei jedem Update sieht es den Hook einfach nicht, nicht einmal console.log es. Ich habe es auch mit findOneAndUpdate usw. versucht, funktioniert nicht wirklich ... Komischerweise bin ich diesen ganzen Thread durchgegangen und habe, wie ich es normalerweise tue, die offizielle Dokumentation überprüft, und Sie behaupten sogar dort, dass es funktioniert.

Welche Beziehung besteht zwischen CompanyAvatarSchema und CompanyDetails im obigen Code?

Firmendetails haben CompanyAvatar.schema als untergeordnetes Dokument

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

Außerdem wird nicht nur der Pre-Hook, sondern auch die Validierung vollständig ignoriert. Dieses Filialdokument wird aufgefüllt, ignoriert aber sowohl die Validierung als auch den Pre-Hook. Ich habe alles gegoogelt, auch DAS versucht, aber nichts scheint zu funktionieren. Wenn ich nur zum Testen meine Abfrage ändere, um ein Modell mit new aka var parent = new Parent() zu erstellen und aufzurufen, funktioniert es.

Sie rufen CompanyDetails.update() , aber der Pre-Hook ist in einem separaten Schema definiert. Die Abfrage-Middleware löst den pre('update') atm des verschachtelten Schemas nicht aus.

Bitte geben Sie auch ein ausführlicheres Codebeispiel für Ihren Fall „Validierung wird vollständig ignoriert“ an.

Hier ist mein Unternehmens-Avatar-Schema, das zur Validierung und Vorab-Hooks für Benutzer erstellt wurde, die ihr Profil aktualisieren (Avatar-Foto):

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

Hier ist das company_details-Schema, in dem company_avatar ein Unterdokument ist:

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],

    }
});

Und hier ist der Update-Profil-Controller und der avatarPath, der validiert/eingehängt werden sollte, bevor dieses Update durchgeführt wird:

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

};

Grundsätzlich wird meine mongodb mit den Feldern aus req.files.file gefüllt, aber ansonsten wird die Validierung ignoriert und es funktionieren keine Hooks.

Das Problem besteht darin, dass die pre('findOneAndUpdate')-Middleware auf einem verschachtelten Schema definiert ist. Momentan löst Mongoose nur Abfrage-Middleware für Top-Level-Schemas aus, sodass Middlewares, die auf CompanyDetailsSchema definiert sind, wegen #3125 gefeuert werden

+1

@ vkarpov15 Das verstehe ich

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

funktioniert, um eine Eigenschaft auf einen harten Wert zu setzen, aber wie würde ich eine Eigenschaft _lesen und ändern_, zB ein Passwort hashen?

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

Irgendwelche Gedanken? Dies ist ein ziemlich häufiger Anwendungsfall, oder? Oder finden die Leute normalerweise() und trennen dann save() ?

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

Das sollte funktionieren @willemmulder.

@vkarpov15 perfekt, danke! Ich werde das heute Abend versuchen. Das sollte auch für pre('update') funktionieren, oder?

@ vkarpov15 Also habe ich es gerade mit versucht

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

und wenn ich console.log this bekomme ich

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

das scheint in Ordnung zu sein (und ich habe sogar versucht, diese Eigenschaft direkt vorher zu setzen), aber am Ende schreibt es nicht wirklich den Hash-Wert in die Datenbank, sondern einfach den 'rohen' Wert. Gibt es etwas, das ich versuchen könnte?

Das ist seltsam. Sie können versuchen, den Mongoose-Debug-Modus mit require('mongoose').set('debug', true); zu aktivieren und zu sehen, welche Abfrage an die Datenbank gesendet wird, die etwas Licht ins Dunkel bringen könnte.

Danke für den Vorschlag. Habe gerade das gemacht:

Ich führe das aus:

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

was dies für die Datei console.log zurückgibt

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

und dann dies für den Mongoose-Debug

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

Irgendeine Ahnung?

Ich bin mir nicht sicher, habe ein neues Problem zum Nachverfolgen geöffnet.

@ vkarpov15 Danke, werde das andere Problem verfolgen.

@ vkarpov15 Ich denke, der richtige Weg, Optionen für die laufende Abfrage in Pre-Hook festzulegen, wäre ungefähr so:

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

aber die Dokumentation http://mongoosejs.com/docs/api.html#query_Query -setOptions erwähnt keine dieser Optionen. Wenn dies als Hacking-Lösung angesehen wird, was wäre angemessener?

Das ist ein Problem mit den Dokumenten, der von Ihnen beschriebene Code sieht so aus, als ob er auf den ersten Blick funktionieren sollte

Kannst du dafür ein separates Thema aufmachen?

@ vkarpov15 Ja, es funktioniert. Ich glaube, ich war nicht klar genug.

setOptions wendet new und runValidators korrekt an, ich habe lediglich gefragt, ob das Festlegen dieser Optionen über setOptions gegenüber this.options bevorzugt werden sollte.

setOptions() ist IMO vorzuziehen, aber beide sollten funktionieren. Oder du könntest es einfach tun

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

Dadurch wird das Passwort bei jedem Aufruf von update() aktualisiert. Wenn ich also nur den Wert anderer Eigenschaften ändere, dh Name oder Alter, wird das Passwort ebenfalls aktualisiert, was nicht korrekt ist!?

@nlonguit Ich denke, das wird es. Aber Sie können über this die Felder zugreifen, die aktualisiert werden, und Sie könnten so etwas tun:

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

Dieser Code funktioniert gut für mich. Danke @akoskm

Ich frage mich, ob es möglich wäre, auch einen Pre-Hook für findByIdAndUpdate hinzuzufügen. Wäre schön, beide Haken zur Verfügung zu haben.

Ich habe es so gemacht und es funktioniert: Einfach findById, dann speichern, ohne Felder zu aktualisieren, dann die Methode findByIdAndUpdate verwenden:

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

Ich versuche, eine Eigenschaft so einzustellen, dass sie die Länge eines Arrays hat.

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

Die korrekte Länge wird protokolliert, obwohl die Abfrage totalNumberOfComments nie festlegt und das Feld auf 0 bleibt (da die Schemareferenzen standardmäßig auf 0 verweisen).

Wenn ich am Ende des Hakens console.log(this) sage, kann ich sehen, dass mein query Folgendes enthält:

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

Wenn ich den Debug-Modus einschalte, wird jedoch nie eine Abfrage von Mongoose protokolliert.

Mache ich etwas falsch, oder ist das ein Bug?

@zilions this.findOneAndUpdate({}, { totalNumberOfComments: result.comments.length }).exec(); muss die Abfrage tatsächlich ausführen :) Seien Sie vorsichtig, Sie erhalten dort eine unendliche Rekursion, da Ihr Post-Save-Hook einen anderen Post-Save-Hook auslöst

@vkarpov15 Ahhhh richtig! Dann kann ich stattdessen einfach this.update({} {....}).exec() verwenden :)
Frage jedoch, wenn Sie dies verwenden, wird das Feld totalNumberOfComments perfekt eingestellt, obwohl auch die ursprüngliche Aktualisierung von findOneAndUpdate durchgeführt wird.

Beispielsweise:

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

Löst den folgenden Hook aus:

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

Allerdings wird der Hook $push zu comments wieder zu myNewComment machen, wodurch ein doppelter Eintrag entsteht.

Da Sie technisch gesehen dieselbe Abfrage ausführen -

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

ist im Wesentlichen dasselbe wie

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

Wenn Sie eine neue Abfrage von Grund auf neu erstellen möchten, tun Sie dies einfach

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

berühren Sie einfach nicht Ihren Pre-Save-Hook,

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

Ich habe festgestellt, dass die Reihenfolge wichtig ist, in der Sie ein Modell definieren und einen pre -Hook definieren. Lassen Sie mich demonstrieren:

Funktioniert nicht:

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

Funktioniert:

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

Hoffe, das hilft jemandem!

Ich habe gerade dasselbe herausgefunden wie @nicky-lenaers.

Es funktioniert gut mit 'safe' . 'delete' . usw., wenn Sie die Hooks definieren, nachdem das Modell definiert wurde.

Gibt es eine Problemumgehung, die einen 'findOneAndUpdate' -Hook definiert, nachdem das Modell definiert wurde?

@albert-92 nein im Moment nicht

Für alle, die versuchen, etwas zu bekommen, das früher so war

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

Das sollte funktionieren

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

Ich kann keine Post-Hooks erhalten, um das Dokument in der Sammlung zu aktualisieren.

`module.exports = function (Mungo) {
var meinSchema = mongoose.Schema({
id: { Typ: Zahl, Index: { eindeutig: wahr } },
Feld1: { Typ: String },
Feld2: {Typ: Zeichenfolge}
}, {
Sammlung: "mySchema",
Versionsschlüssel: falsch
});

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

}`

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

Setzt das Feld in der Sammlung auf { id:1, field1: 'test' ), sollte aber {id: 1, field1: 'test', field2:'New Value'} sein
Nicht sicher, was ich falsch mache

Ich kann das Ergebnis von findOneAndUpdate ändern, indem ich dies tue
mySchema.post('findOneAndUpdate', function (result) { result.field2 = 'something' });

Ich denke, es könnte sein, dass Sie versuchen, das Modell mit einem Element zu aktualisieren, das bereits im Modell vorhanden ist. Oder vielleicht wählst du es falsch aus. Versuchen Sie, "this" in Ihrer mySchema.post auszudrucken. Außerdem scheinen Sie in Ihrem Beitrag kein done() oder next() zu haben. Ich kenne mich mit dem Thema nicht sehr gut aus, aber ich weiß, dass das Ausdrucken dieses Dokuments Ihnen zumindest eine Vorstellung davon geben wird, womit Sie es zu tun haben.

Ist der Aktualisierungspunkt nicht, ein vorhandenes Dokument in Ihrem Modell zu ändern?

Dies ist ein Abfrageobjekt

Soweit ich weiß, brauchen Sie in Post-Hooks keine Done- oder Next-In-Post-Hooks.

Nun, Sie haben this.model.update, das das Schema ist, nicht das Modell des Objekts. Ich denke ... was bedeutet, dass Sie verwenden müssten

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

Dies scheint ein wenig rückständig zu sein, um eine Modellfunktion innerhalb des Modells aufzurufen. Da Sie einfach Teile des "this"-Objekts verwenden könnten, das Ihnen gegeben wird. Möglicherweise ist es besser, nur findOneAndUpdate zu verwenden, anstatt es aufzurufen und dann eine andere Modellfunktion darüber aufzurufen.
bei einem Manager

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

In meinem obigen Beispiel habe ich this._update verwendet, da dies das Update-Objekt war, das von diesem verwendet werden musste.

Ich habe es mit $set versucht. Mein Dokument in der Sammlung wird immer noch nicht geändert.

Wo finde ich alle verfügbaren Pre- und Post-Hooks?

+1

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen