<p>mongoose 4.0.1: pré "update" middleware este objeto não retorna objeto de modelo</p>

Criado em 30 mar. 2015  ·  40Comentários  ·  Fonte: Automattic/mongoose

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.

won't fix

Comentários muito úteis

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

Todos 40 comentários

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

image

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.

Esta página foi útil?
0 / 5 - 0 avaliações