I am trying to update mongoose collection name counter.
But it's not getting updated.
counter collection
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var counterSchema = new Schema({
_id: {type: String, required: true},
sequence_value: {type: Number, default: 1}
});
var Counter = module.exports = mongoose.model('Counter', counterSchema);
API
router.post('/increment',function(req, res, next){
console.log('Sequence Counter::' + getNextSequenceValue("productId"));
)};
getNextSequenceValue Method
function getNextSequenceValue(sequenceName){
var sequenceDocument = Counters.findOneAndUpdate({
query:{_id: sequenceName },
update: {$inc:{sequence_value:1}},
new:true,
upsert: true
});
console.log('Counter value::' + sequenceDocument.sequence_value);
return sequenceDocument.sequence_value;
}
But every time I hit /increment API, console.log is printing undefined.
You will always get undefined since sequenceDocument is a Promise which will later resolve with the updated document if the update operation is successful or will reject with an error if it is not. In your case the console.log statement will run before the database operation has completed. This is because findOneAndUpdate is an asynchronous operation which returns a Promise object.
The update probably fails because you are passing arguments to findOneAndUpdate in an improper manner. The function accepts the query as first argument, update operation as second and query options as third argument.
You can rewrite the getNextSequenceValue in the following way:
function getNextSequenceValue(sequenceName) {
return Counters.findOneAndUpdate(
{ // query
_id: sequenceName
},
{ $inc: { sequence_value: 1 } }, // update operation
{ // update operation options
new: true,
upsert: true
}
).then(sequenceDocument => sequenceDocument.sequence_value)
}
It will now return a Promise which will resolve with the sequence value. You can use it in your controller like this:
router.post('/increment', function(req, res, next) {
getNextSequenceValue('productId')
.then(sequenceValue => {
console.log(sequenceValue)
})
.catch(error => {
// handle possible MongoDB errors here
console.log(error)
})
})
Related
Using mongoose I am querying a list of posts and would like to determine whether or not the user has liked the image or not within the query function by adding a boolean to the response JSON. I am trying to do this in a for loop.
However, when I console.log(), the post with the field returns correctly but does not amend it to the JSON.
My function:
function(req, res) {
var isLiked, likeCount;
Post
.find(/* Params */)
.then(posts => {
for (var index in posts) {
posts[index].isLiked = posts[index].likes.includes(req.headers.userid)
console.log(posts[index]) // does not show 'isLiked' field in JSON
console.log(posts[index].isLiked) // response is correct
}
res.send(posts) // does not have 'isLiked field
})
},
Post schema:
var postSchema = new Schema({
userId: {
type: String,
required: true
},
caption: {
type: String,
required: false
},
likes: [{
type: String,
}]
});
To add properties to queries objects you should convert them to JS objects:
function getPosts(req, res) {
Post.find(/* Params */).then((posts) => {
const result = [];
for (const post of posts) {
const postObj = post.toObject();
postObj.isLiked = postObj.likes.includes(req.headers.userid);
result.push(postObj)
}
res.send(result);
});
}
Cuz
Post.find()
is not return an object, you can set prop isLiked to posts[index] but it's private.
Easy way to fix it is use lean() method to get return object
Post.find().lean()
.then(//do what you want)
I am trying to save the result of findOne(), however, I do not have any idea how to save this result in a variable.
function createSubscription (req, res, next) {
let product_id = "P-5JM98005MT260873LLT44E2Y" ;
let doc = null;
product.findOne({"plans.id": product_id}, { "plans.$": 1
}).sort({create_time: -1}).exec(function(err, docs) {
if(err) {
} else {
if(docs != null) {
this.doc = docs;
}
}
});
console.log(doc);
let result = null;
if (doc.create != null) {
result = processDoc(doc);
}
}
function processDoc(doc) {
//do something
return resul;
}
function processResult(result) {
//do something
}
Below, I copy the product schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ProductSchema = new Schema({
id: {
type: String,
trim: true,
required: true,
},
name: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
create_time: {
type: Date,
required: true,
}
});
module.exports = mongoose.model('Product', ProductSchema);
The doc is always null and does not receive the value.
In general terms, I would like to get the response product.findOne to use another function, calling by createSubscription()
To save the result of findOne() is as easy as this:
var doc = product.findOne();
The problem you're having is that you are calling processDoc() before findOne() is finished. Look into asynchronous javascript. You can fix this by using async/await like this:
async function createSubscription (req, res, next) {
var doc = await product.findOne();
processDoc(doc);
}
The reason is you are calling a callback function, which means function is asynchronous. So there is no guarantee that your query will execute and the values will be set before it reaches to,
if (doc.create != null) {
result = processDoc(doc);
}
To resolve this you may use the Async/Await Syntax:
const createSubscription = async function (params) {
try { return await User.findOne(params)
} catch(err) { console.log(err) }
}
const doc = createSubscription({"plans.id": product_id})
Since you want to query something in database, that means you already created one and saved some data in it.
However before saving data in the database you should be creating your model which should be created under models folder in Product.js(model names should start capital letter in convention) . Then you have to IMPORT it in your above page to access it. You want to query by product.id but which product's id?
Since you have req in your function, that means you are posting something. If you set up the proper settings in app.js you should be able to access to req.body which is the information that being posted by the client side.
app.js //setting for parsing(reading or accessing) req.body
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
Now we can reach req.body.id
this should be your code:
function createSubscription (req, res, next) {
const product=Product.findOne({plan.id:req.body.id})
.
.
}
If you noticed I did not put this plan.id in quotes.Because findOne() method belongs the Product model and that model belongs to the package that you are using. (In mongoose you query without quotes, in mongodb client you query in quotes)
findOne() is an asynchronous operation means result will not come to you immediately. So you should always keep it in try/catch block.
function createSubscription (req, res, next) {
try{
const product=Product.findOne({plan.id:req.body.id})
}
catch(error){console.log(error.message)} //every error object has message property
.
.
}
Lastly since you are querying only one object you do not need to sort it.
I am new to NodeJs and MongoDB, i want to insert row with auto increment primary key 'id'. also defined a function called getNextSequence on mongo server.
this is working perfect on Mongodb server
> db.user.insert({
"id" : getNextSequence('user_id'),
"username" : "test",
"email" : "test#test.com",
"password" : "test123"
})
now i want to insert from NodeJs.I have tried this but not working
db.collection('user').insertOne({
id : "getNextSequence('user_id')",
username : query.name,
email: query.email,
password: query.pass
}, function(err, result) {
assert.equal(err, null);
console.log("row insterted ");
callback();
});
Assuming that getNextSequence is a server-script function (i.e. a method you defined and saved via db.system.js.save), it is not callable outside of the server. One way to go is to use eval, which forces the server to evaluate a string as a js code, even though it is not a good practice. Here is an example:
db.eval('getNextSequence(\'user_id\')', function(err, result) {
db.collection('users').insert({
"id" : result,
"username" : "test",
"email" : "test#test.com",
"password" : "test123"
});
});
Another way is to follow the mongo tutorial and to implement the getNextSequence directly in NodeJS. The syntax is pretty much the same:
function getNextSequence(db, name, callback) {
db.collection("counters").findAndModify( { _id: name }, null, { $inc: { seq: 1 } }, function(err, result){
if(err) callback(err, result);
callback(err, result.value.seq);
} );
}
You then use it in your nodeJS code like:
getNextSequence(db, "user_id", function(err, result){
if(!err){
db.collection('users').insert({
"_id": result,
// ...
});
}
});
Note: of course, you need to have set the counters collection as explained in the docs.
You can also use "mongoose-auto-increment".
The code has just 4 lines
var mongoose = require('mongoose');
var autoIncrement = require('mongoose-auto-increment');
autoIncrement.initialize(mongoose.connection);
userSchema.plugin(autoIncrement.plugin, 'user');
example :
npm i mongoose-auto-increment
connections.js :
const mongoose = require('mongoose');
require("dotenv").config;
const uri = process.env.MONGOURL;
mongoose.connect(uri, { useNewUrlParser: true }, (err) => {
if (!err) { console.log('MongoDB Connection Succeeded.') }
else { console.log('Error in DB connection : ' + err) }
});
require('../schema/userSchema');
userSchema.js :
var mongoose = require('mongoose'); // 1. require mongoose
var autoIncrement = require('mongoose-auto-increment'); // 2. require mongoose-auto-increment
var userSchema = new mongoose.Schema({
name: { type: String },
password: { type: String },
email: { type: String, unique: true, required: 'This field is required.' },
});
autoIncrement.initialize(mongoose.connection); // 3. initialize autoIncrement
userSchema.plugin(autoIncrement.plugin, 'user'); // 4. use autoIncrement
mongoose.model('user', userSchema);
To accomplish this, we will create a function that will keep trying to save the document untill it will have been saved with incremented _id
async function retryUntilSave(db, task) {
try {
const index = await db.collection('tasks').find().count() + 1;
const result = await db.collection('tasks').insertOne(Object.assign(task, { _id: index }))
} catch (error) {
if (error.message.includes("_id_ dup key")) {
console.log("ID already exists!")
console.log("Retrying...");
retryUntilSave(db, task)
} else {
console.log(error.message);
}
}
}
We can use task._id: index instead of Object.assign()
finally you can test this by making some concurrent requests
for (let index = 0; index < 20; index++) {
setTimeout(async () => {
await retryUntilSave(db, { title: "Some Task" })
}, 1000);
}
This function will handle easily if two or more tasks submitted at the same time because mogod throws error when we try to insert a document with duplicate _id, then we will retry saving the document again with incremented _id and this process will run until we save the document successfully !
You can also use "mongodb-autoincrement" module of node js. For example:
var autoIncrement = require("mongodb-autoincrement");
exports.yourMethod = function(newData, callback) {
autoIncrement.getNextSequence(db, your-collection-name, function (err, autoIndex) {
newData.id = autoIndex;
//save your code with this autogenerated id
});
}
You can use the below package on a model schema to auto-increment your collection field.
mongoose-auto-increment //you can download it from npm
Here I am not focusing on how to connect MongoDB. I just focus on how you can integrate auto increment in your model/collection/table.
const mongoose = require("mongoose"); //
const autoIncrement = require("mongoose-auto-increment");
const post_schema = new mongoose.Schema({
title: {
type: String,
required: true,
min: 3,
max: 225,
},
slug: {
type: String,
required: true,
},
});
autoIncrement.initialize(mongoose.connection);
post_schema.plugin(autoIncrement.plugin, {
model: "post", // collection or table name in which you want to apply auto increment
field: "_id", // field of model which you want to auto increment
startAt: 1, // start your auto increment value from 1
incrementBy: 1, // incremented by 1
});
module.exports = mongoose.model("post", post_schema);
I am having issues trying to get the 'runValidators' option to work. My user schema has an email field that has required set to true but each time a new user gets added to the database (using the 'upsert' option) and the email field is empty it does not complain:
var userSchema = new mongoose.Schema({
facebookId: {type: Number, required: true},
activated: {type: Boolean, required: true, default: false},
email: {type: String, required: true}
});
findOneAndUpdate code:
model.user.user.findOneAndUpdate(
{facebookId: request.params.facebookId},
{
$setOnInsert: {
facebookId: request.params.facebookId,
email: request.payload.email,
}
},
{upsert: true,
new: true,
runValidators: true,
setDefaultsOnInsert: true
}, function (err, user) {
if (err) {
console.log(err);
return reply(boom.badRequest(authError));
}
return reply(user);
});
I have no idea what I am doing wrong, I just followed the docs: http://mongoosejs.com/docs/validation.html
In the docs is says the following:
Note that in mongoose 4.x, update validators only run on $set and $unset operations. For instance, the below update will succeed, regardless of the value of number.
I replaced the $setOnInsert with $set but had the same result.
required validators only fail when you try to explicitly $unset the key.
This makes no sense to me but it's what the docs say.
use this plugin:
mongoose-unique-validator
When using methods like findOneAndUpdate you will need to pass this configuration object:
{ runValidators: true, context: 'query' }
ie.
User.findOneAndUpdate(
{ email: 'old-email#example.com' },
{ email: 'new-email#example.com' },
{ runValidators: true, context: 'query' },
function(err) {
// ...
}
In mongoose do same thing in two step.
Find the result using findOne() method.
Add fields and save document using Model.save().
This will update your document.
I fixed the issue by adding a pre hook for findOneAndUpdate():
ExampleSchema.pre('findOneAndUpdate', function (next) {
this.options.runValidators = true
next()
})
Then when I am using findOneAndUpdate the validation is working.
I created a plugin to validate required model properties before doing update operations in mongoose.
Plugin code here
var mongoose = require('mongoose');
var _ = require('lodash');
var s = require('underscore.string');
function validateExtra(schema, options){
schema.methods.validateRequired = function(){
var deferred = Promise.defer();
var self = this;
try {
_.forEach(this.schema.paths, function (val, key) {
if (val.isRequired && _.isUndefined(self[key])) {
throw new Error(s.humanize(key) + ' is not set and is required');
}
});
deferred.resolve();
} catch (err){
deferred.reject(err);
}
return deferred.promise;
}
}
module.exports = validateExtra;
Must be called explicitly as a method from the model, so I recommend chaining it a .then chain prior to the update call.
Plugin in use here
fuelOrderModel(postVars.fuelOrder).validateRequired()
.then(function(){
return fuelOrderModel.findOneAndUpdate({_id: postVars.fuelOrder.fuelOrderId},
postVars.fuelOrder, {runValidators: true, upsert: true,
setDefaultsOnInsert: true, new: true})
.then(function(doc) {
res.json({fuelOrderId: postVars.fuelOrder.fuelOrderId});
});
}, function(err){
global.saveError(err, 'server', req.user);
res.status(500).json(err);
});
If you want to validate with findOneAndUpdate you can not get current document but you can get this keywords's contents and in this keywords's content have "op" property so solution is this :
Note : does not matter if you use context or not. Also, don't forget to send data include both "price" and "priceDiscount" in findOneAndUpdate body.
validate: {
validator: function (value) {
if (this.op === 'findOneAndUpdate') {
console.log(this.getUpdate().$set.priceDiscount);
console.log(this.getUpdate().$set.price);
return (
this.getUpdate().$set.priceDiscount < this.getUpdate().$set.price
);
}
return value < this.price;
},
message: 'Discount price ({VALUE}) should be below regular price',
}
The reason behind this behavior is that mongoose assumes you are just going to update the document, not insert one. The only possibility of having an invalid model with upsert is therefore to perform an $unset. In other words, findOneAndUpdate would be appropriate for a PATCH endpoint.
If you want to validate the model on insert, and be able to perform a update on this endpoint too (it would be a PUT endpoint) you should use replaceOne
changed schema and everything went crazy (see changes below). now accessing properties from .find() and cursor.forEach() is returning 'undefined' in backend:
EDIT: have found
.find().lean().exec(callback)
allows access to properties in callback but hard to do anything with them and that to access properties by doing
doc._doc.property
works in callbacks:
.find(function(err,doc){for (i in docs){doc=docs[i]; console.log(doc._doc.property)}}
and .forEach(function(doc){console.log(doc._doc.property)}:
My schema once looked like this
for collection of people
{
name: String,
v: Types.ObjectId, ref: V //shorthand
r: {
e: [{}],
u: [{}]
}
}
now it looks like this
var people = new mongoose.Schema (
{
name: String,
v: {type: mongoose.Schema.Types.ObjectId, ref: V}
r: {
e: [{type: mongoose.Schema.Types.ObjectId, ref: R}],
u: [{type: mongoose.Schema.Types.ObjectId, ref: R}]
}
}
)
mongoose.model('people',people);
for collection of r
var collR = new mongoose.Schema({}, {strict:false})
mongoose.model('R',collR)
nodejs controller 1:
module.exports.getProducts = function (req, res) {
people.find(req.query)
.populate('v r.e r.u')
.exec(function (err, data) {
if (err) {sendJsonResponse(res,400,err)}
else {
data.forEach(function(single){
single.r.e.forEach(function(sing){
console.log(sing) //defined, and i saw rating, and its defined
console.log(sing.rating); //undefined
// do something with sing.rating but it's undefined here
})
})
sendJsonResponse(res,200,data); //not undefined on frontend success callback
}
});
};
node controller 2:
module.exports.getProducts = function (req, res) {
people.find(req.query)
.populate('v r.e r.u')
.exec(function (err, data) {
if (err) {sendJsonResponse(res,400,err)}
else {
data.forEach(function(single){
R.find({person: single.name}, function (err, dat) {
dat.forEach(function(sing){
console.log(sing) //defined and rating defined
console.log(sing.rating); //undefined ugh.
//do something with rating but cant bc undefined here
})
})
})
//if i send data back here, in success callback, data[i].r.e[j].rating is defined for all i and j, whaaa!?!
}
});
};
one of the sing's logged from the cursor.forEach loop---
{_id: 1254357653, name: peep, rating: 6, type: some type}
EDIT:
ya so:
collection.find(query).exec(function(err,docs) {
docs.forEach(function(singleDoc) {
console.log(singleDoc._doc.property); //DEFINED, bad boyz 4 lyfe *_*
})
})
so i finally decided to console.log the darn keys of the document returned from a cursor.forEach
this also returns defined:
collection.find(query).lean().exec(function(err,docs) {
console.log(docs[i].property); //for all i, THEY'RE DEFINED!!!!! wooo
})
well now another issue pops up when i try to do an update inside a find
collection.find(query).exec(function(err,docs) {
if (err) {return errorHandler(err)};
var doc = docs[0];
var captainKeyes = Object.keys(req.body);
for (k = 0 ; k < captainKeyes.length ; k++) {
//update the doc key/value pairs with what is sent in req.body
doc._doc[captainKeyes[k]] = req.body[captainKeyes[k]];
//from above, learned to access properties captainKeyes[k], you have to first access
//the hidden property _doc to get to actual doc
}
doc.save()
//old doc is still in db, damn. and all this used to work before
//we added that R collection :(
})
I changed the schema for the collection R to have some keys, changing it from just an empty object with strict: false.
from {{},strict:false} to {{name: String, rating: Number, person: String},strict:false}
now i dont have to use _doc, wooohoooo, and all the queries works normally again.
moral of the story, i didn't really understand how to implement a schemaless collection properly, and then stuff got cray