【问题标题】:Mongoose custom validation of several fields on updateMongoose 在更新时对多个字段进行自定义验证
【发布时间】:2017-01-03 01:28:53
【问题描述】:

首先,this 没有帮助。

假设我们有一个用户模型:

const schema = new mongoose.Schema({
    active: { type: Boolean },
    avatar: { type: String }
});

const User = mongoose.model('User', schema);

当我们更新它时(设置头像):

// This should pass validation
User.update({ _id: id }, { $set: { avatar: 'user1.png' } });

我们希望根据当前(或更改的)active 属性值对其进行验证。

案例#1

  • activefalse
  • 我们不应该能够设置头像 - 它不应该通过验证

案例#2

  • activetrue
  • 我们应该能够设置头像 - 它应该通过验证

想法

  1. 使用自定义验证器
const schema = new mongoose.Schema({
    active: { type: Boolean },
    avatar: { type: String, validate: [validateAvatar, 'User is not active'] }
});

function validateAvatar (value) {
    console.log(value); // user.avatar
    console.log(this.active); // undefined
}

所以这不起作用,因为我们无权访问active 字段。

  1. 使用预“验证”挂钩
schema.pre('validate', function (next) {
    // this will never be called
});

此钩子不适用于update 方法。

  1. 使用预“更新”挂钩
schema.pre('update', function (next) {
    console.log(this.active); // undefined
});

这对我们不起作用,因为它无法访问模型字段。

  1. 使用发布“更新”挂钩
schema.post('update', function (next) {
    console.log(this.active); // false
});

这个可行,但在验证方面不是很好的选择,因为该函数仅在模型已保存时才被调用。

问题

那么有没有办法在使用model.update() 方法的同时,在保存模型之前根据几个字段(保存在数据库和新字段中)验证模型?

总结一下:

  1. 初始用户对象
{ active: false, avatar: null }
  1. 更新
User.update({ _id: id }, { $set: { avatar: 'user1.png' } });
  1. 验证应该有权访问
{ active: false, avatar: 'user1.png' }
  1. 如果验证失败,则不应将更改传递给 DB

【问题讨论】:

    标签: javascript node.js mongodb validation mongoose


    【解决方案1】:

    由于使用 update() 的限制,我决定以这种方式解决问题:

    • 使用自定义验证器(问题中提到的想法 #1)
    • 不要使用update()

    所以不是

    User.update({ _id: id }, { $set: { avatar: 'user1.png' } });
    

    我用

    User.findOne({ _id: id })
        .then((user) => {
            user.avatar = 'user1.png';
            user.save();
        });
    

    在这种情况下,自定义验证器按预期工作。

    附:我选择这个答案作为我的正确答案,但我会奖励最相关的答案。

    【讨论】:

      【解决方案2】:

      您可以使用mongoose documentation 中指定的上下文选项来执行此操作。

      上下文选项

      上下文选项允许您在更新验证器中设置 this 的值 到底层查询。


      因此,在您的代码中,您可以像这样在路径上定义您的validator
      function validateAvatar (value) {
          // When running update validators with the `context` option set to
          // 'query', `this` refers to the query object.
          return this.getUpdate().$set.active;
      }
      
      schema.path('avatar').validate(validateAvatar, 'User is not active');
      

      更新时您需要输入两个选项runValidatorscontext。所以你的更新查询变成:

      var opts = { runValidators: true, context: 'query' };
      user.update({ _id: id }, { $set: { avatar: 'user1.png' }, opts });
      

      【讨论】:

      • 感谢您的建议,但它不起作用。 this.getUpdate().$set 是一个更新查询对象,在本例中为 { avatar: 'user1.png' }。所以它仍然没有当前的active 属性。
      【解决方案3】:

      您是否尝试给 active 一个默认值,这样它就不会在 mongodb 中未定义。

      const schema = new mongoose.Schema({
      active: { type: Boolean, 'default': false },
      avatar: { type: String,
                trim: true,
                'default': '',
                validate: [validateAvatar, 'User is not active']
      }});
      
      function validateAvatar (value) {
          console.log(value); // user.avatar
          console.log(this.active); // undefined
      }
      

      创建的时候你是这样设置用户的吗

        var User = mongoose.model('User');
        var user_1 = new User({ active: false, avatar: ''});
        user_1.save(function (err) {
                  if (err) {
                      return res.status(400).send({message: 'err'});
                  }               
                  res.json(user_1);                
              });
      

      【讨论】:

        【解决方案4】:

        您可以尝试使用预“保存”挂钩。我以前用过,可以得到“this”中的值。

        schema.pre('save', function (next) {
            console.log(this.active);
        });
        

        希望这对你也有用!

        【讨论】:

          【解决方案5】:

          您必须为此使用asynchronous custom validator

          const schema = new mongoose.Schema({
            active: { type: Boolean },
            avatar: {
              type     : String,
              validate : {
                validator : validateAvatar,
                message   : 'User is not active'
              }
            }
          });
          
          function validateAvatar(v, cb) {
            this.model.findOne({ _id : this.getQuery()._id }).then(user => {
              if (user && ! user.active) {
                return cb(false);
              } else {
                cb();
              }
            });
          }
          

          (并将 runValidatorscontext 选项传递给 update(),正如 Naeem 的回答中所建议的那样)。

          但是,这将需要对每次更新进行额外查询,这并不理想。

          作为替代方案,您也可以考虑使用类似的东西(如果不能更新非活动用户的限制比实际验证更重要):

          user.update({ _id : id, active : true }, { ... }, ...);
          

          【讨论】:

          • “两个更新语句完全相同” - 请查看两种情况下描述的active 属性。这是不同的,这是主要的。
          • @Leestex 但您可能已经注意到,因此您的问题令人困惑。
          • 我已经更新了这部分问题,希望现在不会那么混乱
          • 当我创建一条记录时,它说 getQuery() 不是一个函数
          • @AliAbbas 我认为getQuery() 仅在更新验证器中可用。但是如果你不能解决你的问题,你可能应该创建一个新问题。
          猜你喜欢
          • 2015-07-20
          • 2023-03-08
          • 2012-11-30
          • 2018-06-29
          • 2013-08-06
          • 2016-06-08
          • 2017-06-21
          • 2016-09-13
          • 1970-01-01
          相关资源
          最近更新 更多