I implemented a auto increment sequence field in mongoose. I set the default/starting value as 5000. But it does not start from 5000, it starts from 1.
Heres my code:
My Counter Schema
// app/models/caseStudyCounter.js
// load the things we need
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
// define the schema for our user model
var caseStudyCounterSchema = mongoose.Schema({
_id: {type: String, required: true},
seq: {type: Number, default: 5000}
});
// methods ======================
// create the model for users and expose it to our app
module.exports = mongoose.model('caseStudyCounter', caseStudyCounterSchema);
My Main Schema:
// grab the mongoose module
var caseStudyCounter = require('../models/caseStudyCounter');
var mongoose = require("mongoose");
// grab the bcrypt module to hash the user passwords
var bcrypt = require('bcrypt-nodejs');
// define the schema for our model
var caseStudySchema = mongoose.Schema({
caseStudyNo: Number,
firstName: String,
lastName: String,
});
caseStudySchema.pre('save', function(next) {
var doc = this;
caseStudyCounter.findByIdAndUpdate({_id: 'caId'},{$inc: { seq: 1}},{"upsert": true,"new": true }, function(error, counter) {
if(error)
return next(error);
doc.caseStudyNo = counter.seq;
next();
});
});
// module.exports allows us to pass this to other files when it is called
// create the model for users and expose it to our app
module.exports = mongoose.model('CaseStudy', caseStudySchema);
I can't figure out why its starting form 1 when I have set the default as 5000. The sequence should be 5001, 5002, 5003 and so on. Any help will be greatly appreciated.
Probably this is why it happens: https://github.com/Automattic/mongoose/issues/3617#issuecomment-160296684
Use the setDefaultsOnInsert option. Or just manually use {$inc: {n:1}, $setOnInsert: {n:776} }
You can install mongoose-auto-increment.
yourSchema.plugin(autoIncrement.plugin, {
model: 'model',
field: 'field',
startAt: 5000,
incrementBy: 1
});
It's easy to install and use.
I was also facing same issue so i came up with this solution.
var mongoose = require("mongoose");
// define the schema for our model
var caseStudySchema = mongoose.Schema({
caseStudyNo: {
type:Number,
default:5000
},
firstName: String,
lastName: String,
});
caseStudySchema.pre('save', function(next) {
var doc = this;
//Retrieve last value of caseStudyNo
CaseStudy.findOne({},{},{sort: { 'caseStudyNo' :-1}}, function(error, counter) {
//if documents are present in collection then it will increment caseStudyNo
// else it will create a new documents with default values
if(counter){
counter.caseStudyNo++;
doc.caseStudyNo=counter.caseStudyNo;
}
next();
});
});
// module.exports allows us to pass this to other files when it is called
// create the model for users and expose it to our app
const CaseStudy = mongoose.model('CaseStudy', caseStudySchema);
module.exports = CaseStudy;
Related
I'm trying to create a document in my Schema that essentially works like a dictionary with an array of values (dates):
I want to use this to keep track of email correspondence I am sending users:
let d = {
'generic-contact' = ['11/02/2019', '11/05/2020'],
'update-profile' = ['1/01/2018']
}
I would then update this document field with something like:
let emailAddresses = ['joebloggs#yahoo.com', 'foobar#googlemail.com']
let recipients = await Profile.find({ email: { "$in" : emailAddresses } })
let emailTopic = 'newsletter03'
recipients.forEach(user => {
user.correspondenceHistory[emailTopic].push(Date.now())
user.save()
})
I want to do this so that I make sure that I don't send the same user the same email within a certain period of time.
However I can't work out how to set this up in my schema. Is this sort of structure possible?
I've tried many different structures for correspondenceHistory, but none of them have worked and I can't work out what I'm doing wrong. Here is my schema as it stands:
const mongoose = require("mongoose");
const passportLocalMongoose = require("passport-local-mongoose");
var profileSchema = new mongoose.Schema({
email: String,
firstname: String,
lastname: String,
correspondenceHistory: { type: Array } ### Here ###
}, { discriminatorKey: 'accountType', retainKeyOrder: true, timestamps: true });
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.
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();
});
});
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.
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
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);
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 and 3 - you remove 2 and insert another a new one it'll get 3 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.
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.
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,
},
<...>
});
test.pre("save",function(next){
if(this.isNew){
this.constructor.find({}).then((result) => {
console.log(result)
this.id = result.length + 1;
next();
});
}
})
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)
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.
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.
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: mongoose#5.11.10
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
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.
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();
}
});
I am currently on the MongoDB and Mongoose section of FreeCodeCamp.
The exercise requires the user to create a document instance using the Person constructor you build before. Pass to the constructor an object having the fields name, age, and favoriteFoods. Their types must be conformant to the ones in the Person Schema. Then call the method document.save() on the returned document instance. Pass to it a callback using the Node convention.
I have created the person schema and constructor but I am not too sure about what is still missing and how to piece it all together to implement the solution. Can someone help clarify?
var mongoose = require("mongoose");
mongoose.connect(process.env.MONGO_URI);
var Schema = mongoose.Schema;
var personSchema = new Schema({
name: {
type: String,
required: true
},
age: Number,
favoriteFoods: [String]
});
var Person = mongoose.model('Person', personSchema);
var joe = new Person({
name: "Joe",
age: 24,
favoriteFoods: ['Apple', 'Banana']
});
joe.save(function(err, persons) {
if(err){
console.log("Failed");
} else {
console.log("Saved Successful");
console.log(persons);
}
});
var createAndSavePerson = function(done) {
done(null /*, data*/);
};
#SQMN has the correct solution. Except that the FCC prompt suggests that we first create a new document from the Person model and add data to it, before we save it.
var Person = mongoose.model('Person', personSchema);
var p = new Person;
p.name = "John";
p.age = 18;
p.favoriteFoods = ["hotpot", "suantangyu"];
var createAndSavePerson = function(done){
p.save(function(err, data){
if (err){
return done(err);
}
return done(null, data);
});
};
As per the FCC forum user dnlnav, you'll want to move your instance and save function into createAndSavePerson and rewrite the callback passed into save like so:
var createAndSavePerson = function(done) {
var joe = new Person({
name: "Joe",
age: 24,
favoriteFoods: ['Apple', 'Banana']
});
joe.save((err, data) => {
if (err)
return done(err);
return done(null, data);
});
};
I haven't studied about the method you just used. But mongoose have it own method create(obj,callback) which does majority of the work for you. You can use it like this:
var mongoose = require("mongoose");
mongoose.connect(process.env.MONGO_URI);
var Schema = mongoose.Schema;
var personSchema = new Schema({
name: {
type: String,
required: true
},
age: Number,
favoriteFoods: [String]
});
var Person = mongoose.model('Person', personSchema);
Person.create({
name: "Joe",
age: 24,
favoriteFoods: ['Apple', 'Banana']
},function(err,result){
//code to manage error or result if Successful
});
This is related to a challenge in freeCodeCamp:
Create & Save Record
If you are trying to complete this challenge then please scroll down the template myApp.js file which has proper instructions and function declaration for each step. you will find a empty function with createAndSavePerson(), Please write your logic in that one.
if you stuck by the timeout issues, try to remove < > tag inside MONGO_URI key or learn MongoDB basics. You'll find out the answer.
What are different ways to insert a document(record) into MongoDB using Mongoose?
My current attempt:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var notificationsSchema = mongoose.Schema({
"datetime" : {
type: Date,
default: Date.now
},
"ownerId":{
type:String
},
"customerId" : {
type:String
},
"title" : {
type:String
},
"message" : {
type:String
}
});
var notifications = module.exports = mongoose.model('notifications', notificationsSchema);
module.exports.saveNotification = function(notificationObj, callback){
//notifications.insert(notificationObj); won't work
//notifications.save(notificationObj); won't work
notifications.create(notificationObj); //work but created duplicated document
}
Any idea why insert and save doesn't work in my case? I tried create, it inserted 2 document instead of 1. That's strange.
The .save() is an instance method of the model, while the .create() is called directly from the Model as a method call, being static in nature, and takes the object as a first parameter.
var mongoose = require('mongoose');
var notificationSchema = mongoose.Schema({
"datetime" : {
type: Date,
default: Date.now
},
"ownerId":{
type:String
},
"customerId" : {
type:String
},
"title" : {
type:String
},
"message" : {
type:String
}
});
var Notification = mongoose.model('Notification', notificationsSchema);
function saveNotification1(data) {
var notification = new Notification(data);
notification.save(function (err) {
if (err) return handleError(err);
// saved!
})
}
function saveNotification2(data) {
Notification.create(data, function (err, small) {
if (err) return handleError(err);
// saved!
})
}
Export whatever functions you would want outside.
More at the Mongoose Docs, or consider reading the reference of the Model prototype in Mongoose.
You can either use save() or create().
save() can only be used on a new document of the model while create() can be used on the model. Below, I have given a simple example.
Tour Model
const mongoose = require("mongoose");
const tourSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "A tour must have a name"],
unique: true,
},
rating: {
type: Number,
default:3.0,
},
price: {
type: Number,
required: [true, "A tour must have a price"],
},
});
const Tour = mongoose.model("Tour", tourSchema);
module.exports = Tour;
Tour Controller
const Tour = require('../models/tourModel');
exports.createTour = async (req, res) => {
// method 1
const newTour = await Tour.create(req.body);
// method 2
const newTour = new Tour(req.body);
await newTour.save();
}
Make sure to use either Method 1 or Method 2.
I'm quoting Mongoose's Constructing Documents documentation:
const Tank = mongoose.model('Tank', yourSchema);
const small = new Tank({ size: 'small' });
small.save(function (err) {
if (err) return handleError(err);
// saved!
});
// or
Tank.create({ size: 'small' }, function (err, small) {
if (err) return handleError(err);
// saved!
});
// or, for inserting large batches of documents
Tank.insertMany([{ size: 'small' }], function(err) {
});
TLDR: Use Create (save is expert-mode)
The main difference between using the create and save methods in Mongoose is that create is a convenience method that automatically calls new Model() and save() for you, while save is a method that is called on a Mongoose document instance.
When you call the create method on a Mongoose model, it creates a new instance of the model, sets the properties, and then saves the document to the database. This method is useful when you want to create a new document and insert it into the database in one step. This makes the creation an atomic transaction. Therefore, the save method leaves the potential to create inefficiencies/inconsistencies in your code.
On the other hand, the save method is called on an instance of a Mongoose document, after you have made changes to it. This method will validate the document and save the changes to the database.
Another difference is that create method can insert multiple documents at once, by passing an array of documents as parameter, while save is intended to be used on a single document.
So, if you want to create a new instance of a model and save it to the database in one step, you can use the create method. If you have an existing instance of a model that you want to save to the database, you should use the save method.
Also, if you have any validation or pre-save hook in your content schema, this will be triggered when using the create method.
I'm building a REST api in node and i'm stuck on trying to include subdocuments into my GET requests. For example, I have two schema's right now, People and Locations. A location can have many people, and a person can have one location. So when you return /people I would like my location entry to contain the location information.
I believe my problem could be one of two things. I'm fairly new to node, so i'm not 100% sure if my schema's can see each other, when I try the common methods around the web, my location field get's populated with null. How I understand it, I store the location id in my people schema and then using subdocuments it will find the location with that id and fill in the info.
I also am not 100% sure how to use the populate function, and how exactly I should go about writing the response to my GET call. My code below, I'd love to hear what you have to say!
app.js
// Node Setup
var application_root = __dirname,
express = require('express'),
path = require('path'),
mongoose = require('mongoose'),
http = require('http');
var app = express();
// MongoDB Connection
var dbLocalhost = mongoose.createConnection('mongodb://localhost/lantern/');
// Configure Node
app.configure(function(){
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(application_root, "public")));
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
app.port = 3000;
});
// Routes
var Locations = require('./routes/locations')(app, { 'mongoose': mongoose, 'db': dbLocalhost });
var People = require('./routes/people')(app, { 'mongoose': mongoose, 'db': dbLocalhost });
// Start the server
app.listen(app.port);
routes/people.js - has all the endpoints, but we're just concerned with GET for now
module.exports = function (app, options) {
var mongoose = options.mongoose;
var Schema = options.mongoose.Schema;
var db = options.db;
var PeopleModel = require('../schemas/peopleSchema')(db);
app.get('/people', function (req, res) {
return PeopleModel.find(function (err, obj) {
if (!err) {
return res.send(obj);
} else {
return res.send(err);
}
});
});
};
schemas/peopleSchema.js (the "location" field is what i want to populate)
module.exports = function(db) {
return db.model('People', PeopleSchema());
}
function PeopleSchema () {
var Schema = require('mongoose').Schema;
return new Schema({
first_name: String,
last_name: String,
address: {
unit: Number,
address: String,
zipcode: String,
city: String,
region: String,
country: String
},
image: String,
job_title: String,
created_at: { type: Date, default: Date.now },
active_until: { type: Date, default: null },
hourly_wage: Number,
location: [{type: Schema.ObjectId , ref: 'Locations'}], // Inheirit store info
employee_number: Number
}, { collection: 'people' });
}
schemas/locationsSchema.js - and the locations schema just incase
module.exports = function(db) {
return db.model('Locations', LocationsSchema());
}
function LocationsSchema () {
var Schema = require('mongoose').Schema;
return new Schema({
title: String,
address: {
unit: Number,
address: String,
zipcode: String,
city: String,
region: String,
country: String
},
current_manager: String, // Inherit person details
alternate_contact: String, // Inherit person details
hours: {
sunday: String,
monday: String,
tuesday: String,
wednesday: String,
thursday: String,
friday: String,
saturday: String,
holidays: String
},
employees: "", // mixin employees that work at this location
created_at: { type: Date, default: Date.now },
active_until: { type: Date, default: null }
}, { collection: 'locations' });
}