不知道怎么对 Mongoose 里面的 Ref 路径进行搜索?一个插件快速解决问题

345 阅读1分钟

假设你也在使用 Mongoose,类似这样的 Schema 也许是很常见的吧!

// Todo Schema
{
    name: String,
    user: { type: mongoose.Types.ObjectId, ref: 'User' },
    parent: { type: mongoose.Types.ObjectId, ref: 'Todo' },
}
// User Schema
{
    name: String,
}

那么问题来了,我要找用户名字是张三的 Todo 该怎么找?

你可能直觉反应是这么写:

Todo.find({'user.name':'张三'})
Todo.find({ user:{ name:'张三' }})

但是你很快就会发现不对劲。

Todo.find({'user.name':'张三'})返回的是空数组,Todo.find({ user:{ name:'张三' }}) 直接报错了!

CastError: Cast to ObjectId failed for value "{ name: '张三' }" (type Object) at path "user" for model "Todo"

这是因为,'user.name' 的形式 Mongoose 读取到 Schema 里面没有这样的字段,就忽略了; user:{ name:'张三' } 的形式 Mongoose 读取到 userObjectId,就把 { name:'张三' }ObjectId 解析了,所以就会报错。

哪怕你设置了自动填充也没用,因为填充是 Mongoose 层面的操作,而不是 MongoDB 的。

那么我们怎么解决这个问题呢?

假设你只是对单个 Model 的少数 Ref 路径有这个需求,那么你可以给每个路径创建一个对于的方法:

schema.statics.findByUserName = async function(name){
    const { _id } = await User.findOne({name},'_id').exec()
    return await Todo.find({ user:_id })
}

原理就是事先把符合条件的 ObjectId 拿到手,再判断 Todo 的 user 在不在里面。

但是你要是嫌麻烦,或者干脆就是想要一个通用的自动搜索功能,那么你可以安装我编写的 mongoose-find-by-reference 插件:

npm i -S mongoose-find-by-reference

接着使用它:

...
// Require mongoose-find-by-reference
const { MongooseFindByReference } = require('mongoose-find-by-reference');
...
// 把插件应用在对应的 Schema 上
TodoSchema.plugin(MongooseFindByReference);

接着,你可以这么用:

const result = await todoModel
  .find({
    $and: {
      parent: {
        'user.name': '李四',
      },
      user: {
        name : '张三',
      }
    },
  })
  .exec();

搜索条件就会被插件自动替换成:

const newConditions = {
  $and: {
    parent: {
      $in: [/* 符合条件的 Todo 的 ObjectId 数组 */],
    },
    user: {
      $in: [/* 符合条件的 User 的 ObjectId 数组 */],
    },
  },
};

就此,大功告成!

假如你喜欢的话,也可以去 GitHub 上面给我个 Star,有任何问题也欢迎提 Issue 哦~