Как добавить числа приращения к Slug с помощью Mongoose?

#javascript #node.js #express #mongoose

#javascript #node.js #выразить #mongoose

Вопрос:

Я использую mongoose в проекте nodejs. В нем я хочу сделать следующее: когда я сохраняю «slug» в db, я хочу проверить, существует ли slug, а затем добавить к нему счетчик перед сохранением. Итак, что-то вроде этого my-title my-title-2 my-title-3 …

Я создал приведенный ниже код промежуточного программного обеспечения express, который проверяет данный slug, выполняет проверку и добавляет к нему счетчик. Я нашел это из сообщения stackoverflow, но я думаю, что это работает только до моего-title-2. Если вы добавите еще один «my-title» slug, он перейдет в бесконечные рекурсионные вызовы. Ниже приведен код:

 module.exports.generateSlug = (req, res, next) => {
    // remove special chars, trim spaces, replace all spaces to dashes, lowercase everything
    var slug = req.body.slug
        .replace(/[^ws]/gi, "")
        .trim()
        .replace(/s /g, "-")
        .toLowerCase();
    var counter = 2;

    // check if there is existing release with same slug
    Release.findOne({ slug: slug }, checkSlug);

    // recursive function for checking repetitively
    function checkSlug(err, existingRelease) {
        if (existingRelease) {
            // if there is release with the same slug
            if (counter == 2)
                // if first round, append '-2'
                slug = slug.concat("-"   counter  );
            // increment counter on slug (eg: '-2' becomes '-3')
            else slug = slug.replace(new RegExp(counter     "$", "g"), counter);
            Release.findOne({ slug: slug }, checkSlug); // check again with the new slug
        } else {
            // else the slug is set
            req.body.slug = slug;
            next();
        }
    }
};
  

Я думаю, что ошибка в этой строке

 slug = slug.replace(new RegExp(counter     "$", "g"), counter);
  

Комментарии:

1. Независимо от того, как вы это делаете, если только поле slug каким-то образом не определено как уникальное в базе данных (например, с УНИКАЛЬНЫМ индексом в SQL; не уверен, что такое аналог MongoDB), это может привести к условиям гонки и, как таковым, столкновениям.

Ответ №1:

Просто понял это сам. Ниже приведен код 🙂

 module.exports.generateSlug = (req, res, next) => {
  var slug = req.body.slug
          .replace(/[^ws]/gi, "")
          .trim()
          .replace(/s /g, "-")
          .toLowerCase();

  // check if there is existing release with same slug
  Release.find({ slug: new RegExp("^"   slug, "i") }, (err, results) => {
    if (results == null) {
      next();
    } else {
      var count = results.length   1;
      req.body.slug = `${slug}-${count}`;
      next();
    }
  });
};
  

Комментарии:

1. Это приведет к ненужной выборке всех результатов из базы данных, и это не будет работать корректно, если у вас есть, например foo , foobar , fooquux в базе данных и ваш req.body.slug is foo .

2. Если в вашей БД у вас есть slug , и slug-3 тогда ваш код создаст дубликат slug-3

3. @CuongLeNgoc да, я только что это заметил. Это действительно сложно реализовать

Ответ №2:

Вот нерекурсивная переформулировка с использованием асинхронных функций.

Я повторю то, что я сказал в комментариях, хотя:

Независимо от того, как вы это делаете, если только поле slug каким-то образом не определено как уникальное в базе данных (например, с УНИКАЛЬНЫМ индексом в SQL; не уверен, что такое аналог MongoDB), это может привести к условиям гонки и, как таковым, столкновениям.

 /**
 * Find a free slug (or other value) by appending a counter to the given prefix and passing it
 * to the given `func` function, which should return a promise with a falsy value if the slug is free.
 * @param prefix Prefix for attempts.
 * @param func Predicate function.
 * @param maxAttempts Maximum number of attempts.
 * @returns {Promise<string|*>}
 */
async function findFreeSlug(prefix, func, maxAttempts = 10) {
  for (let i = 1; i < maxAttempts; i  ) {
    const slug = i > 1 ? `${prefix}-${i}` : prefix;
    const result = await func(slug);
    if (!result) {
      return slug;
    }
  }
  throw new Error(
    `Could not find a free slug for ${prefix} after ${maxAttempts} attempts`,
  );
}

/**
 * Return a promise that returns true if a Release with the given slug exists.
 * @param slug
 * @returns {Promise<boolean>}
 */
async function doesReleaseWithSlugExist(slug) {
  return new Promise((resolve, reject) => {
    Release.findOne({ slug }, (err, release) => {
      if (err) return reject(err);
      resolve(!!release);
    });
  });
}

module.exports.generateSlug = async (req, res, next) => {
  // remove special chars, trim spaces, replace all spaces to dashes, lowercase everything
  const initialSlug = req.body.slug
    .replace(/[^ws]/gi, "")
    .trim()
    .replace(/s /g, "-")
    .toLowerCase();

  req.body.slug = await findFreeSlug(initialSlug, doesReleaseWithSlugExist);
  next();
};