首页 > 解决方案 > Mongoose.js 唯一验证

问题描述

注意:问题底部有一个编辑:

我可以使用 Mongoose.js 模型文件中的自定义验证器或预挂钩来检查数据库的唯一性吗?我知道我可以在控制器中检查它,但我宁愿将它与其他验证器一起放入模型文件中,以保持一致性。

我也知道有一个名为 mongoose-unique-validator 的 npm 包可以执行此操作,但我不喜欢安装一个库来执行应该是一到五行代码顶部的操作。

Mongoose 还有一个“唯一”属性,如果该项目不是唯一的,它将引发错误。但他们的文件清楚地表明这不是验证器。并且它抛出的错误不会与验证错误相同。

这是模型文件的相关部分。这将检查数据库,如果没有 dup 则创建文章,但如果有 dup 则会引发错误,但不会引发我想要的验证错误。如果我只是在有 dup 的情况下返回 false,它只会忽略验证并创建重复的文章。这无疑与 Promises/Async 有关。以下是相关的 Mongoose 文档https://mongoosejs.com/docs/validation.html#async-custom-validators。他们谈论了唯一属性如何不是验证器https://mongoosejs.com/docs/faq.html

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const articleSchema = new Schema({
  title: {
    type: String,
    required: [true, "Title is required"],
    // unique: true, 
    // isAsync: true,
    validate: {
      validator: function(value) {
        this.constructor.findOne({title: value}, (err, article) => {
          if (err || !article) { 
            return true;
          } else {
            // return false;
            throw new Error('Duplicate');
          }
        });
      },
      message: (props) => `Title "${props.value}" is already in use.`
    },
  },
  content: { type: String, required: true }
});

编辑:我想通了,但它只适用于创建新文章,而不是更新。所以这个问题仍然悬而未决,但重点是如何让它在更新上工作。在更新时,Mongoose 不会像在创建时那样将“this”视为文档对象。相反,“this”是请求对象,“this.constructor.findOne()”会抛出错误“this.constructor.findOne is not a function”。这是修改后的验证器:

  title: {
    type: String,
    required: [true, "Title is required"],
    isAsync: true,
    validate: {
      validator: async function(value) {
        const article = await this.constructor.findOne({title: value});
        if (article) {
          throw new Error(`${value} is already in use.`);
        }
      }
    }
  }

标签: node.jsvalidationmongoose

解决方案


您的validator函数只会运行脚本,它不会向 mongoose 传递任何回调或承诺,因此 mongoose 假设验证器返回 true 并继续该过程。根据文档,您应该返回承诺或使用回调。

承诺:

validator: function(value) {
    var here = this;
    return new Promise(function(resolve, reject) {
        here.constructor.findOne({title: value}, (err, article) => {
        if (err || !article) { 
          resolve(true);
        } else {
          resolve(false);
        }
      });
    })
}

回调:(需要设置isAsync: true

validator: function(value, cb) {
      this.constructor.findOne({title: value}, (err, article) => {
        if (err || !article) { 
          cb(true);
        } else {
          cb(false, "Content is used");
        }
     });
}

推荐阅读