mongoose预验证钩子未触发。

6
我正在尝试使用mongoose创建一个模型,然后再创建另一个模型并保存对原始模型的引用。我一直在查阅mongoose的中间件和钩子文档,但其中一些似乎没有被触发。 这个答案告诉我为什么我的init钩子不会触发HERE,pre和post init仅在从数据库加载预先存在的模型时才会触发。因此,我知道validate将在创建新模型时运行。因此,我从pre init切换到pre validate。 以下是我想要做的代码:
GroupSchema.pre('validate', function (next, data) {
    console.log("inside pre validate");
    if (data.location) {
        location = new Location(data.location);
        data.location = location._id;
        location.save(function (err) {
            if (err) handleError(err);
        });
    }

    next();
})

我知道因为文档还没有填充数据,所以我不能使用this。这就是为什么我有了data,但这似乎仍然不起作用,哦,我从这个答案中弄清楚了我应该传递哪些参数。

非常感谢您的帮助。

**************更新**************

为了更好地说明问题,根据一个答案中的建议,将我的pre validate函数更改为:

GroupSchema.pre('validate', function (next, data) {
    console.log("inside pre validate", this);
    if (this.location) {
        location = new Location(this.location);
        this.location = location._id;
        location.save(function (err) {
            if (err) handleError(err);
        });
    }

    next();
})

我收到了以下错误信息
service err: CastError: Cast to ObjectId failed for value "[object Object]" at path "location"

这是有道理的,因为在我的模型中它希望传递一个ObjectId而不是我所传递的[object, Object]。然而,我想我可以先保存location,获取生成的ObjectId并将其存储在组模型中,然后再抛出错误。因此最初使用了preinit,直到发现这样做行不通,现在发现prevalidate也不起作用。有没有办法实现我所尝试的呢?
以下是我尝试的步骤:
1. 创建位置 2. 获取新的ObjectId 3. 将新的位置ObjectId存储在组中而不是对象本身
我试图将这个过程添加到一个pre挂钩中,这样我就可以把这段代码放在一个地方,当新建一个组模型时,它会自动处理这个操作。

位置: [{类型: ObjectId, 引用: '位置'}],这是对另一个集合的引用。 - gmaniac
嗯...它是一个数组并不完全符合你的代码,在其中你将data.location传入new Location调用,然后使用data.location = location._id;进行赋值。 - JohnnyHK
我明白你的意思,我正在将应该是数组的东西变成了一个变量。然而,这不是我遇到的问题。pre validate 根本没有被触发。所以如果那是个问题,它并没有出现,因为它甚至没有达到那个点。 - gmaniac
实际上,我认为恰恰相反。因为“位置”不是正确的类型,所以在到达您的自定义验证之前,验证就已经失败了。 - JohnnyHK
pre validate 会在模型实际验证之前触发吗? - gmaniac
显示剩余2条评论
3个回答

7

您尝试做的事情并不适合使用validate或任何其他中间件,因为您需要提供位置数据以及创建分组所需的内容。

更好的方法是在GroupSchema上创建一个静态方法,显式执行此增强的创建功能。

因此,假设基本的位置和分组模式如下:

var LocationSchema = new Schema({
    city: String,
    state: String
});

var GroupSchema = new Schema({
    name: String,
    location: {type: ObjectId, ref: 'Location'}
});

在组模式中添加一个静态方法并注册模型:

GroupSchema.statics.createWithLocation = function(group, location, callback) {
    Location.create(location, function(err, location) {
        if (err) return callback(err);
        group.location = location._id;
        Group.create(group, callback);
    });
};

var Location = mongoose.model('Location', LocationSchema);
var Group = mongoose.model('Group', GroupSchema);

通过分别传递组和位置数据参数来调用新方法:

Group.createWithLocation(
    {name: 'group1'}, 
    {city: 'New York', state: 'NY'},
    function(err, group) {
        console.log('group created with location: ', group);
    }
);

1
我同意,使用中间件无法实现我想要的功能。 - gmaniac

1

pre(validate) 在 ".save" 之前触发,注意数据没有当前文档。"this" 变量将拥有它。还要注意,每当我们执行 ".save" 时,.pre(validate) 将触发。以下是如何实现您所需的示例。我已经解决了它,它完美地工作。

   var mongoose = require('mongoose');
    var Schema = mongoose.Schema;
    //group schema
    var GroupSchema = new Schema({
        location: String,
        pass: String
    });
    //dependant location schema
    var LocationSchema = new Schema({
        city: String
    });

  //here I compile it into a model -groupschema
    var Groupuser = mongoose.model('usertest', GroupSchema, 'usertest');
    //here I compile it into a model-location schema
    var Location = mongoose.model('locationtest', LocationSchema, 'locationtest');


    //here is the pre validate,note this will trigger when we save groupschema realted documents.

    GroupSchema.pre('validate', function(next, data) { //will be triggered before the save
        console.log("inside pre validate", this); //the this will have amsterdam,and test see below.
        if (this.location) {//will be amsterdam
            var location = new Location();
            location.city = this.location//adding amsterdam to the location.city 
            location.save(function(err, data) {//this data will have the location document that we just saved.
            });
        }
        next();
    });

    //now I create a new user for groupschema
    var newuser = new Groupuser();
    newuser.location = 'amsterdam';
    newuser.pass = 'test'
    newuser.save(function(err) { //Before this save function does the save,it triggers the .pre(validatE)
        if (err) return console.log(err);//
    });

我可能没有完全按照您的期望实现内部位置功能,但我确信 .pre(validate) 只会在您执行 .save 时触发。它可以是一个新文档或一个现有文档。另请注意,它不会在加载模型时触发。
1)we define a schema (var schema=new Schma({datatypes})
2)we compile it into a model(var model=mongoose.model('collectionname',schema)
3)we instantiate a new variable of the model(var user=new model(); and then define the values
4) finally we save the data(user.save()->its here that the pre.validate triggers
Hope its clear now

我已经编辑了我的答案并提供了更多信息。希望你能理解我想说的话,也希望这正是你所寻找的。 - harryy000
谢谢您的回答,现在我遇到的错误是服务错误:CastError: Cast to ObjectId failed for value "[object Object]" at path "location"。我知道为什么会这样做,我希望在它抛出错误之前能够捕获它。它需要一个ObjectId而不是一个[object, object],这就是为什么我最初想要看看是否可以使用pre init,希望在它对我生气之前保存位置并设置新的ObjectId。这有意义吗? - gmaniac

0

很可能你的问题是在实际保存位置之后调用了next(),因为它是异步调用的。

如果你将next()添加到save()内部,我相信你应该能够解决你的问题。

GroupSchema.pre('validate', function (next, data) {
  console.log("inside pre validate", this);
  if (this.location) {
    location = new Location(this.location);
    this.location = location._id;
    location.save(function (err) {
        if (err) handleError(err);
        next();
    });
  } else {
    next();
  }
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接