<p>mongoose 4.0.1: antes de "actualizar" el middleware, este objeto no devuelve el objeto modelo</p>

Creado en 30 mar. 2015  ·  40Comentarios  ·  Fuente: Automattic/mongoose

Hola, tengo un problema con la versión más reciente de Mongoose. Estoy creando una API con Express y Mongoose 4.0.1, y no estoy seguro de si estoy haciendo algo mal, pero el hecho es que cada vez que trato de usar el nuevo middleware de actualización pre la misma manera Uso pre save middleware, el objeto this no devuelve el objeto que se está actualizando, sino que devuelve el objeto Query .

Ejemplo de lo que estoy tratando de explicar:

ExerciseSchema.pre('save', function (next, done) {
        var self = this;
        console.log('exercise:', self); // returns exercise object
        // some validations here
       next();
});
ExerciseSchema.pre('update', function (next, done) {
        var self = this;
        console.log('exercise:', self); // returns Query object instead of exercise object
        // some validations here
       next();
});

Esto es lo que obtengo en el objeto de referencia this dentro del middleware, y no sé cómo me es útil.

{ _mongooseOptions: {},
  mongooseCollection: 
   { collection: { s: [Object] },
     opts: { bufferCommands: true, capped: false },
     name: 'exercises',
     conn: 
     ... },
   ...
}

Mirando el código fuente, algunas de sus propiedades están definidas dentro de la función Query definida en ./node_modules/mongoose/lib/query.js :

¿Es esto algo inesperado o estoy haciendo algo mal? Sería interesante tener una solución porque no me gusta la idea de validar dentro de un middleware al guardar objetos y verme obligado a ejecutar validaciones directamente en el controlador al actualizar.

Lo mismo sucede con el middleware findOneAndUpdate pre , pero no espero que devuelva el ejercicio antes de encontrarlo. De hecho, y en mi honesta opinión, creo que sería interesante que los middlewares previos findAndUpdate y findOneAndUpdate activaran los middlewares find(One) y update, en este orden, en lugar de tener su propio middleware.

Gracias de antemano por tu ayuda.

won't fix

Comentario más útil

Ah, está bien, veo que estás usando findOneAndUpdate() en lugar de update() . Estas son dos funciones distintas con ganchos distintos, por lo que si desea agregar operaciones de actualización a findOneAndUpdate en un gancho previo, debe hacerlo

schema.pre('findOneAndUpdate', function() {
  this.findOneAndUpdate({}, { $set: { key: 'value' } });
});

Todos 40 comentarios

Por diseño, es posible que el documento que se actualiza ni siquiera esté en la memoria del servidor. Para hacer eso, mongoose tendría que hacer un findOne() para cargar el documento antes de hacer la actualización(), lo cual no es aceptable.

El diseño es para permitirle manipular el objeto de consulta agregando o eliminando filtros, actualizando parámetros, opciones, etc. Por ejemplo, llamando automáticamente a .populate() con find() y findOne() , configurando multi: true opción por defecto en determinados modelos, control de acceso, y otras posibilidades .

findOneAndUpdate() es un nombre un poco inapropiado, usa el comando subyacente mongodb findAndModify , no es lo mismo que findOne() + update() . Como operación separada, debe tener su propio middleware.

Entonces, ¿cómo sugeriría actualizar un campo de documento durante el middleware de actualización 'pre'?

this.update({ field: val }); agregará { $set: { field: val } } a la operación de actualización antes de que suceda.

Gracias por responder, ¿en qué versión se supone que funciona? porque estoy usando 4.0.2 y no lo hace. ¿Tengo que llamar a "ejecutivo" en la consulta?

si ejecuto this.update, ¿no estaría llamando a este método http://mongoosejs.com/docs/api.html#query_Query -update?

Terminé haciendo esto.findOneAndUpdate({matcher: "myvalue"}); Espero que ese sea el enfoque correcto.

Hmm 4.0.2 debería funcionar. ¿Cómo se ve tu código?

Acabo de hacer algo como esto:

var schema = mongoose.Schema({
    name: String,
    description: String,
    matcher: String,

});

var generateMatcherUpdate= function(next) {
     var matcher = "generate matcher function"
        this.update({matcher: matcher});

    next();
};

 schema.pre('update', generateMatcherUpdate);

Ah, está bien, veo que estás usando findOneAndUpdate() en lugar de update() . Estas son dos funciones distintas con ganchos distintos, por lo que si desea agregar operaciones de actualización a findOneAndUpdate en un gancho previo, debe hacerlo

schema.pre('findOneAndUpdate', function() {
  this.findOneAndUpdate({}, { $set: { key: 'value' } });
});

La documentación del hombre mangosta es odiosamente mala. ¿Alguien puede poner esto ahí?

@askdesigners puso qué exactamente ahí?

Lo siento, escribí eso en un momento de estrés :)

Solo la fragilidad de la compatibilidad de versiones realmente. No es un problema obvio, y deja a mucha gente rascándose la cabeza.

No hay problema. ¿Qué tipo de problemas de compatibilidad de versiones tienes?

Y qué pasa si quiero desinfectar un valor en pre.update... Parece que no puedo encontrar una manera de hacerlo.
¡Gracias!

schema.pre('update', function() {
  var v = this.getUpdate().valueToSanitize;
  this.update({}, { $set: { valueToSanitize: sanitize(v) } });
});

Genial, lo probaré!
¡¡Gracias!!

Hola @vkarpov15 , me encontré con algo que diría que es un comportamiento inesperado, o al menos debería documentarse.

Si hago algo como

MyModel.findOneAndUpdate({a: 1}, {v: '__3'}, function (err, model) {
    console.log(model);
});

Y en mi modelo implemento tu sugerencia.

mySchema.pre('findOneAndUpdate', function() {
  var v = this.getUpdate().v;
  this.findOneAndUpdate({}, { $set: { v: sanitize(v) } });
});

El registro generará '__3' ya que la consulta de actualización original se ejecutará después del enlace. Puedo cambiar otras propiedades del esquema, pero todos los campos que se actualizan en la consulta original seguirán siendo los mismos, incluso si se modifican en el gancho previo.

@CMatias, este es un caso en el que debe tener cuidado al usar $set o no de manera consistente. Mongoose no necesariamente sabe si envolver o no en $set , por lo que en su caso debe hacer this.findOneAndUpdate({}, { v: sanitize(v) });

@ vkarpov15 gracias, eso funcionó. ¿Podría explicar brevemente cuál es la diferencia entre usar $set y no usarlo? No puedo encontrar mucha información al respecto.

Entonces, si usa el controlador mongodb nativo directamente y hace coll.findOneAndUpdate({ a: 1 }, { __v: 3 }) , mongodb tomará el primer documento con a = 1 y lo reemplazará con el documento { __v: 3 } modulo _id . En otras palabras, sobrescribirá el documento existente. Para configurar la clave '__v', debe hacer coll.findOneAndUpdate({ a: 1 }, { $set: { __v: 3 } }) .

Este comportamiento siempre ha sido controvertido y propenso a errores, por lo que, de forma predeterminada, mongoose evita que se sobrescriba el documento. En otras palabras, MyModel.findOneAndUpdate({ a: 1 }, { __v: 3 }) se convierte en MyModel.collection.findOneAndUpdate({ a: 1 }, { $set: { __v: 3 } }) _a menos que_ establezca la opción overwrite: true . Sin embargo, puede hacer cosas extrañas como MyModel.update({}, { $set: { b: 2 } }).findOneAndUpdate({ a: 1 }, { __v: 3 }, { overwrite: true }) que hacen que sea bastante confuso cuál es el estado actual de getUpdate() , así que dejamos el estado de update como está para el software intermedio.

Estoy tratando de asimilar todo esto, pero tengo dificultades para determinar si puedo modificar el documento que se devolverá de una búsqueda utilizando estos métodos de middleware. Específicamente, me gustaría "decorar" la respuesta agregando campos adicionales.

Por ejemplo, si tengo un esquema:

const Thing = new mongoose.Schema({
  name: {type: String}
});

Thing.post('find', function(doc, next){
  doc.newfield = "example text";
  next();
}

es posible?

La razón por la que newfield no está definido en el esquema es que, dependiendo de los valores de Thing , se necesitan diferentes nombres de campo.

¡Gracias!

¿Necesito algún complemento específico para usar schema.pre('update')?

No @jrogatis

@vkarpov15

Mirando el ejemplo que diste arriba ...:

schema.pre('update', function() {
  var v = this.getUpdate().valueToSanitize;
  this.update({}, { $set: { valueToSanitize: sanitize(v) } });
});

Esto no funciona para mí. El this.getUpdate().value no contiene mi propiedad value como sugiere el ejemplo. En cambio, está disponible por this.getUpdate().$set.property .

¿Se supone que debería ser así? ¿Estoy entendiendo algo mal? ¿Cambió la API? ¿Hay alguna documentación sobre esto?

@qqilihq , ¿puedes abrir un problema separado con un script de reproducción?

@qqilihq nvm, acabo de ver que lo hiciste :P

@varunjayaraman En realidad, el problema separado que abrí es diferente :) Pero con gusto abriré uno nuevo para el comentario anterior, una vez que tenga algunos minutos libres para compilar un ejemplo.

@qqilihq ah vale, gracias!

Exactamente lo mismo para mí como lo menciona @qqilihq

image

Entonces, el comportamiento deseado podría lograrse con este código:

userSchema.pre('update', function (next) {
  const newEmail = this.getUpdate().$set.email
  if (newEmail) {
    this.update({}, {$set: {email: normalizeEmail(newEmail)}})
  }
  next()
})

Pero parece que estoy tratando de usar algunos campos que no están destinados a esto.

El método pre no debería funcionar con this.update , porque es "pre-guardado" y no hay nada que actualizar todavía, mientras que this.newProp = "value" funciona perfectamente, gracias chicos.

mi caso es:

UserSchema.pre('save', function(next) {
    this.created = Date.now()
    next()
})

Entiendo el razonamiento detrás de tener la consulta como "esto" en el gancho de actualización "previo", pero ¿por qué el gancho de actualización "posterior" se comporta de manera similar? ¿No debería publicar la actualización tener el modelo real como "esto"?

Entonces, ¿post find se ejecutaría varias veces o tendría this como una matriz de múltiples resultados? ¿Y si no hay resultados, debería publicar find not run?

Todo esto es muy extraño. En mi humilde opinión, todos los ganchos previos deberían comportarse igual, con un this que es el objeto que se está modificando y buenos métodos como isModified para verificar las cosas.

En realidad, creo que ahora entiendo. Debería usar el método pre 'guardar' y luego el método this.isNew. ¡Gracias!

@ajbraus es un poco extraño, pero la alternativa es una sobrecarga de rendimiento masiva. Suponga que tiene un updateMany que actualiza millones de documentos: mongoose tendría que cargarlos todos en la memoria.

¿Cómo se supone que uno debe verificar los campos del documento antes de las actualizaciones? Digamos que necesito verificar si ciertos campos están llenos para establecer el documento como "válido", ¿cómo puedo acceder a todo el documento y no solo a los campos que se actualizan? this se refiere a la consulta aquí

@ PDS42 tendrá que ejecutar una consulta separada para cargar el documento desde mongodb

¿Hay algún enlace particular en la documentación de la mangosta donde todo esto se explica con más profundidad? Me encantaría leer sobre esto y entender esto más completamente :).

EDITAR:

enlaces previos a la actualización: https://github.com/Automattic/mongoose/issues/2812
software intermedio: https://mongoosejs.com/docs/middleware.html
objeto de consulta: https://mongoosejs.com/docs/api.html#Query
consultas: https://mongoosejs.com/docs/queries.html

Tengo problemas para encontrar documentación sobre cómo se deben usar exactamente las consultas a las que hace referencia 'esto' en el middleware de actualización. Por ejemplo, qué sucede cuando:
this.update({field: "value"}, {$set: {updatedAt: new Date()}}); ¿Esto agrega {field: "value"} al filtro? O no tiene efecto? ¿Hay documentación que no estoy viendo?

@ronakvora hay una función Query#getConditions() .

schema.pre('update', function() {
  console.log(this.getConditions()); // Will include `{ field: 'value' }`
});

Te tengo, eso me ayudará a jugar con las cosas :). ¡Gracias!

PD: este es Ronak desde arriba. Aparentemente tengo dos cuentas iniciadas en diferentes dispositivos.

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