4

I was just playing around the concepts of sailsjs then I came to know we cannot use auto increments in sails if we are using mongodb as our database. I cannot able to use the auto increments even for the non primary key attributes. Is there any special methods to use auto increments operations for an attribute which is not a primary key? Thanks in advance

Anandapriyan S.D
  • 315
  • 4
  • 15

4 Answers4

9

If waterline doesn't support it automatically, which is what it seems like, you can do it in the beforeCreate lifecycle callback of waterline. It is independent of any database adapter.

I would recommend you to take a look at how lifecycle callbacks work for clear understanding. Workflow would be something like following. Before creating any record you would check the count of records of the Model. Then update the x field of the records to be created as one more than that found count and pass the batton.

beforeCreate: function(obj, next){
    Model.count().exec(function(err, cnt){
        if(err) next(err);
        else{
            obj['x'] = cnt + 1;
            next(null);
        }
    })
}

Counting the records is not the perfect way. You can change the way you like to find the value of auto-incremented value. Purpose of this example is to give you intuition of how it can be done. This is not the exact alternative of autoincrement but it's an effective workaround I guess. Hope it helps you.

taufique
  • 2,701
  • 1
  • 26
  • 40
  • Cool solving manner! Something weird but it works and solve the problem easily! Thanks! – alexventuraio Dec 06 '15 at 20:42
  • 1
    Where is the `Model` var ? – throrin19 Apr 27 '16 at 08:58
  • While the beforeCreate recommendation is sound, using the count() method is not, because (a) the values of obj['x'] may not coincide with the count, (b) if records get deleted the values will be thrown off and all records need to be updated to be accurate. Better method: Do a find() and sort the results based on an integer column, pick the first result, then increment that! – Joshua Mar 24 '17 at 16:17
  • The only safe way to use auto increment with mongodb is to have a counter collection where you store other collections auto increment index. – kailniris May 25 '17 at 12:49
7

There are different strategies, how you can create auto increment sequences with mongo. You can find the general information in the official documentation http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/

Here is an adaptation of the first approach for sails.js waterline, which uses counter collection. First you have to create a Sequence Model and implement a get next method.

module.exports = {
    attributes : {
        num : {
            type : "integer"
        },
    },

    next : function (id, cb) {

        Sequence.native(function (err, col) {

            col.findAndModify(
                { _id: id },
                [['_id', 'asc']],
                {$inc: { num : 1 }},
                { new: true, upsert : true}
                , function(err, data) {

                    cb(err, data.value.num);
                });

        });

    },
};

Since waterline doesn't support findAndModify functionality of mongodb, you have to make use of native driver method. It looks bit weird, but it works. More about it here.

And then you can just call the Sequence.next() in the beforeCreate lifecycle callback method of your model to get next auto increment value for the new collection document.

// Model Order, for Example.
module.exports = {

    attributes: {

        number: {
            type: "integer"
        },
        // other attributes
    },

    // add auto increment value for "number" before create a document
    beforeCreate : function (values, cb) {

        // add seq number, use
        Sequence.next("order", function(err, num) {

            if (err) return cb(err);

            values.number = num;

            cb();
        });
    }

    // your other methods ...
};

I know it is a bit late, but I just solved it for me and wanted to share.

devza
  • 189
  • 1
  • 3
2

Here's a solution that works with Sails v1

// api/models/Sequence.js

module.exports = {
  attributes: {
    num: { type: 'number' }
  },
  next (id, cb) {
    var db = Sequence.getDatastore().manager
    var collection = db.collection('num')

    collection.findAndModify(
      { _id: id },
      [[ '_id', 'asc' ]],
      { $inc: { num : 1 }},
      { new: true, upsert : true },
      (err, data) => cb(err, data.value.num)
    )
  }
}
// api/models/Order.js

module.exports = {
  attributes: {
    number: { type: 'number' },
  },
  beforeCreate (obj, cb) {
    Sequence.next('order', (err, num) => {
      if (err) return cb(err)
      obj.number = num
      cb()
    })
  }
}
Aronanda
  • 191
  • 1
  • 2
  • 3
1

For anyone interested in the implementation with Native in Sails, here it's a working sample. The sample assumes that you have a Counters collection:

Counters.native(function(err, collection) {

        collection.findAndModify(
          {"_id": "order_id"}, // query
          [['_id','asc']],  // sort order
          { "$inc": { "seq": 1 }}, // increments the seq
          {"new": true}, // options
          function(err, object) {
              if (err){
                  sails.log.debug("ERROR",err.message);  // returns error if no matching object found
              }else{
                  sails.log.debug(object);
              }
          });
        });
GiulioG
  • 369
  • 4
  • 15