Get the _id of the sub-document from mongoose findOne query - node.js

The schema of my Sample model is:-
var nameSchema = new mongoose.Schema({
firstname:String,
lastname:String
})
var sampleSchema= new mongoose.Schema({
number: {
type: String
},
name :{
type : [nameSchema]
}
});
I am trying to update the first and last name by searching them by their number property by making use of Sample.findOne({number:number}). And i am performing the update operation in the following manner:-
module.exports.updateNumber = function(req, res){
var number= req.body.number;
var lname= req.body.lname;
var fname= req.body.fname;
Sample
.findOne({number:number})
.select('name')
.exec(function(err, doc){
console.log(doc)
var this_id;
var thisService = doc.name.id('this_id');
thisService.firstname=fname;
thisService.lastname=lname;
doc.save(function(err, update) {
if (err) {
res
.status(500)
.json(err);
} else {
res
res.render('done')
}
});
})
}
If i console log the output i got is:
{ _id: 5bc5d71f47ff14361c0639d1,
name:
[ { _id: 5bc5d71f47ff14361c0639d2,
firstname: 'firstname',
lastname: 'lastname' } ] }
Is there any way, i could store _id: 5bc5d71f47ff14361c0639d2 in 'this_id' variable, so that this updation would be possible

name is an array, so if you want the first _id then name[0]._id would suffice, if you want an array of all values for _id in name, then name.map((person) => person._id) would give you an array of _id
However, more details about the context of this object would help give a better answer.

Related

Store value of a subquery - mongoose

What im doing:
When I call getData() the backend server .find() all my data.
My documents:
My test document has an _id a name and stuff fields. The stuff field contains the _id to the data document.
My data document has an _id and a age field
My goal:
When I send the data to the frontend I donĀ“t want the stuff field to appear with the _id, I want it to appear with the age field from the correspondingdata.
What I have:
router.route('/data').get((req, res) => {
Test.find((err, aval) => {
if (err)
console.log(err);
else{
var result = [];
aval.forEach(e => {
var age;
// Get the age, only 1
Data.findById(e.stuff, 'age', function (err, a) {
age = a.age;
});
result.push({name: e.name, age: age});
});
res.json(result);
}
});
});
I find all the test documents then, for each one of them, I find the age and put the result in the array. Finaly I send the result array.
My problem:
The age field on my result array is always undefined, why? Any solutions?
UPDATE 1 - The schemas
The test schema
var TestSchema = new Schema(
{
stuff: {type: Schema.Types.ObjectId, ref: 'Data', required: true},
name: {type: String, required: true}
}
);
The data schema
var DataSchema = new Schema(
{
age: {type: Number, required: true}
}
);
router.route('/data').get((req, res) => {
Test.find({})
.populate('stuff')
.exec((err, aval) => {
if (err) console.log(err);
res.json(aval);
});
});
Mongoose model has a populate property that uses the value in the model attribute definition to get data matching the _id from another model.
It's a scop problem with your code try this out :
Data.findById(e.stuff, 'age', function (err, a) {
result.push({name: e.name, age: a.age});
});
But as a better solution think to use the Aggregation Framework

insert and insertOne not a function and update not creating mongo ID

I thought I could read my way to this solution, but I cant see what im doing wrong.
Here is my model:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var inspectSchema = new Schema({
_id: Object, // Mongo ID
property: String, // Property ID
room: String, // The room Name
item: Array // The Items text
});
module.exports = mongoose.model('inspectModel', inspectSchema, 'inspect');
And here is where I try to insert or insertOne
var inspectModel = require('../../models/inspectModel');
var inspectTable = mongoose.model('inspectModel');
inspectTable.insert(
{
"property" : inspectRecord.property,
"room" : inspectRecord.room,
"item" : inspectRecord.item
},
function (err, res) {
if (err) { return reject({err:true, err:"addInspect ERROR" + err}) }
else {
show("=====RESOLVE addInspect=====")
return resolve();
}
})
I tried
inspectTable.insert
inspectModel.insert
inspectTable.insertOne
inspectModel.insertOne
No matter what I always get
TypeError: inspectTable.insert is not a function
I also tried just update with { upsert: true } but then the mongo ID becomes null.
Any ideas?
The method you're looking for is create:
inspectTable.create(
{
"property" : inspectRecord.property,
"room" : inspectRecord.room,
"item" : inspectRecord.item
}, ...
However, your schema definition of _id: Object is likely wrong. Just leave any definition of _id out of your schema and it will use the default ObjectId, which is likely what you want.
You can try this
var insert_table = new inspectTable(
{
"property" : inspectRecord.property,
"room" : inspectRecord.room,
"item" : inspectRecord.item
});
insert_table.save(function (err, res) {
if (err) { return reject({err:true, err:"addInspect ERROR" + err}) }
else {
show("=====RESOLVE addInspect=====")
return resolve();
}
});

Trying to update nested / subdocument in Express

I have a Mongoose model that has nested array and a subdocument.
I seem to be ok when posting to the object arrays/subdocument, but I'm having trouble with the .put
I hard coded the params for testing, just in case they were not coming in from PostMan for some reason.
The result I get from the above code is an empty array!
So I'm getting the right record and it creates the "phone" array, but does not populate.
.put(function(req, res){
Member.find({'_id':req.params.id}, function(err, member){
if(err)
res.send(err);
member.phone.push({
number: "78787878787",
phoneType: 2
})
member.save(function(err){
if(err)
res.send(err);
res.json(member);
});
});
});
I want to have an endpoint that simply adds another "phone" record.
Here is my model:
//DEPENDENCIES
var mongoose = require('mongoose');
var contactSchema = new mongoose.Schema({
name:{type:String},
age:{type:Number}
});
var phoneSchema = new mongoose.Schema({
number:{ type: String },
phoneType:{ type: Number }
})
var memberSchema = new mongoose.Schema({
firstname: {
type: String
},
lastname: {
type: String
},
phone:[phoneSchema],
contacts:[contactSchema]
});
//RETURN MODEL
module.exports = mongoose.model('member', memberSchema);
Now when I run my code I get the following undefined for "members":
{ id: '587bcbffe64e9f28a6894dd7' }
[ { _id: 587bcbffe64e9f28a6894dd7,
lastname: 'Stanley',
firstname: 'Dave',
__v: 0,
contacts: [ [Object] ],
phone: [] } ]
events.js:154
throw er; // Unhandled 'error' event
^
TypeError: Cannot read property 'push' of undefined
find returns an array of documents, not just one document. Thats is why it is giving error when you are trying to do member.phone.
Use findOne instead of find as you are querying by _id, it will return only one matched document or null(if its not present), so its a better choice than find.
Also, its better to check if the result is null or not. member will be null if no such _id is present.
Member.findOne({'_id':req.params.id}, function(err, member){
if(err)
res.send(err);
else if(member!=null)
{
member.phone.push({
number: "78787878787",
phoneType: 2
});
member.save(function(err){...});
}
});
If you are keen on using find. Use member[0] (first element) instead of member.
Member.find({'_id':req.params.id}, function(err, member){
if(err)
res.send(err);
else if(member.length!=0)
{
member[0].phone.push({
number: "78787878787",
phoneType: 2
});
member[0].save(function(err){...});
}
});
Hope that helps you.

MongoDB and Nodejs insert ID with auto increment

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);

Setting a virtual field in a Model based on an async query from another model

I want to have a user setting (in a user model) that is derived from the sum of values in another model.
What I have tried to do is create a virtual value using a query like this:
var schemaOptions = {
toObject: {
virtuals: true
}
,toJSON: {
virtuals: true
}
};
/**
* User Schema
*/
var UserSchema = new Schema({
firstname: String,
lastname: String,
email: String,
username: String,
provider: String,
phonenumber: Number,
country: String,
emailverificationcode: {type:String, default:'verifyme'},
phoneverificationcode: {type:Number, default:4321 },
emailverified: {type:Boolean, default:false},
phoneverified: {type:Boolean,default:false},
}, schemaOptions)
UserSchema
.virtual('credits')
.get(function(){
//Load Credits model
var Credit = mongoose.model('Credit');
Credit.aggregate([
{ $group: {
_id: '5274d0e5a84be03f42000002',
currentCredits: { $sum: '$amount'}
}}
], function (err, results) {
if (err) {
return 'N/A'
} else {
return results[0].currentCredits.toString();
//return '40';
}
}
);
})
Now, this gets the value but it fails to work correctly (I cannot retrieve the virtual 'value' credits). I think this is because of the async nature of the call.
Can someone suggest the correct way to achieve this?
Once again many thanks for any input you can provide.
Edit:
So I am trying to follow the suggested way but no luck so far. I cannot get my 'getCredits' method to call.
Here is what I have so far:
UserSchema.method.getCredits = function(cb) {
//Load Credits model
var Credit = mongoose.model('Credit');
Credit.aggregate([
{ $group: {
_id: '5274d0e5a84be03f42000002',
currentCredits: { $sum: '$amount'}
}}
], function (err, results) {
cb(results);
}
);
};
var User = mongoose.model('User');
User.findOne({ _id : req.user._id })
.exec(function (err, tempuser) {
tempuser.getCredits(function(result){
});
})
Any ideas? Thanks again
There are a few issues with your implementation:
UserSchema.method.getCredits
^^^^^^ should be 'methods'
Also, you have to make sure that you add methods (and virtuals/statics) to your schema before you create the model, otherwise they won't be attached to the model.
So this isn't going to work:
var MySchema = new mongoose.Schema(...);
var MyModel = mongoose.model('MyModel', MySchema);
MySchema.methods.myMethod = ... // too late, model already exists
Instead, use this:
var MySchema = new mongoose.Schema(...);
MySchema.methods.myMethod = ...
var MyModel = mongoose.model('MyModel', MySchema);
I would also advise you to always check/propagate errors.

Resources