Mongoose: pré, pós middleware não são executados em findByIdAndUpdate

Criado em 15 jun. 2012  ·  102Comentários  ·  Fonte: Automattic/mongoose

Porque um método findAndUpdate é apresentado para reduzir o seguinte código:

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

para isso:

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

Precisamos usar o middleware pré e pós exatamente da mesma forma. No momento pré, pós middleware não são executados quando eu faço findByIdAndUpdate.

Comentários muito úteis

Olá,

Sei que o assunto está encerrado, mas não estou totalmente satisfeito com a resposta. Não deveria haver pelo menos um middleware pós-salvamento ou pós-atualização para findOneAndUpdate e outras operações semelhantes? Parece ok desde que o documento é retornado. Não é viável para middlewares pré ou para Model.update, concordo.

Isso melhoraria muito os recursos de plugins como o mongoosastic, que atualmente está cego para algumas operações que deve ser capaz de suportar.

Se não houver middleware, alguém tem uma ideia de como gerenciar algumas operações de pós-atualização em um plugin?

Obrigado

Todos 102 comentários

por projeto. existem documentos envolvidos para chamar ganchos.

correção, não há documentos para chamar ganchos.

Assim? Se eu quiser chamar pré, pós middleware eu preciso usar a primeira abordagem?

sim, está correto. Model.update,findByIdAndUpdate,findOneAndUpdate,findOneAndRemove,findByIdAndRemove são todos os comandos executados diretamente no banco de dados.

Isso definitivamente deve ser esclarecido no guia , especialmente se você estiver falando sobre validação nessa mesma página e descrevendo findByIdAndUpdate como "melhor"

se você clicar no link "melhor" leva você para a documentação completa do
o método onde ele explica a validação etc.

sinta-se à vontade para enviar um pull request para adicionar algo que você considere melhor. Está
bem fácil de fazer:
https://github.com/LearnBoost/mongoose/blob/master/CONTRIBUTING.md

Na quarta-feira, 31 de outubro de 2012 às 18h03, Jesse Fulton [email protected] escreveu :

Isso definitivamente deve ser esclarecido no guiahttp://mongoosejs.com/docs/documents.html ,
especialmente se você estiver falando sobre validação nessa mesma página e
descrevendo findByIdAndUpdate como "melhor"


Responda a este e-mail diretamente ou visualize-o no Gi tHubhttps://github.com/LearnBoost/mongoose/issues/964#issuecomment -9967865.

Arão
@aaronheckmann https://twitter.com/#!/aaronheckmann

Notas adicionadas ao documento de middleware

Solicitação de pull: https://github.com/LearnBoost/mongoose/pull/1750

Olá,

Sei que o assunto está encerrado, mas não estou totalmente satisfeito com a resposta. Não deveria haver pelo menos um middleware pós-salvamento ou pós-atualização para findOneAndUpdate e outras operações semelhantes? Parece ok desde que o documento é retornado. Não é viável para middlewares pré ou para Model.update, concordo.

Isso melhoraria muito os recursos de plugins como o mongoosastic, que atualmente está cego para algumas operações que deve ser capaz de suportar.

Se não houver middleware, alguém tem uma ideia de como gerenciar algumas operações de pós-atualização em um plugin?

Obrigado

@albanm certos métodos ignoram o mongoose completamente , para que você não obtenha os ganchos do middleware. AFAIK, a única maneira de executar os ganchos é usar chamadas separadas find() e save() , conforme mencionado acima.

Eu entendo isso e faz sentido. Os middlewares pré estão fora de questão, pois não há busca antes de certos métodos. Mas ainda assim, o mangusto deve ser capaz de envolver as operações de atualização que também retornam os documentos atualizados e acionam alguns ganchos de postagem.

Acho que @albanm está certo, as pessoas podem querer a mesma funcionalidade quando usam a mesma função. Que tal envolver esses métodos de 'atualização direta' com alguma interceptação de verificação de que, se houver algum gancho, existe? Se o gancho existir, use-o ou chame os métodos de atualização originais.

+1

+1

+1

+1

:+1:

+1

:-1:
Recebo a solicitação de recurso, mas quanto mais tempo trabalho com middleware, me vejo precisando ignorar o middleware com frequência ao chamar scripts de atualização de banco de dados e outros itens que não são rotineiros para minhas operações normais. Ao usar métodos estáticos, torna-se muito fácil criar um wrapper personalizado que já implemente as solicitações de recursos acima. Como o mangusto agora está abrindo ideias para a versão 4, é improvável que qualquer mudança de API dessa magnitude aconteça na v3. Solicito que isso seja movido para a discussão da v4.

Eu, no entanto, :+1: se eu pudesse desabilitar o middleware em uma chamada de salvamento. Muitas vezes me encontro com um objeto mangusto que foi passado de outra função e simplesmente quero executar um .save - nesta situação, fazer um .save é preferível a escrever uma nova consulta. Se for possível, indique

De qualquer forma, biblioteca incrível. Parabéns aos mantenedores incríveis. Não tenho certeza de como eu operaria sem ele.

+1

Uma maneira simples de adicionar ganchos para esses métodos é substituir as funções de incorporação:

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

Em seguida, cada chamada de atualização do mangusto corrigirá a chave de dados updatedAt.

Você pode experimentar este modelo limbo . É um wrapper simples do modelo mongoose, suportando bind static/method/overwrite a todos os esquemas e chama métodos rpc para consultar o mongodb.

+1, preciso desse recurso...

Embora eu entenda o raciocínio, a falta de ganchos nas atualizações atômicas do IMHO torna o Mongoose um pouco inútil na prática. Quando eu uso atualizações atômicas, quaisquer validações, padrões, etc. não são executados, então todo o propósito de usar um ODM é derrotado. Usar localizar/salvar fará o trabalho, mas há alguma garantia de que isso seja sempre usado?

Além disso, geralmente eu tentaria evitar encontrar/salvar, pois não é uma operação atômica. O MongoDB compensa sua falta de suporte a transações fornecendo recursos poderosos de consulta e atualização atômica. Então, eu usaria essas operações atômicas, mas sem suporte a middleware, o Mongoose não fornecerá muito valor sobre o MongoClient nativo.

Mesmo os exemplos em http://aaronheckmann.tumblr.com/post/48943525537/mongoose-v3-part-1-versioning usariam update , portanto, ignoraria o middleware. Posso usar versionamento corretamente ou middleware mas não combinar os dois? Realmente, qual é o ponto em tê-lo?

Eu nem entendo completamente as razões técnicas: Se update & co. envolver as operações do banco de dados, por que não podemos interceptar a chamada e passar os objetos de consulta para que possamos fazer alguma validação/personalização antes de realmente fazer a atualização?

@joerx +1 seria suficiente .. :) mas seu raciocínio é impecável.

A ramificação 3.9.x tem suporte para ganchos pré e pós para find e findOne - deve ser fácil adicionar suporte para findOneAndUpdate e update .

Este recurso é mesclado?

Portanto, os ganchos pre('findOneAndUpdate') e post('findOneAndUpdate') estão no mestre, ainda não há um gancho de atualização. Não há nenhum lançamento que contenha qualquer um desses ainda.

Então, ele aciona o pré-salvamento após .update() agora?

Não. Existe um gancho update() separado para Query.update() . Os ganchos de salvamento são distintos dos ganchos de consulta.

@vkarpov15 Você pode vincular a documentação de suporte para o gancho de atualização? Alguma palavra sobre quando pre('findOneAndUpdate') e post('findOneAndUpdate') serão lançados?

@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á um passo atrás. o que estou perdendo?

AH, a mudança de quebra padrão do sinalizador new . Embora haja algo a dizer sobre consistência com o driver subjacente, devo dizer que é realmente contra-intuitivo, especialmente ao considerar esse novo gancho.

@nuncafox :)

Sim @neverfox boa captura. Essa alteração está documentada nas notas de lançamento e você pode ver mais discussões sobre por que isso foi alterado em #2262.

A razão é que new é falso por padrão no driver do nó mongodb, no shell mongodb, no servidor mongodb real, etc. Meu exemplo canônico de um módulo que faz isso terrivelmente errado é gulp-uglify , que substitui vários padrões do uglify-js.

Vejo que o problema está encerrado, mas a funcionalidade em vigor na versão 4.0.2 está apenas na versão instável? Ele não parece ser executado em findOneAndUpdate com Scema.pre('update') ou Schema.pre('findOneAndUpdate') com a versão 4.0.2. Estou faltando algo que tenho que passar para a função?

Como você está declarando o pre hook @CaptainStaplerz ?

O gancho de middleware findOneAndUpdate está disponível na versão 4.0.2? Eu atualizei de 3.8 para o último mongoose 4.0.2 para usar isso e o middlware Document.schema.post('findOneAndUpdate', function (doc) não está sendo acionado como save() ou remove()

@honitus me mostre seu código

@vkarpov15 - Obrigado pela resposta rápida, aqui está

blog.controller,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 Obrigado por reabrir. Isso significa que o gancho para findOneAndUpdate não existe no 4.0.2 e você planeja incluir no 4.0.3.

@vkarpov15 Aqui está o código onde declaro os 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();
});

E aqui é onde eu chamo a atualização:

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

Até onde eu sei, o gancho findOneAndUpdate() deve estar lá, se não estiver funcionando, é um bug

@CaptainStaplerz tente:

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

Além disso, a instrução console.log está sendo executada ou é apenas a parte Date.now() que está dando resultados inesperados?

@vkarpov15
Eu mudei meu código-fonte, adicionando as alterações que você fez em Adicionar implementação #964 https://github.com/Automattic/mongoose/commit/e98ef98e857965c4b2ae3339fdd7eefd2a5a9913

Funciona como charme agora. Então eu acho que a correção não está verificada no main

@honitus tem certeza de que está usando o mangusto 4.0.2? Essa mudança está de fato comprometida com a versão 4.0.0 e superior.

honitus$ npm ver a versão do mangusto
4.0.2

@honitus o que você está fazendo incorretamente é adicionar ganchos ao esquema após compilar seu modelo. Espera-se que Model.schema.pre('remove'); não funcione, consulte "Compilando seu primeiro modelo" na documentação do modelo . Anexe os ganchos ao esquema primeiro e depois as coisas devem funcionar - essa é a única diferença que vejo entre seu código e nossos testes.

@CaptainStaplerz a única maneira que encontro para reproduzir seu código é com uma atualização vazia. O código abaixo 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);
});

Mas depois de deixar a atualização vazia:

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

O gancho de pré-atualização não é mais executado. Isso é consistente com o que você está vendo?

@vkarpov15
Model.schema.pre('remover'); é um dos stubs criados automaticamente pelo full stack angular
Eu adicionei a implementação #964 e98ef98
Tudo o que fiz foi adicionar uma linha abaixo
this._schema.s_.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,
Veja a linha 1526 em 4.0.2 - this.model.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,

Substitua a linha 1526 por this.schema.s.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,
e funciona.

Isso é por design. O Mongoose não deve permitir a modificação dos ganchos do esquema após a compilação do modelo (ou seja, a chamada mongoose.model() ), e é por isso que isso não funciona mais.

@vkarpov15 Consegui fazer o gancho funcionar agora com o seguinte, deve ter sido um erro de digitação estúpido ou outra coisa:

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

No entanto, 'this' não parece se referir ao modelo atualizado (mas faz no gancho 'save'?), portanto this.updatedAt se refere a undefined.

Como atualizo o campo 'updatedAt' no gancho 'findOneAndUpdate'?

Devo adicionar mais documentos esclarecendo isso - o documento que está sendo atualizado pode não existir na memória quando você chama findOneAndUpdate , então o objeto this se refere à consulta em vez do documento no middleware de consulta . Experimente this.update({ $set: { updatedAt: Date.now() } });

@vkarpov15 Não consegui fazer this.update({ $set: { updatedAt: Date.now() } }); funcionar, mas consegui atualizar com sucesso o documento no gancho findOneAndUpdate com isto: this._update['$setOnInsert'].updatedAt=Date.now(); .

Eu recomendo não depender de ajustes no estado interno como esse. Estou muito surpreso que você não conseguiu fazer this.update() funcionar - você pode me mostrar como é o seu gancho?

Certo! (Observe que estou 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, eu poderia lidar com isso quando inicializar o documento em outro lugar do meu aplicativo, mas é bom ter tudo contido ali mesmo no Mongoose. Bem-vindo quaisquer pensamentos!

Sim, eu estava confuso e pensei que você estava usando update . O roteiro abaixo

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 corretamente e executa a consulta desejada:

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

Então, por favor, use

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

Em vez de manipular manualmente o estado interno do mquery - isso geralmente é uma má ideia, a menos que você realmente saiba o que está fazendo.

Isso está funcionando ou não? O único gancho que funciona para mim é 'salvar', o resto é completamente ignorado. Estou rodando no 4.0.6. Obrigado

@agjs forneça um exemplo 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();
                }
            });
        }
    });

});

Tenho outro pre-hook em outro modelo com model.create e pre-save e funciona normalmente.

Com qualquer atualização, ele simplesmente não vê o gancho, nem mesmo console.log. Eu tentei findOneAndUpdate também etc, realmente não funciona... Engraçado que eu passei por todo esse tópico e como costumo fazer, verifiquei a documentação oficial e vocês afirmam mesmo lá que funciona.

Qual é a relação entre CompanyAvatarSchema e CompanyDetails no código acima?

Os detalhes da empresa têm CompanyAvatar.schema como um subdocumento

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

Além disso, não é apenas pré-gancho, mas a validação também é ignorada completamente. Este subdocumento é preenchido, mas ignora a validação e o pré-gancho. Eu pesquisei tudo no Google, também tentei ISSO , mas nada parece funcionar. Quando eu apenas para testar, altero minha consulta para criar e invocar o modelo com new aka var parent = new Parent(), ele funciona.

Você está chamando CompanyDetails.update() mas o pre hook está definido em um esquema separado. O middleware de consulta não dispara o pre('update') atm do esquema aninhado.

Além disso, forneça um exemplo de código mais completo para o caso 'a validação é ignorada completamente' também.

Aqui está o esquema de avatar da minha empresa feito para validação e pré-ganchos para usuários que estão atualizando seu perfil (foto do 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;

Aqui está o esquema company_details em que company_avatar é um 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],

    }
});

E aqui está o controlador de perfil de atualização e o avatarPath que deve ser validado / conectado antes que esta atualização seja executada:

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

};

Basicamente, meu mongodb é preenchido com os campos de req.files.file, mas fora isso, a validação é ignorada e nenhum gancho está funcionando.

O problema é que o middleware pre('findOneAndUpdate') é definido em um esquema aninhado. No momento, o mongoose só dispara middleware de consulta para esquemas de nível superior, então middlewares definidos em CompanyDetailsSchema serão acionados re: #3125

+1

@vkarpov15 eu entendo isso

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

funciona para definir uma propriedade para um valor rígido, mas como eu _ler e alterar_ uma propriedade, por exemplo, hash uma senha?

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

Alguma ideia? Este é um caso de uso bastante comum, certo? Ou as pessoas geralmente encontram () e depois separam save () ?

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

Isso deve funcionar @willemmulder.

@vkarpov15 perfeito, obrigado! Vou tentar isso esta noite. Isso também deve funcionar para pre('update') certo?

@vkarpov15 Então eu tentei usando

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

e se eu console.log this eu recebo

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

o que parece bom (e eu até tentei definir essa propriedade diretamente antes), mas no final, ele não grava o valor com hash no banco de dados, mas simplesmente o valor 'bruto'. Existe algo que eu possa tentar?

Isso é estranho. Você pode tentar habilitar o modo de depuração do mangusto com require('mongoose').set('debug', true); e ver qual é a consulta que está sendo enviada para o banco de dados, o que pode esclarecer alguma coisa.

Obrigado pela sugestão. Acabei de fazer isso:

Eu corro isso:

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

que retorna isso para o console.log

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

e então isso para a depuração do 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 } }

Qualquer pista?

Não tenho certeza, abriu um novo problema para rastrear.

@vkarpov15 Obrigado, rastreará o outro problema.

@vkarpov15 Acho que a maneira correta de definir opções para a consulta em andamento no pré-hook seria algo como:

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

mas a documentação, http://mongoosejs.com/docs/api.html#query_Query -setOptions não menciona nenhuma dessas opções. Se isso fosse considerado uma solução hackish, o que seria mais apropriado?

Esse é um problema com os documentos, esse código que você descreve parece que deve funcionar à primeira vista

Você pode abrir uma questão separada para isso?

@vkarpov15 Sim, funciona. Acho que não fui claro o suficiente.

setOptions aplica new e runValidators corretamente, eu estava apenas perguntando se definir essas opções em setOptions deveria ser preferível a this.options .

setOptions() é preferível IMO, mas ambos devem funcionar. Ou você poderia simplesmente fazer

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

Isso atualizará a senha em cada chamada de update(). Então, se eu apenas alterar o valor de outras propriedades, ou seja, nome ou idade, a senha também será atualizada, o que não está correto!?

@nlonguit eu acho que sim. Mas você pode acessar através de this os campos que serão atualizados e poderia fazer 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 bem para mim. Obrigado @akoskm

Gostaria de saber se seria possível adicionar um pre hook para findByIdAndUpdate também. Seria bom ter os dois ganchos disponíveis.

Eu fiz assim e está funcionando: Basta findById e salvar sem atualizar nenhum campo e então usar o 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');
    });`

Estou tentando definir uma propriedade para ter o comprimento de uma matriz.

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

O comprimento correto é registrado, embora a consulta nunca defina totalNumberOfComments e o campo permaneça em 0 (já que o esquema referencia o padrão: 0).

Quando eu console.log(this) no final do gancho, posso ver que meu query contém o seguinte:

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

Embora quando eu ligo o modo de depuração, uma consulta nunca é registrada pelo Mongoose.

Há algo que estou fazendo de errado, ou isso é um bug?

@zilions this.findOneAndUpdate({}, { totalNumberOfComments: result.comments.length }).exec(); precisa realmente executar a consulta :) Apenas tenha cuidado, você obterá uma recursão infinita lá porque seu gancho de salvamento de postagem acionará outro gancho de salvamento de postagem

@vkarpov15 Ahhhh certo! Então eu posso usar this.update({} {....}).exec() em vez disso :)
Pergunta, porém, ao usar isso, ele define o campo totalNumberOfComments perfeitamente, embora também execute a atualização original do findOneAndUpdate .

Por exemplo:

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

Irá acionar o seguinte gancho:

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

Embora, o gancho irá $push para comments o myNewComment novamente, portanto, fazendo uma entrada duplicada.

Porque você está tecnicamente executando a mesma consulta -

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

é essencialmente o mesmo que

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

Se você quiser criar uma nova consulta do zero, basta fazer

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

apenas não toque no gancho de pré-salvamento,

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

Descobri que a ordem importa na qual você define um modelo e define um gancho pre . Permitam-me demonstrar:

Não 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 isso ajude alguém!

Acabei de descobrir a mesma coisa que @nicky-lenaers.

Funciona muito bem com 'safe' . 'delete' . etc. se você definir os ganchos após a definição do modelo.

Existe uma solução alternativa para definir um gancho 'findOneAndUpdate' após a definição do modelo?

@albert-92 não, não no momento

Para quem está tentando obter algo que costumava 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();
});

Isso deve funcionar

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

Não consigo postar ganchos para atualizar o documento na coleção.

`module.exports = function (mangusto) {
var meuEsquema = mangusto.Schema({
id: { type: Number, index: { unique: true } },
field1: { tipo: String },
field2: { tipo: String}
}, {
coleção: "mySchema",
versionKey: false
});

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

}`

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

Define o campo na coleção como { id:1, field1: 'test' ), mas deve ser {id: 1, field1: 'test', field2:'New Value'}
Não tenho certeza do que estou fazendo de errado

Eu posso alterar o resultado do findOneAndUpdate fazendo isso
mySchema.post('findOneAndUpdate', function (result) { result.field2 = 'something' });

Acho que pode ser que você esteja tentando atualizar o modelo com um elemento que já existe no modelo. Ou possivelmente você está selecionando errado. Tente imprimir "isto" em seu mySchema.post. Além disso, você não parece ter um done() ou next() em seu post. Eu não sou muito conhecedor do assunto, mas sei que imprimir isso pelo menos lhe dará uma idéia do que você está lidando.

O objetivo da atualização não é alterar um documento existente em seu modelo?

este é um objeto de consulta

Você não precisa fazer ou próximo em ganchos de postagem, tanto quanto eu entendo.

Bem, você tem this.model.update que é o esquema e não o modelo do objeto. Eu acho.. O que significa que você teria que usar

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

Isso parece um pouco atrasado para chamar uma função de modelo dentro do modelo. Como você pode usar apenas partes do objeto "this" que é fornecido a você. talvez seja melhor usar o findOneAndUpdate em vez de chamá-lo e, em seguida, chamar outra função de modelo em cima dele.
em um gerente

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

No meu exemplo acima eu usei this._update porque esse era o objeto de atualização que precisava ser usado fora disso.

Já tentei usar $set. Ainda não altera meu documento na coleção.

Onde posso encontrar todos os ganchos pré e pós disponíveis?

+1

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