Mongoose: pre, post middleware no se ejecutan en findByIdAndUpdate

Creado en 15 jun. 2012  ·  102Comentarios  ·  Fuente: Automattic/mongoose

Porque se presenta un método findAndUpdate para reducir el siguiente código:

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

a esto:

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

Necesitamos usar pre, post middleware exactamente igual. En este momento, el middleware posterior no se ejecuta cuando hago findByIdAndUpdate.

Comentario más útil

Hola,

Sé que el problema está cerrado, pero no estoy del todo satisfecho con la respuesta. ¿No debería haber al menos un middleware posterior al guardado o posterior a la actualización para findOneAndUpdate y otras operaciones similares? Parece estar bien ya que se devuelve el documento. No factible para pre middlewares o para Model.update, estoy de acuerdo.

Mejoraría en gran medida las capacidades de complementos como mongoosastic que actualmente no reconoce algunas operaciones que debería poder admitir.

Si no hay middleware, ¿alguien tiene una idea sobre cómo administrar algunas operaciones posteriores a la actualización en un complemento?

Gracias

Todos 102 comentarios

por diseño. hay documentos involucrados para llamar ganchos.

Corrección, no hay documentos para llamar ganchos.

¿Entonces? Si quiero llamar a pre, post middleware, ¿necesito usar el primer enfoque?

si, eso es correcto. Model.update,findByIdAndUpdate,findOneAndUpdate,findOneAndRemove,findByIdAndRemove son todos los comandos que se ejecutan directamente en la base de datos.

Esto definitivamente debería aclararse en la guía , especialmente si está hablando de validación en esa misma página y describe findByIdAndUpdate como "mejor"

si hace clic en el enlace "mejor" le lleva a la documentación completa de
el método donde explica la validación, etc.

siéntete libre de enviar una solicitud de extracción para agregar algo que creas que es mejor. su
bastante fácil de hacer así:
https://github.com/LearnBoost/mongoose/blob/master/CONTRIBUTING.md

El miércoles, 31 de octubre de 2012 a las 6:03 p. m., Jesse Fulton [email protected] escribió:

Esto definitivamente debería aclararse en la guía http://mongoosejs.com/docs/documents.html ,
especialmente si estás hablando de validación en esa misma página y
describiendo findByIdAndUpdate como "mejor"


Responda a este correo electrónico directamente o véalo en Gi tHubhttps://github.com/LearnBoost/mongoose/issues/964#issuecomment -9967865.

Aarón
@aaronheckmann https://twitter.com/#!/aaronheckmann

Se agregaron notas al documento de middleware.

Solicitud de extracción: https://github.com/LearnBoost/mongoose/pull/1750

Hola,

Sé que el problema está cerrado, pero no estoy del todo satisfecho con la respuesta. ¿No debería haber al menos un middleware posterior al guardado o posterior a la actualización para findOneAndUpdate y otras operaciones similares? Parece estar bien ya que se devuelve el documento. No factible para pre middlewares o para Model.update, estoy de acuerdo.

Mejoraría en gran medida las capacidades de complementos como mongoosastic que actualmente no reconoce algunas operaciones que debería poder admitir.

Si no hay middleware, ¿alguien tiene una idea sobre cómo administrar algunas operaciones posteriores a la actualización en un complemento?

Gracias

@albanm ciertos métodos omiten la mangosta por completo , por lo que no obtiene los ganchos de middleware. AFAIK, la única manera de hacer que los ganchos se ejecuten es usar llamadas separadas find() y save() como se mencionó anteriormente.

Lo entiendo y tiene sentido. Los pre-middlewares están fuera de discusión ya que no hay recuperación antes de ciertos métodos. Pero aún así, mongoose debería poder envolver las operaciones de actualización que también devuelven los documentos actualizados y activan algunos enlaces de publicación.

Creo que @albanm tiene razón, las personas pueden querer la misma funcionalidad cuando usan la misma función. ¿Qué tal envolver esos métodos de 'actualización directa' con alguna intercepción de verificación de que si existe algún gancho? Si existe un enlace, utilícelo o llame a los métodos de actualización originales de lo contrario.

+1

+1

+1

+1

:+1:

+1

:-1:
Recibo la solicitud de función, pero cuanto más tiempo trabajo con el middleware, me veo en la necesidad de omitir el middleware a menudo cuando llamo a los scripts de actualización de db y otros elementos que no son rutinarios para mis operaciones normales. Al usar métodos estáticos, se vuelve muy fácil crear un contenedor personalizado que ya implemente las solicitudes de funciones anteriores. Dado que mongoose ahora se está abriendo a ideas para la versión 4, es poco probable que ocurra algún cambio de API de esta magnitud en la v3. Solicito que esto se mueva a la discusión v4.

Sin embargo, :+1: si pudiera deshabilitar el middleware en una llamada guardada. A menudo me encuentro con un objeto mangosta que se pasó desde otra función y simplemente quiero realizar un .save; en esta situación, hacer un .save es preferible a escribir una nueva consulta. Si esto es posible, por favor indíquelo.

De cualquier manera, increíble biblioteca. Felicitaciones a los mantenedores increíbles. No estoy seguro de cómo operaría sin él.

+1

Una forma sencilla de agregar ganchos para este método es sobrescribir las funciones de inserción:

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

Luego, cada llamada de actualización de mangosta arreglará la clave de datos actualizada.

Puede probar este modelo de limbo . Es un envoltorio simple del modelo mongoose, compatible con bind static/method/overwrite a todos los esquemas, y llamar a métodos rpc para consultar mongodb.

+1, necesito esta función...

Si bien entiendo el razonamiento, la falta de ganchos en las actualizaciones atómicas en mi humilde opinión hace que Mongoose no tenga sentido en la práctica. Cuando uso actualizaciones atómicas, no se ejecutan validaciones, valores predeterminados, etc., por lo que se anula todo el propósito de usar un ODM. Usar buscar/guardar hará el trabajo, pero ¿hay alguna garantía de que esto siempre se use?

Además, normalmente trataría de evitar buscar/guardar ya que no es una operación atómica. MongoDB compensa su falta de soporte de transacciones al proporcionar potentes funciones de actualización y consulta atómica. Por lo tanto, usaría estas operaciones atómicas, pero sin soporte de middleware, Mongoose no proporcionará mucho valor sobre el MongoClient nativo.

Incluso los ejemplos en http://aaronheckmann.tumblr.com/post/48943525537/mongoose-v3-part-1-versioning usarían update , por lo tanto, omitirían el middleware. ¿Puedo usar el control de versiones correctamente o el middleware pero no combinar ambos? Realmente, ¿cuál es el punto en tenerlo?

Ni siquiera entiendo completamente las razones técnicas: si update & co. ajustar las operaciones de la base de datos, ¿por qué no podemos interceptar la llamada y pasar los objetos de consulta para que podamos hacer alguna validación/personalización antes de que realmente hagamos la actualización?

@joerx +1 sería suficiente... :) pero tu razonamiento es perfecto.

La rama 3.9.x tiene soporte para ganchos previos y posteriores para find y findOne ; debería ser fácil agregar soporte para findOneAndUpdate y update .

¿Esta función está fusionada?

Entonces, los ganchos pre('findOneAndUpdate') y post('findOneAndUpdate') están en maestro, todavía no hay un gancho de actualización. Todavía no hay ningún lanzamiento que contenga ninguno de esos.

Entonces, ¿activa el guardado previo después de .update() ahora?

No. Hay un gancho update() separado para Query.update() . Los ganchos de guardado son distintos de los ganchos de consulta.

@ vkarpov15 ¿Puedes vincular a la documentación de apoyo para el gancho de actualización? ¿Algún comentario sobre cuándo pre('findOneAndUpdate') y post('findOneAndUpdate') se lanzarán?

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

Parece que está un paso atrás. ¿Qué me estoy perdiendo?

AH, el cambio de ruptura predeterminado de la bandera new . Si bien hay algo que decir sobre la consistencia con el controlador subyacente, debo decir que es realmente contrario a la intuición, especialmente cuando se considera este nuevo gancho.

@neverfox :)

, @neverfox, buena captura. Este cambio está documentado en las notas de la versión y puede ver más información sobre por qué se cambió en el n.º 2262.

La razón es que new es falso de forma predeterminada en el controlador de nodo mongodb, el shell mongodb, el servidor mongodb real, etc. y las capas envolventes que establecen valores predeterminados no estándar dificultan la vida de los desarrolladores. Mi ejemplo canónico de un módulo que hace esto terriblemente mal es gulp-uglify , que anula un montón de valores predeterminados de uglify-js.

Veo que el problema está cerrado, pero ¿la funcionalidad está en su lugar en la versión 4.0.2? ¿Todavía está en la versión inestable? No parece ejecutarse en findOneAndUpdate con Scema.pre('update') o Schema.pre('findOneAndUpdate') con la versión 4.0.2. ¿Me estoy perdiendo algo que tengo que pasar a la función?

¿Cómo estás declarando el gancho previo @CaptainStaplerz ?

¿El enlace de middleware findOneAndUpdate está disponible en 4.0.2? Actualicé de 3.8 a la última mongoose 4.0.2 para usar esto y el middlware Document.schema.post('findOneAndUpdate', la función (doc) no se activa como lo hace save() o remove()

@honitus muéstrame tu código

@vkarpov15 - Gracias por la rápida respuesta, aquí tienes

blog.controlador,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 Gracias por reabrir. ¿Significa esto que el enlace para findOneAndUpdate no está en 4.0.2 y planea incluirlo en 4.0.3?

@ vkarpov15 Aquí está el código donde declaro los ganchos:

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

Y aquí es donde llamo a la actualización:

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

Por lo que sé, el findOneAndUpdate() debería estar allí, si no funciona, es un error

@CaptainStaplerz intente:

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

Además, ¿se está ejecutando la declaración console.log , o es solo la parte Date.now() la que está dando resultados inesperados?

@vkarpov15
Cambié mi código fuente, agregando los cambios que hiciste en Agregar implementación #964 https://github.com/Automattic/mongoose/commit/e98ef98e857965c4b2ae3339fdd7eefd2a5a9913

Funciona a las mil maravillas ahora. Así que creo que la solución no está registrada en main

@honitus , ¿estás seguro de que estás usando mongoose 4.0.2? De hecho, ese cambio está comprometido con 4.0.0 y versiones posteriores.

honitus$ npm ver versión mangosta
4.0.2

@honitus lo que está haciendo incorrectamente es que está agregando enlaces al esquema después de compilar su modelo. No se espera que Model.schema.pre('remove'); funcione, consulte "Compilación de su primer modelo" en los documentos del modelo . Adjunte los ganchos al esquema primero y luego todo debería funcionar; esa es la única diferencia que veo entre su código y nuestras pruebas.

@CaptainStaplerz, la única forma que puedo encontrar para reproducir su código es con una actualización vacía. El siguiente código funciona

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

Pero una vez que haces la actualización vacía:

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

El enlace previo a la actualización ya no se ejecuta. ¿Es esto consistente con lo que estás viendo?

@vkarpov15
Modelo.esquema.pre('eliminar'); es uno de los stubs creados automáticamente por angular full stack
Agregué la implementación #964 e98ef98
Todo lo que hice fue agregar una línea debajo
this._schema.s_.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,
Ver Línea 1526 en 4.0.2 - this.model.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,

Reemplace la línea 1526 con this.schema.s.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,
y funciona.

Eso es por diseño. Se supone que Mongoose no permite modificar los enlaces de esquema después de compilar el modelo (es decir, la llamada mongoose.model() ), razón por la cual ya no funciona.

@ vkarpov15 Logré que el enlace funcionara ahora con lo siguiente, debe haber sido un error tipográfico estúpido o algo más:

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

Sin embargo, 'esto' no parece referirse al modelo actualizado (¿pero lo hace en el enlace 'guardar'?), Por lo tanto, this.updatedAt se refiere a indefinido.

¿Cómo actualizo el campo 'updatedAt' en el gancho 'findOneAndUpdate'?

Debería agregar más documentos que aclaren esto: es posible que el documento que se está actualizando no exista en la memoria cuando llama a findOneAndUpdate , por lo que el objeto this se refiere a la consulta en lugar del documento en el middleware de consulta . Prueba this.update({ $set: { updatedAt: Date.now() } });

@vkarpov15 No pude hacer que this.update({ $set: { updatedAt: Date.now() } }); funcionara, pero pude actualizar con éxito el documento en el enlace findOneAndUpdate con esto: this._update['$setOnInsert'].updatedAt=Date.now(); .

Recomiendo encarecidamente no depender de ajustar el estado interno de esa manera. Estoy bastante sorprendido de que no pudiste hacer funcionar this.update() . ¿Puedes mostrarme cómo se ve tu gancho?

¡Seguro! (Tenga en cuenta que estoy usando 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();
  });
});

Obviamente podría manejar esto cuando inicializo el documento en otra parte de mi aplicación, pero es bueno tenerlo todo allí mismo en Mongoose. ¡Bienvenido cualquier pensamiento!

Sí, estaba confundido y pensé que estabas usando update . El siguiente guión

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

Funciona correctamente y ejecuta la consulta deseada:

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

Así que por favor usa

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

En lugar de manipular manualmente el estado interno de mquery, generalmente es una mala idea a menos que realmente sepa lo que está haciendo.

¿Esto funciona o no? El único gancho que funciona para mí es 'guardar', el resto se ignora por completo. Estoy ejecutando en 4.0.6. Gracias

@agjs proporcione un ejemplo de código, por favor.

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

});

Tengo otro preenganche en otro modelo con model.create y pre-save y funciona normalmente.

Con cualquier actualización, simplemente no ve el gancho, ni siquiera lo consola. Lo registra. Intenté findOneAndUpdate también, etc., realmente no funciona... Es curioso que haya revisado todo este hilo y, como suelo hacer, revisé la documentación oficial y ustedes afirman incluso allí que funciona.

¿Cuál es la relación entre CompanyAvatarSchema y CompanyDetails en el código anterior?

Los detalles de la empresa tienen CompanyAvatar.schema como subdocumento

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

Además, no solo se preengancha, sino que la validación también se ignora por completo. Este subdocumento se completa pero ignora tanto la validación como el enlace previo. Busqué en Google todo, también probé ESTO pero nada parece funcionar. Cuando solo para probar cambio mi consulta para crear e invocar el modelo con new aka var parent = new Parent(), funciona.

Estás llamando a CompanyDetails.update() pero el gancho previo está definido en un esquema separado. El middleware de consulta no activa el cajero automático pre('update') del esquema anidado.

Además, proporcione un ejemplo de código más completo para su caso de "validación se ignora por completo".

Aquí está el esquema de avatar de mi empresa hecho para validación y enlaces previos para usuarios que están actualizando su perfil (foto de 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;

Aquí está el esquema company_details donde company_avatar es un subdocumento:

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

    }
});

Y aquí está el controlador de perfil de actualización y el avatarPath que debe validarse/engancharse antes de que se realice esta actualización:

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

};

Básicamente, mi mongodb se llena con los campos de req.files.file pero aparte de eso, la validación se ignora y no funciona ningún enlace.

El problema es que el middleware pre('findOneAndUpdate') está definido en un esquema anidado. En este momento, mongoose solo activa el middleware de consulta para los esquemas de nivel superior, por lo que los middleware definidos en CompanyDetailsSchema se activarán re: #3125

+1

@vkarpov15 Entiendo eso

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

funciona para establecer una propiedad en un valor fijo, pero ¿cómo puedo _leer y cambiar_ una propiedad, por ejemplo, codificar una contraseña?

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

¿Alguna idea? Este es un caso de uso bastante común, ¿verdad? ¿O la gente suele encontrar() y luego separar save() ?

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

Eso debería funcionar @willemmulder.

@vkarpov15 perfecto, gracias! Lo intentaré esta noche. Eso también debería funcionar por pre('update') , ¿verdad?

@ vkarpov15 Así que lo probé usando

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

y si consola.log this obtengo

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

lo que parece estar bien (e incluso intenté configurar esa propiedad directamente antes) pero al final, en realidad no escribe el valor hash en la base de datos, sino simplemente el valor 'sin procesar'. ¿Hay algo que pueda intentar?

Eso es extraño. Puede intentar habilitar el modo de depuración de mongoose con require('mongoose').set('debug', true); y ver cuál es la consulta que se envía a la base de datos, eso podría arrojar algo de luz.

Gracias por la sugerencia. Acabo de hacer eso:

Yo corro esto:

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

que devuelve esto para la consola.log

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

y luego esto para la depuración 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 } }

¿Cualquier pista?

No estoy seguro, abrió un nuevo problema para rastrear.

@vkarpov15 Gracias, realizaré un seguimiento del otro problema.

@ vkarpov15 Creo que la forma correcta de establecer opciones para la consulta en curso en el enlace previo sería algo como:

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

pero la documentación, http://mongoosejs.com/docs/api.html#query_Query -setOptions no menciona ninguna de esas opciones. Si esto se considera una solución de pirateo, ¿qué sería más apropiado?

Ese es un problema con los documentos, ese código que describe parece que debería funcionar a primera vista

¿Puedes abrir un tema aparte para eso?

@ vkarpov15 Sí, funciona. Creo que no fui lo suficientemente claro.

setOptions aplica new y runValidators correctamente, simplemente preguntaba si se debería preferir configurar esas opciones a través setOptions a this.options .

setOptions() es preferible en mi opinión, pero ambos deberían funcionar. O simplemente podrías hacer

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

Esto actualizará la contraseña en cada llamada de actualización (). Entonces, si solo cambio el valor de otras propiedades, es decir, el nombre o la edad, la contraseña también se actualizará, ¡lo cual no es correcto!

@nlonguit Supongo que lo hará. Pero puedes acceder a través this a los campos que se van a actualizar y podrías hacer algo como:

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

Este código está funcionando bien para mí. Gracias @akoskm

Me pregunto si sería posible agregar un enlace previo para findByIdAndUpdate también. Sería bueno tener ambos ganchos disponibles.

Lo hice de esta manera y está funcionando: solo findById, luego guarde sin actualizar ningún campo y luego use el método 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');
    });`

Estoy tratando de establecer una propiedad para que tenga la longitud de una matriz.

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

Se registra la longitud correcta, aunque la consulta nunca establece totalNumberOfComments y el campo permanece en 0 (ya que el esquema hace referencia al valor predeterminado: 0).

Cuando hago console.log(this) al final del gancho, puedo ver que mi query contiene lo siguiente:

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

Aunque cuando enciendo el modo de depuración, Mongoose nunca registra una consulta.

¿Hay algo que estoy haciendo mal o es un error?

@zilions this.findOneAndUpdate({}, { totalNumberOfComments: result.comments.length }).exec(); necesita ejecutar la consulta :) Solo tenga cuidado, obtendrá una recursividad infinita allí porque su gancho de guardado de publicación activará otro gancho de guardado de publicación

@vkarpov15 ¡Ahhhh cierto! Entonces puedo usar this.update({} {....}).exec() en su lugar :)
Sin embargo, la pregunta es que al usar esto, establece el campo totalNumberOfComments perfectamente, aunque también realiza la actualización original de findOneAndUpdate .

Por ejemplo:

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

Activará el siguiente gancho:

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

Aunque, el gancho $push a comments el myNewComment otra vez, por lo tanto haciendo una entrada duplicada.

Porque técnicamente estás ejecutando la misma consulta:

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

es esencialmente lo mismo que

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

Si desea crear una nueva consulta desde cero, simplemente hágalo

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

simplemente no toques tu gancho de guardado previo,

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

Descubrí que importa el orden en el que defines un modelo y defines un gancho pre . Permítanme demostrar:

No funciona:

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

Funciona:

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

¡Espero que esto ayude a alguien!

Me acabo de enterar de lo mismo que @nicky-lenaers.

Funciona bien con 'safe' . 'delete' . etc. si define los ganchos después de definir el modelo.

¿Hay alguna solución para definir un enlace 'findOneAndUpdate' después de definir el modelo?

@albert-92 no por el momento

Para cualquiera que intente obtener algo que solía ser como

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

Esto debería funcionar

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

No puedo obtener ganchos de publicación para actualizar el documento en la colección.

`módulo.exportaciones = función (mangosta) {
var mySchema = mangosta.Schema({
id: {tipo: número, índice: {único: verdadero}},
campo1: {tipo: Cadena},
campo2: {tipo: Cadena}
}, {
colección: "miEsquema",
clave de versión: falso
});

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

}`

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

Establece el campo de la colección en { id:1, field1: 'test' ) pero debería ser {id: 1, field1: 'test', field2:'New Value'}
No estoy seguro de lo que estoy haciendo mal

Puedo cambiar el resultado de findOneAndUpdate haciendo esto
mySchema.post('findOneAndUpdate', function (result) { result.field2 = 'something' });

Creo que podría ser que esté intentando actualizar el modelo con un elemento que ya existe en el modelo. O posiblemente que lo estés seleccionando mal. Intente imprimir "esto" en su mySchema.post. Además, parece que no tienes done() o next() en tu publicación. No soy muy conocedor del tema, pero sé que imprimir esto al menos te dará una idea de a qué te enfrentas.

¿No es el objetivo de la actualización cambiar un documento existente en su modelo?

este es un objeto de consulta

Por lo que yo entiendo, no es necesario terminar o seguir en los ganchos de publicación.

Bueno, tienes this.model.update que es el esquema, no el modelo del objeto. Creo... Lo que significa que tendrías que usar

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

Esto parece un poco al revés para llamar a una función de modelo dentro del modelo. Ya que podría usar partes del objeto "este" que se le da. es posible que sea mejor simplemente usar findOneAndUpdate en lugar de llamarlo y luego llamar a otra función de modelo encima.
en un gerente

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

En mi ejemplo anterior, usé this._update porque ese era el objeto de actualización que debía usarse fuera de esto.

He intentado usar $set. Todavía no cambia mi documento en la colección.

¿Dónde puedo encontrar todos los ganchos previos y posteriores disponibles?

+1

¿Fue útil esta página
0 / 5 - 0 calificaciones