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.
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
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.
Comentario más útil
Ah, está bien, veo que estás usando
findOneAndUpdate()
en lugar deupdate()
. Estas son dos funciones distintas con ganchos distintos, por lo que si desea agregar operaciones de actualización afindOneAndUpdate
en un gancho previo, debe hacerlo