4

I would like to use a mongoose middleware hook to re-try saving a document in case the initial save failed with a duplicate key error. The use case is as follows:

My model uses slugs for identification which are automatically generated. E.g. item, item-2, item-3, …, item-n. In case, item already exists, a counter should be added to the slug. I cannot check the "next" slug in advance, as I need to avoid conflicts under any circumstance.

As this logic will be involved in several different models, I would like to isolate it into a mongoose plugin.

Is the idea at all possible? E.g. schema.post('save', handler) only get executed on a successful save. Are there any other hooks, which I can exploit?

qqilihq
  • 10,794
  • 7
  • 48
  • 89

2 Answers2

0

I'm using pre save hook to check if the slug already exists with the following code and so far it seems to work just fine.

pageSchema.pre('save', function(next){
    var page = this;

    page.createdDT = new Date();
    page.updatedDT = new Date();

    page.slug = page.title.slug(); // create slug from title

    var re = new RegExp(page.slug, 'i');

    mongoose.models["page"].find({slug: { $regex: re}}, function(err, pages){
        // slug doesn't exist, good to go
        if(!err && !pages) return next();

        var slugs = [];

        // let's get all slugs
        pages.forEach(function(page){
            slugs.push(page.slug);
        });

        // keep increasing `i` until slug is unique
        // set i to 1 to avoid hello-0 
        var i = 1;
        var tempSlug = page.slug;
        while(slugs.indexOf(tempSlug) >= 0){
            tempSlug = page.slug + '-' + i;
            i++;
        }

        //unique slug for example "hello-2"
        page.slug = tempSlug;
        next();
    }); 
});
Molda
  • 5,619
  • 2
  • 23
  • 39
  • 2
    Thank you for your suggestion. The potential problem I'm having with this solution is, that checking for available slugs and saving the new document is not atomic. Honestly, this is not a *huge* issue currently, but I wanted to avoid future pitfalls from the beginning. – qqilihq Nov 26 '15 at 11:15
0

I finally went with the solution used by mongoose-uniqueslugs, which I adapted to our needs. While this variant does not solely work with pre/post hooks, it ensures atomicity (i.e. not checking for available slugs in advance and then saving, but simply re-trying).

The central idea is to override the model's save function (see enhanceModel function) and provide a wrapper which catches unique errors by slug collisions and then re-trying to save (instead of the random string appended, we want for a sequential number approach).

qqilihq
  • 10,794
  • 7
  • 48
  • 89