Reputation: 39250
According to this mongodb article it is possible to auto increment a field and I would like the use the counters collection way.
The problem with that example is that I don't have thousands of people typing the data in the database using the mongo console. Instead I am trying to use mongoose.
So my schema looks something like this:
var entitySchema = mongoose.Schema({
testvalue:{type:String,default:function getNextSequence() {
console.log('what is this:',mongoose);//this is mongoose
var ret = db.counters.findAndModify({
query: { _id:'entityId' },
update: { $inc: { seq: 1 } },
new: true
}
);
return ret.seq;
}
}
});
I have created the counters collection in the same database and added a page with the _id of 'entityId'. From here I am not sure how to use mongoose to update that page and get the incrementing number.
There is no schema for counters and I would like it to stay that way because this is not really an entity used by the application. It should only be used in the schema(s) to auto increment fields.
Upvotes: 61
Views: 182005
Reputation: 613
I have done this in my way, i want to setup one auto increment field with appending 3 chars
qrOrgSaleInvoiceSchema.pre('save', async function (next) {
if (this.isNew) {
let nextInvoiceNumber = "INV1";
const QROrgSaleInvoiceModel = this.model('QROrgSaleInvoice');
const getLastInvoice: any = await QROrgSaleInvoiceModel.findOne().sort({ created_at: -1 }).exec();
if(getLastInvoice){
const lastNumber = parseInt(getLastInvoice.invoiceNumber.substr(3), 10);
nextInvoiceNumber = `INV${(lastNumber + 1).toString()}`;
}
this.invoiceNumber = nextInvoiceNumber;
}
next();
});
Upvotes: 1
Reputation: 31
without using any packages, you can just sort your primary id field by descending order and then increment it by one.
id field must be Number type
Model.find().sort({id: 'desc'}).limit(1).exec(function(err,model){
console.log(err,model);
//model is an array
const newId = ++model[0].id
console.log('new id: ',newId)
});
Upvotes: 0
Reputation: 41
After reading the previous good answers, here is my concise solution in TS:
import {Schema, model} from 'mongoose';
const counterSchema = new Schema({
_id: {
type: String,
required: true,
unique: true
},
seq: {
type: Number,
default: 0
}
});
const Counter = model('counter', counterSchema);
export async function getCounter(counterName: string): Promise<number> {
const count = await Counter.findByIdAndUpdate(
counterName,
{$inc: {seq: 1}},
{new: true, upsert: true}
);
return count.seq;
}
import {model, Schema} from 'mongoose';
import {getCounter} from "./counter";
const testSchema = new Schema(
{
seq: Number,
name: {
type: String,
required: true
}
});
testSchema.pre('save', async function (next) {
if (!this.isNew) return;
this.seq = await getCounter('test');
next();
})
export const Test = model('test', testSchema);
import {Test} from "./test";
const testObj = new Test({name: 'TEST'});
Upvotes: 1
Reputation: 41
None of above answer works when you have unique fields in your schema
because unique check at db level and increment happen before db level validation, so you may skip lots of numbers in auto increments like above solutions
only in post save can find if data already saved on db or return error
schmea.post('save', function(error, doc, next) {
if (error.name === 'MongoError' && error.code === 11000) {
next(new Error('email must be unique'));
} else {
next(error);
}
});
https://stackoverflow.com/a/41479297/10038067
that is why none of above answers are not like atomic operations auto increment in sql like dbs
Upvotes: 0
Reputation: 3077
test.pre("save",function(next){
if(this.isNew){
this.constructor.find({}).then((result) => {
console.log(result)
this.id = result.length + 1;
next();
});
}
})
Upvotes: 4
Reputation: 67
I had an issue using Mongoose Document when assigning value to Schema's field through put(). The count
returns an Object itself and I have to access it's property.
I played at @Tigran's answer and here's my output:
// My goal is to auto increment the internalId field
export interface EntityDocument extends mongoose.Document {
internalId: number
}
entitySchema.pre<EntityDocument>('save', async function() {
if(!this.isNew) return;
const count = await counter.findByIdAndUpdate(
{_id: 'entityId'},
{$inc: {seq: 1}},
{new: true, upsert: true}
);
// Since count is returning an array
// I used get() to access its child
this.internalId = Number(count.get('seq'))
});
Version: [email protected]
Upvotes: 0
Reputation: 2986
Attention!
As hammerbot and dan-dascalescu pointed out this does not work if you remove documents.
If you insert 3 documents with id
1
,2
and3
- you remove2
and insert another a new one it'll get3
as id which is already used!
In case you don't ever remove documents, here you go:
I know this has already a lot of answers, but I would share my solution which is IMO short and easy understandable:
// Use pre middleware
entitySchema.pre('save', function (next) {
// Only increment when the document is new
if (this.isNew) {
entityModel.count().then(res => {
this._id = res; // Increment count
next();
});
} else {
next();
}
});
Make sure that entitySchema._id
has type:Number
.
Mongoose version: 5.0.1
.
Upvotes: 12
Reputation: 2493
This problem is sufficiently complicated and there are enough pitfalls that it's best to rely on a tested mongoose plugin.
Out of the plethora of "autoincrement" plugins at http://plugins.mongoosejs.io/, the best maintained and documented (and not a fork) is mongoose sequence.
Upvotes: 8
Reputation: 3575
I've combined all the (subjectively and objectively) good parts of the answers, and came up with this code:
const counterSchema = new mongoose.Schema({
_id: {
type: String,
required: true,
},
seq: {
type: Number,
default: 0,
},
});
// Add a static "increment" method to the Model
// It will recieve the collection name for which to increment and return the counter value
counterSchema.static('increment', async function(counterName) {
const count = await this.findByIdAndUpdate(
counterName,
{$inc: {seq: 1}},
// new: return the new value
// upsert: create document if it doesn't exist
{new: true, upsert: true}
);
return count.seq;
});
const CounterModel = mongoose.model('Counter', counterSchema);
entitySchema.pre('save', async function() {
// Don't increment if this is NOT a newly created document
if(!this.isNew) return;
const testvalue = await CounterModel.increment('entity');
this.testvalue = testvalue;
});
One of the benefits of this approach is that all the counter related logic is separate. You can store it in a separate file and use it for multiple models importing the CounterModel
.
If you are going to increment the _id
field, you should add its definition in your schema:
const entitySchema = new mongoose.Schema({
_id: {
type: Number,
alias: 'id',
required: true,
},
<...>
});
Upvotes: 7
Reputation: 1970
Here is a proposal.
Create a separate collection to holds the max value for a model collection
const autoIncrementSchema = new Schema({
name: String,
seq: { type: Number, default: 0 }
});
const AutoIncrement = mongoose.model('AutoIncrement', autoIncrementSchema);
Now for each needed schema, add a pre-save hook
.
For example, let the collection name is Test
schema.pre('save', function preSave(next) {
const doc = this;
if (doc.isNew) {
const nextSeq = AutoIncrement.findOneAndUpdate(
{ name: 'Test' },
{ $inc: { seq: 1 } },
{ new: true, upsert: true }
);
nextSeq
.then(nextValue => doc[autoIncrementableField] = nextValue)
.then(next);
}
else next();
}
As findOneAndUpdate
is an atomic
operation, no two updates will return same seq
value. Thus each of your insertion will get an incremental seq regardless of number of concurrent insertions. Also this can be extended to more complex auto incremental logic and the auto increment sequence is not limited to Number type
This is not a tested code. Test before you use until I make a plugin for mongoose
.
Update I found that this plugin implemented related approach.
Upvotes: 1
Reputation: 2520
So combining multiple answers, this is what I ended up using:
counterModel.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
const counterSchema = new Schema(
{
_id: {type: String, required: true},
seq: { type: Number, default: 0 }
}
);
counterSchema.index({ _id: 1, seq: 1 }, { unique: true })
const counterModel = mongoose.model('counter', counterSchema);
const autoIncrementModelID = function (modelName, doc, next) {
counterModel.findByIdAndUpdate( // ** Method call begins **
modelName, // The ID to find for in counters model
{ $inc: { seq: 1 } }, // The update
{ new: true, upsert: true }, // The options
function(error, counter) { // The callback
if(error) return next(error);
doc.id = counter.seq;
next();
}
); // ** Method call ends **
}
module.exports = autoIncrementModelID;
myModel.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
const autoIncrementModelID = require('./counterModel');
const myModel = new Schema({
id: { type: Number, unique: true, min: 1 },
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date },
someOtherField: { type: String }
});
myModel.pre('save', function (next) {
if (!this.isNew) {
next();
return;
}
autoIncrementModelID('activities', this, next);
});
module.exports = mongoose.model('myModel', myModel);
Upvotes: 17
Reputation: 424
I didn't wan to use any plugin (an extra dependencie, initializing the mongodb connection apart from the one I use in the server.js, etc...) so I did an extra module, I can use it at any schema and even, I'm considering when you remove a document from the DB.
module.exports = async function(model, data, next) {
// Only applies to new documents, so updating with model.save() method won't update id
// We search for the biggest id into the documents (will search in the model, not whole db
// We limit the search to one result, in descendant order.
if(data.isNew) {
let total = await model.find().sort({id: -1}).limit(1);
data.id = total.length === 0 ? 1 : Number(total[0].id) + 1;
next();
};
};
And how to use it:
const autoincremental = require('../modules/auto-incremental');
Work.pre('save', function(next) {
autoincremental(model, this, next);
// Arguments:
// model: The model const here below
// this: The schema, the body of the document you wan to save
// next: next fn to continue
});
const model = mongoose.model('Work', Work);
module.exports = model;
Hope it helps you.
(If this Is wrong, please, tell me. I've been having no issues with this, but, not an expert)
Upvotes: 4
Reputation: 4652
var CounterSchema = Schema({
_id: { type: String, required: true },
seq: { type: Number, default: 0 }
});
var counter = mongoose.model('counter', CounterSchema);
var entitySchema = mongoose.Schema({
testvalue: { type: String }
});
entitySchema.pre('save', function(next) {
if (this.isNew) {
var doc = this;
counter.findByIdAndUpdate({ _id: 'entityId' }, { $inc: { seq: 1 } }, { new: true, upsert: true })
.then(function(count) {
doc.testvalue = count.seq;
next();
})
.catch(function(error) {
throw error;
});
} else {
next();
}
});
Upvotes: -2
Reputation: 67
I use together @cluny85 and @edtech. But I don't complete finish this issues.
counterModel.findByIdAndUpdate({_id: 'aid'}, {$inc: { seq: 1} }, function(error,counter){
But in function "pre('save...) then response of update counter finish after save document.
So I don't update counter to document.
Please check again all answer.Thank you.
Sorry. I can't add comment. Because I am newbie.
Upvotes: -1
Reputation: 175
The most voted answer doesn't work. This is the fix:
var CounterSchema = new mongoose.Schema({
_id: {type: String, required: true},
seq: { type: Number, default: 0 }
});
var counter = mongoose.model('counter', CounterSchema);
var entitySchema = mongoose.Schema({
sort: {type: String}
});
entitySchema.pre('save', function(next) {
var doc = this;
counter.findByIdAndUpdateAsync({_id: 'entityId'}, {$inc: { seq: 1} }, {new: true, upsert: true}).then(function(count) {
console.log("...count: "+JSON.stringify(count));
doc.sort = count.seq;
next();
})
.catch(function(error) {
console.error("counter error-> : "+error);
throw error;
});
});
The options parameters gives you the result of the update and it creates a new document if it doesn't exist. You can check here the official doc.
And if you need a sorted index check this doc
Upvotes: 15
Reputation: 73
The answers seem to increment the sequence even if the document already has an _id field (sort, whatever). This would be the case if you 'save' to update an existing document. No?
If I'm right, you'd want to call next() if this._id !== 0
The mongoose docs aren't super clear about this. If it is doing an update type query internally, then pre('save' may not be called.
CLARIFICATION
It appears the 'save' pre method is indeed called on updates.
I don't think you want to increment your sequence needlessly. It costs you a query and wastes the sequence number.
Upvotes: 1
Reputation: 4226
You can use mongoose-auto-increment
package as follows:
var mongoose = require('mongoose');
var autoIncrement = require('mongoose-auto-increment');
/* connect to your database here */
/* define your CounterSchema here */
autoIncrement.initialize(mongoose.connection);
CounterSchema.plugin(autoIncrement.plugin, 'Counter');
var Counter = mongoose.model('Counter', CounterSchema);
You only need to initialize the autoIncrement
once.
Upvotes: 32
Reputation: 1754
Here is an example how you can implement auto-increment field in Mongoose:
var CounterSchema = Schema({
_id: {type: String, required: true},
seq: { type: Number, default: 0 }
});
var counter = mongoose.model('counter', CounterSchema);
var entitySchema = mongoose.Schema({
testvalue: {type: String}
});
entitySchema.pre('save', function(next) {
var doc = this;
counter.findByIdAndUpdate({_id: 'entityId'}, {$inc: { seq: 1} }, function(error, counter) {
if(error)
return next(error);
doc.testvalue = counter.seq;
next();
});
});
Upvotes: 79