Пн goose: последовательно сохранять порядок документов - PullRequest
1 голос
/ 31 марта 2020

Предположим, у нас есть такая схема:

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

Мы хотим, чтобы order всегда было уникальным числом от 0 до n-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 ]

1 голос
/ 31 марта 2020

Вы можете использовать findOneAndDelete pre и post ловушки для выполнения sh this.

Как вы видите в ловушке 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
    }
]

При удалении content2 с "_id": ObjectId("5e830a6d0dec1443e82ad282") с методом findOneAndDelete, например:

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();
});
0 голосов
/ 01 апреля 2020

Основываясь на ответе SuleymanSah, я написал плагин mon goose, который делает эту работу. Таким образом, его можно применять к нескольким схемам без ненужного дублирования кода.

У него есть два необязательных аргумента:

  • 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 } }
    )
  })
}
...