猫鼬:始终保存文档顺序

时间:2020-03-31 09:06:21

标签: mongodb mongoose mongoose-schema

假设我们有一个这样的模式:

const PageSchema = new mongoose.Schema({
  content: String
  order: Number
})

我们希望order始终是0n-1之间的唯一数字,其中n是文档总数。

在插入或删除文档时如何确保这一点?

对于插入物,我目前使用此钩子:

PageSchema.pre('save', async function () {
  if (!this.order) {
    const lastPage = await this.constructor.findOne().sort({ order: -1 })
    this.order = lastPage ? lastPage.order + 1 : 0
  }
})

这似乎在插入新文档时起作用。 删除文档后,我必须减少order较高的文档的order。但是,我不确定删除文档时会调用哪个挂钩。

效率对我来说不是问题:插入和删除的次数不多。 如果我能以某种方式仅提供一个对整个集合进行迭代的功能,例如fix_order,那完全可以。如何安装此功能,以便在插入或删除文档时调用它?

2 个答案:

答案 0 :(得分:1)

您可以使用findOneAndDelete前后钩子来完成此操作。

正如您在pre findOneAndDelete挂钩中所看到的,我们保存了对已删除文档的引用,并将其传递给postfindOneAndDelete,以便我们可以使用构造函数访问模型,并使用updateMany方法调整订单。

PageSchema.pre("findOneAndDelete", async function(next) {
  this.page = await this.findOne();
  next();
});

PageSchema.post("findOneAndDelete", async function(doc, next) {
  console.log(doc);

  const result = await this.page.constructor.updateMany(
    { order: { $gt: doc.order } },
    {
      $inc: {
        order: -1
      }
    }
  );

  console.log(result);

  next();
});

假设您有以下3个文档:

[
    {
        "_id": ObjectId("5e830a6d0dec1443e82ad281"),
        "content": "content1",
        "order": 0,
        "__v": 0
    },
    {
        "_id": ObjectId("5e830a6d0dec1443e82ad282"),
        "content": "content2",
        "order": 1,
        "__v": 0
    },
    {
        "_id": ObjectId("5e830a6d0dec1443e82ad283"),
        "content": "content3",
        "order": 2,
        "__v": 0
    }
]

当您使用带有这样的findOneAndDelete方法的"_id": ObjectId("5e830a6d0dec1443e82ad282")删除content2时:

router.delete("/pages/:id", async (req, res) => {
  const result = await Page.findOneAndDelete({ _id: req.params.id });
  res.send(result);
});

中间件将运行,并调整顺序,其余2个文档将如下所示:

[
    {
        "_id": ObjectId("5e830a6d0dec1443e82ad281"),
        "content": "content1",
        "order": 0,
        "__v": 0
    },
    {
        "_id": ObjectId("5e830a6d0dec1443e82ad283"),
        "content": "content3",
        "order": 1,    => DECREASED FROM 2 to 1
        "__v": 0
    }
]

此外,您最好将下一个包含在预保存的中间件中,以便其他中间件也可以在以后添加时使用。

PageSchema.pre("save", async function(next) {
  if (!this.order) {
    const lastPage = await this.constructor.findOne().sort({ order: -1 });
    this.order = lastPage ? lastPage.order + 1 : 0;
  }
  next();
});

答案 1 :(得分:0)

根据SuleymanSah的回答,我编写了一个猫鼬插件来完成这项工作。这样,它可以应用于多个架构,而无需重复代码。

它有两个可选参数:

  • path:要存储序号的路径名(默认为order
  • scope:相对于应该给出数字的路径名或路径名数组(默认为[])

示例。不应对章节进行全局编号,而应相对于其所属的书进行编号:

ChapterSchema.plugin(orderPlugin, { path: 'chapterNumber', scope: 'book' })

文件orderPlugin.js

function getConditions(doc, scope) {
  return Object.fromEntries([].concat(scope).map((path) => [path, doc[path]]))
}

export default (schema, options) => {
  const path = (options && options.path) || 'order'
  const scope = (options && options.scope) || {}

  schema.add({
    [path]: Number,
  })

  schema.pre('save', async function () {
    if (!this[path]) {
      const last = await this.constructor
        .findOne(getConditions(this, scope))
        .sort({ [path]: -1 })
      this[path] = last ? last[path] + 1 : 0
    }
  })

  schema.post('findOneAndDelete', async function (doc) {
    await this.model.updateMany(
      { [path]: { $gt: doc[path] }, ...getConditions(doc, scope) },
      { $inc: { [path]: -1 } }
    )
  })
}