Mongoose: pre, post 中间件不在 findByIdAndUpdate 上执行

创建于 2012-06-15  ·  102评论  ·  资料来源: Automattic/mongoose

因为提出了一个 findAndUpdate 方法来减少下面的代码:

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

对此:

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

我们需要使用完全相同的 pre、post 中间件。 目前,当我制作 findByIdAndUpdate 时,不会执行 post 中间件。

最有用的评论

你好,

我知道问题已经结束,但我对答案并不完全满意。 findOneAndUpdate 和其他类似操作不应该至少有一个保存后或更新后中间件吗? 似乎没问题,因为文档已返回。 我同意,对于预中间件或 Model.update 来说是不可行的。

它将极大地提高插件的功能,例如 mongoosastic,这些插件目前对它应该能够支持的某些操作视而不见。

如果没有中间件,是否有人知道如何在插件中管理一些更新后操作?

谢谢

所有102条评论

按设计。 有涉及调用钩子的文档。

更正,没有文档可以调用钩子。

所以? 如果我想调用 pre, post 中间件我需要使用第一种方法吗?

对,那是正确的。 Model.update,findByIdAndUpdate,findOneAndUpdate,findOneAndRemove,findByIdAndRemove 都是直接在数据库中执行的命令。

这绝对应该在指南中澄清,特别是如果您在同一页面中谈论验证并将findByIdAndUpdate描述为“更好”

如果您单击“更好”链接,它会将您带到完整的文档
它解释验证等的方法。

随时发送拉取请求以添加您认为更好的内容。 它的
很容易做到这一点:
https://github.com/LearnBoost/mongoose/blob/master/CONTRIBUTING.md

2012 年 10 月 31 日星期三下午 6:03,Jesse Fulton [email protected]写道:

这绝对应该在指南中阐明http://mongoosejs.com/docs/documents.html
特别是如果您在同一页面中谈论验证并且
将 findByIdAndUpdate 描述为“更好”


直接回复此邮件或在 Gi tHub上查看 https://github.com/LearnBoost/mongoose/issues/964#issuecomment -9967865。

亚伦
@aaronheckmann https://twitter.com/#!/aaronheckmann

在中间件文档中添加了注释

拉取请求: https ://github.com/LearnBoost/mongoose/pull/1750

你好,

我知道问题已经结束,但我对答案并不完全满意。 findOneAndUpdate 和其他类似操作不应该至少有一个保存后或更新后中间件吗? 似乎没问题,因为文档已返回。 我同意,对于预中间件或 Model.update 来说是不可行的。

它将极大地提高插件的功能,例如 mongoosastic,这些插件目前对它应该能够支持的某些操作视而不见。

如果没有中间件,是否有人知道如何在插件中管理一些更新后操作?

谢谢

@albanm某些方法完全绕过猫鼬,所以你不会得到中间件钩子。 AFAIK,执行钩子的唯一方法是使用单独的find()save()调用,如上所述。

我明白了,这是有道理的。 Pre 中间件是没有问题的,因为在某些方法之前没有获取。 但是,猫鼬应该能够包装更新操作,这些操作也返回更新的文档并触发一些 post 钩子。

我认为@albanm是对的,人们在使用相同功能时可能想要相同的功能。 如何包装那些“直接更新”方法,并检查是否存在任何钩子? 如果钩子存在,则使用它,否则调用原始更新方法。

+1

+1

+1

+1

:+1:

+1

:-1:
我收到了功能请求,但是我使用中间件的时间越长,我发现自己在调用数据库更新脚本和其他对我的正常操作不常见的项目时经常需要绕过中间件。 通过使用静态方法,创建已经实现上述功能请求的自定义包装器变得非常容易。 由于 mongoose 现在对版本 4 的想法持开放态度,因此在 v3 中不太可能发生任何如此大规模的 API 更改。 我要求将其移至 v4 讨论。

但是,我会:+1:如果我能够在保存调用中禁用中间件。 我经常发现自己有一个从另一个函数传递的 mongoose 对象,并且只想执行 .save - 在这种情况下,执行 .save 比编写新查询更可取。 如果可以,请指出

无论哪种方式,令人惊叹的图书馆。 向出色的维护者致敬。 不知道没有它我将如何运作。

+1

为这些方法添加挂钩的一种简单方法是覆盖嵌入函数:

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

然后每次 mongoose 的更新调用都会修复数据的 updatedAt 键。

你可以试试这个模型的边缘。 它是一个简单的 mongoose 模型包装器,支持绑定静态/方法/覆盖到所有模式,并调用 rpc 方法来查询 mongodb。

+1,需要这个功能...

虽然我有点理解其中的原因,但恕我直言,缺乏原子更新的钩子使得 Mongoose 在实践中有些毫无意义。 当我使用原子更新时,不会执行任何验证、默认值等,因此使用 ODM 的整个目的都被破坏了。 使用 find/save 可以完成这项工作,但是否可以保证始终使用它?

此外,通常我会尽量避免查找/保存,因为它不是原子操作。 MongoDB 通过提供强大的原子查询和更新功能来弥补它对事务支持的不足。 所以我会使用这些原子操作,但没有中间件支持 Mongoose 不会提供比原生MongoClient更多的价值。

即使是http://aaronheckmann.tumblr.com/post/48943525537/mongoose-v3-part-1-versioning中的示例也会使用update ,因此绕过中间件。 我可以正确使用版本控制或中间件,但不能将两者结合起来? 真的,拥有它的意义在哪里?

我什至不完全理解技术原因:如果update & co. 围绕数据库操作,为什么我们不能拦截调用并传递查询对象,以便我们可以在实际进行更新之前进行一些验证/自定义?

@joerx +1 就足够了.. :) 但你的推理是完美的。

3.9.x 分支支持findfindOne的 pre 和 post 挂钩 - 应该很容易添加对findOneAndUpdateupdate的支持。

这个功能合并了吗?

所以pre('findOneAndUpdate')post('findOneAndUpdate')钩子在 master 中,还没有更新钩子。 目前还没有包含其中任何一个的版本。

那么它现在会在 .update() 之后触发预保存吗?

没有。 Query.update()有一个单独的update()挂钩。 保存挂钩不同于查询挂钩。

@vkarpov15您能否链接到更新挂钩的支持文档? 关于pre('findOneAndUpdate')post('findOneAndUpdate')何时发布的任何消息?

@karlstanton使用 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

好像落后了一步。 我错过了什么?

啊, new标志默认的重大更改。 虽然关于与底层驱动程序的一致性有一些话要说,但我不得不说这确实违反直觉,尤其是在考虑这个新钩子时。

@neverfox :)

的@neverfox很好。 此更改记录在发行说明中,您可以在 #2262 中看到更多讨论为什么会更改此更改。

原因是new在 mongodb 节点驱动程序、mongodb shell、实际的 mongodb 服务器等中默认为 false,并且包装层设置非标准默认值使开发人员的生活变得困难。 我的典型例子是gulp-uglify ,它覆盖了一堆 uglify-js 的默认值。

我看到问题已经解决了,但是 4.0.2 版本中的功能是否还在不稳定版本中? 它似乎没有在 findOneAndUpdate 与 Scema.pre('update') 或 Schema.pre('findOneAndUpdate') 与 4.0.2 版本上执行。 我错过了必须传递给函数的东西吗?

你是如何声明 pre hook @CaptainStaplerz的?

findOneAndUpdate 中间件挂钩在 4.0.2 中可用吗? 我从 3.8 升级到最新的 mongoose 4.0.2 以使用它并且中间件 Document.schema.post('findOneAndUpdate', 函数 (doc) 没有像 save() 或 remove() 那样被触发

@honitus给我看你的代码

@vkarpov15 - 感谢您的快速回复,给您

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

}

博客.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感谢您重新打开。 这是否意味着 findOneAndUpdate 的钩子在 4.0.2 中不存在,而您计划在 4.0.3 中包含。

@vkarpov15这是我声明挂钩的代码:

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

这就是我所说的更新:

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

据我所知findOneAndUpdate()钩子应该在那里,如果它不工作那是一个错误

@CaptainStaplerz尝试:

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

此外,是console.log语句正在执行,还是只是 Date.now() 部分给出了意想不到的结果?

@vkarpov15
我更改了我的源代码,添加了您在 Add implementation #964 https://github.com/Automattic/mongoose/commit/e98ef98e857965c4b2ae3339fdd7eefd2a5a9913中所做的更改

它现在就像魅力一样。 所以我认为修复程序没有检查到 main

@honitus你确定你使用的是猫鼬 4.0.2 吗? 该更改实际上已提交给 4.0.0 及更高版本。

honitus$ npm 查看猫鼬版本
4.0.2

@honitus您做错的是在编译模型向架构添加了钩子。 Model.schema.pre('remove');预计不起作用,请参阅模型文档上的“编译您的第一个模型” 。 首先将钩子附加到架构上,然后事情就可以正常工作了——这是我在您的代码和我们的测试之间看到的唯一区别。

@CaptainStaplerz我能找到重现您的代码的唯一方法是使用空更新。 下面的代码有效

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

但是,一旦您将更新设为空:

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

预更新挂钩不再执行。 这与您所看到的一致吗?

@vkarpov15
Model.schema.pre('remove'); 是角全栈自动创建的存根之一
我添加了实现#964 e98ef98
我所做的只是在下面添加一行
this._schema.s_.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,
请参阅 4.0.2 中的第 1526 行 - this.model.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,

将第 1526 行替换为 - this.schema.s.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,
它有效。

这是设计使然。 Mongoose 不应该允许在模型编译后修改模式挂钩(即mongoose.model()调用),这就是它不再起作用的原因。

@vkarpov15我设法让钩子现在使用以下内容,它一定是一个愚蠢的错字或其他东西:

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

然而,'this' 似乎并不指代更新的模型(但它在 'save' 钩子中?),因此 this.updatedAt 指的是未定义的。

如何更新“findOneAndUpdate”挂钩中的“updatedAt”字段?

我应该添加更多文档来澄清这一点 - 当您调用findOneAndUpdate时,正在更新的文档可能不存在于内存中,因此this对象指的是查询而不是查询中间件中的文档。 试试this.update({ $set: { updatedAt: Date.now() } });

@vkarpov15我无法让this.update({ $set: { updatedAt: Date.now() } });工作,但我能够成功地更新findOneAndUpdate钩子上的文档: this._update['$setOnInsert'].updatedAt=Date.now();

我强烈建议不要像那样调整内部状态。 我很惊讶你不能让this.update()工作 - 你能告诉我你的钩子是什么样的吗?

当然! (请注意,我使用的是 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();
  });
});

当我在我的应用程序的其他地方初始化文档时,我显然可以处理这个问题,但是很高兴将它全部包含在 Mongoose 中。 欢迎任何想法!

是的,我很困惑,以为你在使用update 。 下面的脚本

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

正常工作并执行所需的查询:

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

所以请使用

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

而不是手动操作 mquery 的内部状态——这通常是一个坏主意,除非你真的知道你在做什么。

这是否有效? 只有对我有用的钩子是“保存”,其余的完全被忽略了。 我在 4.0.6 上运行。 谢谢

@agjs请提供代码示例。

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

});

我使用 model.create 和 pre-save 对另一个模型进行了另一个预挂钩,它可以正常工作。

无论如何更新,它根本看不到钩子,甚至没有 console.log 它。 我也尝试了 findOneAndUpdate 等,但并没有真正起作用……有趣的是,我已经完成了整个线程,并且像往常一样,检查了官方文档,你们甚至声称它可以工作。

上面代码中的 CompanyAvatarSchema 和 CompanyDetails 是什么关系?

公司详细信息有 CompanyAvatar.schema 作为子文档

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

此外,它不仅是 pre-hook,而且验证也被完全忽略。 该子文档被填充但忽略了验证和预挂钩。 我已经用谷歌搜索了所有内容,也尝试了这个,但没有任何接缝可以工作。 当我只是为了测试更改我的查询以使用new aka var parent = new Parent() 创建和调用模型时,它确实有效。

您正在调用CompanyDetails.update()但预挂钩是在单独的架构上定义的。 查询中间件不会触发嵌套模式的pre('update') atm。

另外,请为您的“验证被完全忽略”的情况提供更全面的代码示例。

这是我的公司头像模式,用于为正在更新其个人资料(头像照片)的用户进行验证和预挂钩:

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

这是 company_details 架构,其中 company_avatar 是子文档:

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],

    }
});

这是更新配置文件控制器和应在此更新执行之前验证/挂钩的 avatarPath:

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

};

基本上,我的 mongodb 填充了 req.files.file 中的字段,但除此之外,验证被忽略并且没有钩子在工作。

问题是 pre('findOneAndUpdate') 中间件是在嵌套模式上定义的。 现在,mongoose 只为顶级模式触发查询中间件,因此在CompanyDetailsSchema上定义的中间件将被触发 re:#3125

+1

@vkarpov15我明白了

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

可以将属性设置为硬值,但我将如何_读取和更改_属性,例如散列密码?

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

有什么想法吗? 这是一个很常见的用例,对吧? 还是人们通常 find() 然后分开 save() ?

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

这应该工作@willemmulder。

@vkarpov15完美,谢谢! 今晚我会试试的。 这也应该适用于pre('update')对吗?

@vkarpov15所以我只是尝试使用

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

如果我 console.log this我得到

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

这看起来不错(我什至尝试过直接设置该属性)但最后,它实际上并没有将散列值写入数据库,而只是将“原始”值写入数据库。 有什么我可以尝试的吗?

真奇怪。 您可以尝试使用require('mongoose').set('debug', true);启用 mongoose 调试模式,并查看发送到数据库的查询是什么,这可能会有所帮助。

谢谢你的建议。 只是这样做:

我运行这个:

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

它为 console.log 返回这个

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

然后这个用于猫鼬调试

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

有什么线索吗?

不确定,开辟了一个新的问题来跟踪。

@vkarpov15谢谢,将跟踪其他问题。

@vkarpov15我认为在 pre hook 中为正在进行的查询设置选项的正确方法是:

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

但是文档http://mongoosejs.com/docs/api.html#query_Query -setOptions 没有提到任何这些选项。 如果这被认为是一种骇人听闻的解决方案,那么更合适的是什么?

这是文档的问题,您描述的代码乍一看应该可以工作

你能为此打开一个单独的问题吗?

@vkarpov15是的,它确实有效。 我想我不够清楚。

setOptions正确应用newrunValidators ,我只是问是否应该优先通过setOptions设置这些选项而不是this.options

setOptions()是更可取的 IMO,但两者都应该工作。 或者你可以做

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

这将在每次 update() 调用时更新密码。 因此,如果我只是更改其他属性的值,即姓名或年龄,那么密码也会被更新,这是不正确的!?

@nlonguit我想它会的。 但是您可以通过this访问将要更新的字段,您可以执行以下操作:

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

这段代码对我来说效果很好。 谢谢@akoskm

我想知道是否也可以为 findByIdAndUpdate 添加一个预挂钩。 最好有两个钩子可用。

我这样做并且它正在工作:只需 findById 然后保存而不更新任何字段,然后使用 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');
    });`

我正在尝试将属性设置为具有数组的长度。

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

记录了正确的长度,尽管查询从不设置 totalNumberOfComments,并且该字段保持为 0(因为架构引用默认值:0)。

当我在console.log(this)结束时,我可以看到我的query包含以下内容:

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

虽然当我打开调试模式时,Mongoose 永远不会记录查询。

是我做错了什么,还是这是一个错误?

@zilions this.findOneAndUpdate({}, { totalNumberOfComments: result.comments.length }).exec();需要实际执行查询:) 请注意,您将在那里获得无限递归,因为您的保存后挂钩将触发另一个保存后挂钩

@vkarpov15 啊啊啊对! 然后我可以改用this.update({} {....}).exec() :)
但是问题是,使用它时,它完美地设置了totalNumberOfComments字段,尽管也执行了findOneAndUpdate的原始更新。

例如:

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

将触发以下钩子:

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

虽然,钩子会再次将$pushcommentsmyNewComment ,因此会产生重复条目。

因为您在技术上执行相同的查询 -

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

本质上是一样的

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

如果您想从头开始创建新查询,只需执行

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

只是不要碰你的预存钩,

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

我发现定义模型和定义pre钩子的顺序很重要。 请允许我演示一下:

不工作:

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

是否有效:

// 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`);

希望这对任何人都有帮助!

我刚刚发现了和@nicky-lenaers 一样的东西。

它适用于'safe''delete' 。 等等,如果您在定义模型后定义挂钩。

定义模型后是否有解决方法来定义'findOneAndUpdate'挂钩?

@albert-92 目前不

对于任何试图获得曾经的东西的人

SCHEMA.pre('validate', function(done) {
    // and here use something like 
    this.yourNestedElement 
    // to change a value or maybe create a hashed character    
    done();
});

这应该工作

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

我无法获取帖子挂钩来更新集合中的文档。

`module.exports = 功能(猫鼬){
var mySchema = mongoose.Schema({
id: { type: Number, index: { unique: true } },
字段1:{类型:字符串},
字段2:{类型:字符串}
}, {
集合:“mySchema”,
版本密钥:假
});

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

}`

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

将集合中的字段设置为 { id:1, field1: 'test' ) 但应该是 {id: 1, field1: 'test', field2:'New Value'}
不知道我做错了什么

我可以通过这样做来改变 findOneAndUpdate 的结果
mySchema.post('findOneAndUpdate', function (result) { result.field2 = 'something' });

我认为您可能正在尝试使用模型上已经存在的元素来更新模型。 或者可能是你选错了。 尝试在 mySchema.post 中打印出“this”。 此外,您的帖子中似乎没有 done() 或 next() 。 我对这个主题不是很了解,但我知道打印出来至少会让你知道你正在处理什么。

更新的目的不是更改模型中的现有文档吗?

这是一个查询对象

据我了解,您不需要在帖子挂钩中完成或下一步。

那么你有 this.model.update 这是模式而不是对象的模型。 我想..这意味着你必须使用

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

在模型内部调用模型函数似乎有点倒退。 因为您可以只使用提供给您的“this”对象的一部分。 你最好只使用 findOneAndUpdate 而不是调用它,然后在它之上调用另一个模型函数。
在经理

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

在我上面的示例中,我使用了 this._update 因为这是需要使用的更新对象。

我尝试过使用 $set。 它仍然不会更改我在集合中的文档。

我在哪里可以找到所有可用的前后挂钩?

+1

此页面是否有帮助?
0 / 5 - 0 等级