Oi, eu tenho um problema com a versão mais recente do Mongoose. Estou criando uma API com Express e Mongoose 4.0.1, e não tenho certeza se estou fazendo algo errado, mas o fato é sempre que tento usar o novo middleware de atualização pre
da mesma forma Eu uso pre
save middleware, this
object não retorna o objeto que está sendo atualizado, ao invés disso ele retorna o objeto Query
.
Exemplo do que estou tentando 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();
});
Isso é o que eu recebo no objeto de referência this
dentro do middleware, e não sei como isso é útil para mim.
{ _mongooseOptions: {},
mongooseCollection:
{ collection: { s: [Object] },
opts: { bufferCommands: true, capped: false },
name: 'exercises',
conn:
... },
...
}
Observando o código-fonte, algumas de suas propriedades são definidas dentro da função Query
definida em ./node_modules/mongoose/lib/query.js
:
Isso é algo inesperado ou estou fazendo algo errado? Seria interessante ter uma solução porque não gosto da ideia de validar dentro de um middleware no salvamento de objetos e ser forçado a executar validações diretamente no controller ao atualizar.
A mesma coisa acontece com o middleware findOneAndUpdate pre
, mas não espero que ele retorne o exercício antes de encontrá-lo. Na verdade e na minha opinião honesta, eu acho que seria interessante que findAndUpdate e findOneAndUpdate pré middlewares acionassem find(One) e atualizassem middlewares, nesta ordem, ao invés de ter seu próprio middleware.
Agradeço antecipadamente por sua ajuda.
Por design - o documento que está sendo atualizado pode nem estar na memória do servidor. Para fazer isso, o mangusto teria que fazer um findOne() para carregar o documento antes de fazer o update(), o que não é aceitável.
O design é permitir que você manipule o objeto de consulta adicionando ou removendo filtros, atualizando parâmetros, opções, etc. Por exemplo, chamando automaticamente .populate() com find() e findOne() , definindo o multi: true
opção por padrão em alguns modelos, controle de acesso e outras possibilidades .
findOneAndUpdate()
é um nome um pouco impróprio, ele usa o comando mongodb findAndModify
subjacente, não é o mesmo que findOne()
+ update()
. Como uma operação separada, deve ter seu próprio middleware.
Como você sugeriria atualizar um campo de documento durante o middleware de atualização 'pré' então?
this.update({ field: val });
adicionará { $set: { field: val } }
à operação de atualização antes que ela aconteça.
Obrigado por responder, em qual versão isso deveria funcionar? porque estou usando 4.0.2 e não funciona. Eu tenho que chamar "exec" na consulta?
se eu executar this.update não estaria chamando este método http://mongoosejs.com/docs/api.html#query_Query -update?
Acabei fazendo isso.findOneAndUpdate({matcher: "myvalue"}); espero que seja a abordagem certa.
Hmm 4.0.2 deve funcionar. Como é o seu código?
Acabei de fazer algo assim:
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 ok, vejo que você está usando findOneAndUpdate()
em vez de update()
. Estas são duas funções distintas com ganchos distintos, então se você quiser adicionar operações de atualização para findOneAndUpdate
em um pré gancho, você deve fazer
schema.pre('findOneAndUpdate', function() {
this.findOneAndUpdate({}, { $set: { key: 'value' } });
});
a documentação do man mongoose é terrivelmente ruim. Alguém pode por favor colocar isso aí?
@askdesigners colocou o que exatamente lá?
Desculpe, eu escrevi isso em um momento de estresse :)
Apenas a fragilidade da compatibilidade de versão realmente. Não é um problema óbvio e deixa muita gente coçando a cabeça.
Sem problemas. Que tipo de problemas de compatibilidade de versão você está tendo?
E se eu quiser higienizar um valor em pre.update... não consigo encontrar uma maneira de fazer isso.
Obrigado!
schema.pre('update', function() {
var v = this.getUpdate().valueToSanitize;
this.update({}, { $set: { valueToSanitize: sanitize(v) } });
});
Legal, vou tentar isso!
Obrigado!!
Ei @vkarpov15 , me deparei com algo que eu diria que é um comportamento inesperado, ou pelo menos deveria ser documentado.
Se eu fizer algo como
MyModel.findOneAndUpdate({a: 1}, {v: '__3'}, function (err, model) {
console.log(model);
});
E no meu modelo eu implemento sua sugestão
mySchema.pre('findOneAndUpdate', function() {
var v = this.getUpdate().v;
this.findOneAndUpdate({}, { $set: { v: sanitize(v) } });
});
O log produzirá '__3' já que a consulta de atualização original será executada após o gancho. Consigo alterar outras propriedades do esquema, mas todos os campos atualizados na consulta original permanecerão os mesmos, mesmo que sejam alterados no pré-hook.
@CMatias este é um caso em que você precisa ter cuidado ao usar $set
ou não de forma consistente. O Mongoose não sabe necessariamente se deve ou não envolver $set
, então no seu caso você deve fazer this.findOneAndUpdate({}, { v: sanitize(v) });
@vkarpov15 obrigado, funcionou. Você poderia explicar brevemente qual é a diferença entre usar $set
e não usá-lo? Não consigo encontrar muitas informações a respeito.
Portanto, se você usar o driver mongodb nativo diretamente e fizer coll.findOneAndUpdate({ a: 1 }, { __v: 3 })
, o mongodb pegará o primeiro documento com a = 1 e o substituirá pelo documento { __v: 3 }
módulo _id
. Em outras palavras, ele substituirá o documento existente. Para definir apenas a chave '__v', você precisa fazer coll.findOneAndUpdate({ a: 1 }, { $set: { __v: 3 } })
.
Esse comportamento sempre foi controverso e propenso a erros, portanto, por padrão, o mangusto impede que você sobrescreva o documento. Em outras palavras, MyModel.findOneAndUpdate({ a: 1 }, { __v: 3 })
se torna MyModel.collection.findOneAndUpdate({ a: 1 }, { $set: { __v: 3 } })
_a menos que você defina a opção overwrite: true
. No entanto, você pode fazer coisas estranhas como MyModel.update({}, { $set: { b: 2 } }).findOneAndUpdate({ a: 1 }, { __v: 3 }, { overwrite: true })
que tornam bastante confuso qual é o estado atual de getUpdate()
, então deixamos o status de update
como está para middleware.
Estou tentando grocar tudo isso, mas estou tendo dificuldade em determinar se posso modificar o documento que será retornado de uma localização usando esses métodos de middleware. Especificamente, gostaria de "decorar" a resposta adicionando campos adicionais.
Por exemplo, se eu tiver um Schema:
const Thing = new mongoose.Schema({
name: {type: String}
});
Thing.post('find', function(doc, next){
doc.newfield = "example text";
next();
}
Isso é possível?
O motivo pelo qual newfield
não está definido no esquema é que, dependendo dos valores de Thing
, são necessários nomes de campo diferentes.
Obrigado!
eu preciso de algum plugin específico para usar schema.pre('update') ?
Não @jrogatis
@vkarpov15
Olhando para o exemplo que você deu acima ...:
schema.pre('update', function() {
var v = this.getUpdate().valueToSanitize;
this.update({}, { $set: { valueToSanitize: sanitize(v) } });
});
Isso não funciona para mim. O this.getUpdate().value
não contém minha propriedade value
como o exemplo sugere. Em vez disso, está disponível em this.getUpdate().$set.property
.
É suposto ser assim? Estou entendendo algo errado? A API mudou? Existe alguma documentação sobre isso?
@qqilihq você pode abrir um problema separado com um script de reprodução?
@qqilihq nvm, acabei de ver que você fez :P
@varunjayaraman Na verdade, a edição separada que abri é diferente :) Mas com prazer abrirei uma nova para o comentário acima, assim que tiver alguns minutos livres para compilar um exemplo.
@qqilihq ah ok, obrigado!
Exatamente o mesmo para mim mencionado por @qqilihq
Portanto, o comportamento desejado pode ser alcançado com este código:
userSchema.pre('update', function (next) {
const newEmail = this.getUpdate().$set.email
if (newEmail) {
this.update({}, {$set: {email: normalizeEmail(newEmail)}})
}
next()
})
Mas parece que estou tentando usar alguns campos que não são destinados a isso.
O método pre
não deve funcionar com this.update
, porque é "pre"-save e não há nada para atualizar ainda, enquanto this.newProp = "value"
funciona perfeitamente, obrigado pessoal.
Meu caso é:
UserSchema.pre('save', function(next) {
this.created = Date.now()
next()
})
Eu entendo o raciocínio por trás de ter a consulta como "this" no gancho de atualização "pré", mas por que o gancho de atualização "pós" está se comportando de maneira semelhante? A atualização do post não deveria ter o modelo real como "this"?
Então, post find seria executado várias vezes ou teria this
como uma matriz de vários resultados? E se não houver resultados, o post find não deve ser executado?
Isso tudo é muito estranho. IMHO Os ganchos pré devem se comportar da mesma forma, com um this
que é o objeto que está sendo modificado e métodos legais como isModified para verificar as coisas.
Na verdade, acho que agora entendi. Eu deveria estar usando o método pre 'save' e então o método this.isNew. Obrigado!
@ajbraus é um pouco estranho, mas a alternativa é uma enorme sobrecarga de desempenho. Suponha que você tenha um updateMany que atualize milhões de documentos - o mangusto teria que carregá-los todos na memória.
Como se deve verificar os campos do documento antes das atualizações? Digamos que preciso verificar se determinados campos estão preenchidos para definir o documento como "válido", como posso acessar todo o documento e não apenas os campos que estão sendo atualizados? this
está se referindo à consulta aqui
@PDS42 você terá que executar uma consulta separada para carregar o documento do mongodb
Existe um link específico na documentação do mangusto onde tudo isso é explicado com mais profundidade? Eu adoraria ler sobre isso e entender isso mais completamente :).
EDITAR:
ganchos de pré-atualização: https://github.com/Automattic/mongoose/issues/2812
middleware: https://mongoosejs.com/docs/middleware.html
objeto de consulta: https://mongoosejs.com/docs/api.html#Query
consultas: https://mongoosejs.com/docs/queries.html
Estou tendo problemas para encontrar documentação sobre como exatamente se deve usar as consultas referenciadas por 'this' no middleware de atualização. Por exemplo, o que acontece quando:
this.update({field: "value"}, {$set: {updatedAt: new Date()}});
Isso adiciona {field: "value"}
ao filtro? Ou não tem efeito? Existe documentação que não estou vendo?
@ronakvora existe uma função Query#getConditions()
.
schema.pre('update', function() {
console.log(this.getConditions()); // Will include `{ field: 'value' }`
});
Gotcha, isso vai me ajudar a brincar com as coisas :). Obrigado!
Ps este é Ronak de cima. Aparentemente, tenho duas contas conectadas em dispositivos diferentes.
Comentários muito úteis
Ah ok, vejo que você está usando
findOneAndUpdate()
em vez deupdate()
. Estas são duas funções distintas com ganchos distintos, então se você quiser adicionar operações de atualização parafindOneAndUpdate
em um pré gancho, você deve fazer