Upserting subdocument in for loop - node.js

Not sure why this isn't working, I've tried a few different variations of syntax, but no go. I'm also unsure if having an update inside a for loop is a good idea.
Basically I'm updating a user's inventory by passing an object that contains two arrays (item[], and item_qty[]) that resembles as such:
var itemset = {}
itemset.item['Gold','Silver','Bronze']
itemset.item_qty[1,3,5]
itemset.qty = itemset.item.length
and the arrays can have varying lengths as such:
var itemset = {}
itemset.item['Platinum','Bronze']
itemset.item_qty[1,1]
itemset.qty = itemset.item.length
The goal is to keep adding to a user's inventory item quantity if the item exists, or add it (along with the quantity) if it doesn't exist. So using the two updated examples, the user will have Platinum(1), Gold(1), Silver(3) and Bronze(6) after they have both passed through the function.
The schema for the USER is:
var UserSchema = new mongoose.Schema({
name: {type: String, required: true},
mail: {type: String, required: true},
inv: {
item: {type: String},
iqty: {type: Number}
}
})
And here is the function:
function addInv(userId,itemset) {
for(i=0;i<itemset.qty;i++){
console.log('Updating:' + itemset.item[i])
db.User.update(
//Criteria
{
_id: userId,
inv: {item: itemset.item[i]}
},
{
$set:
{
inv:
{
'$item': itemset.item[i],
$inc: {'$iqty': itemset.item_count[i]}
}
}
},
{upsert:true},
function(err,i)
{
console.log('Error: ' + err)
console.log('Db: ' + i)
}) //End Update()
}
}
This works, in the sense of syntax, but it never updates the data...
I'd also like to see if there's a way to do this without having the for loop & multiple db calls.

Related

How do I create a number of objects with insertMany with existing data from the db?

I'm currently trying to insert a large number of models through insertMany, but I can't seem to figure out how to populate the array when creating an object. I'm relatively new to Mongoose and any help would be appreciated, here is the code I have right now.
const ProgramsSchema = new mongoose.Schema({
program_id: {
type: String,
required: true
},
description: {
type: String
},
});
const schoolsSchema = new mongoose.Schema({
inst_url: {
type: String
},
programs: {
type: [{type: ProgramsSchema, ref: "Programs"}]
}
});
And here's the code where I try to create a number of schools and add it to the database.
let new_schools = []
for (let i = 0; i < schools.length; i++) {
let school = schools[i]
let p_arr = []
for (let p_index = 0; p_index < school["PROGRAMS"].length; p_index++) {
let p_id = school["PROGRAMS"][p_index]
Programs.find({program_id: p_id}).populate('Programs').exec(function(err, data) {
if (err) {
console.log(err);
} else {
p_arr.push(data[0])
}
})
}
let newSchool = {
inst_url: school["INSTURL"],
programs: p_arr,
}
new_schools.push(newSchool);
}
Schools.insertMany(new_schools);
I can basically add all of the school data into the db, but none of the programs are being populated. I was wondering if there was a way to do this and what the best practice was. Please let me know if you guys need more info or if my question wasn't clear.
There are a few problems with your mongoose schemas. The operation you are trying to do in find is not available, based on your mongoose schemas. You cannot populate from "Programs" to "Schools". You can populate from "Schools" to "Programs", for instance:
Schools.find().populate(programs)
And to do that, several changes in your schemas are necessary. The idea is to store the programs _id in your programs array in School collection and be able to get the programs info through populate(), either regular populate or 'custom populate' (populate virtuals).
Regular populate()
I would change the schoolsSchema in order to store an array of _id into programs:
const schoolsSchema = new mongoose.Schema({
inst_url: {
type: String
},
programs: [
{type: String, ref: "Programs"}
]
});
You should change ProgramsSchema as well:
const ProgramsSchema = new mongoose.Schema({
_id: Schema.Types.ObjectId, // that's important
description: {
type: String
},
});
And now, you can do:
Programs.find({_id: p_id}).exec(function(err, data) {
if (err) {
console.log(err);
} else {
p_arr.push(data[0]._id)
}
})
Your documents should be inserted correctly. And now you can populate programs when you are performing a query over School, as I indicated above:
Schools.find().populate(programs)
Populate Virtual
The another way. First of all, I have never tried this way, but I think it works as follows:
If you want to populate over fields that are not ObjectId, you can use populate virtuals (https://mongoosejs.com/docs/populate.html#populate-virtuals). In that case, your schemas should be:
const ProgramsSchema = new mongoose.Schema({
program_id: String,
description: {
type: String
},
});
const schoolsSchema = new mongoose.Schema({
inst_url: {
type: String
},
programs: [
{type: String, ref: "Programs"}
]
});
Enable virtual in your School schema:
Schools.virtual('programs', {
ref: 'Programs',
localField: 'programs',
foreignField: 'program_id'
});
Then, you should store the program_id.
Programs.find({program_id: p_id}).exec(function(err, data) {
if (err) {
console.log(err);
} else {
p_arr.push(data[0].program_id)
}
})
And as before, you can populate() when you need.
I hope I helped

Saving ALL nested documents on parent update Mongoose

My parent model looks like this:
var OrderSchema = new mongoose.Schema({
serviceNotes: {type: mongoose.Schema.Types.ObjectId, ref: 'Service'},
vehicle: {type: mongoose.Schema.Types.ObjectId, ref: 'Vehicle'}
});
The children look like this:
var VehicleSchema = new mongoose.Schema({
name: String
});
var ServiceSchema = new mongoose.Schema({
baseCost: Number
});
I am trying to find an easy solution to updating all of these documents at once. The problem is, when I call an update on an order, it does not update the nested documents. Please see the following:
exports.updateOrder = function (req, res) {
var order = req.body.order,
update = {
$set: order
};
Order.findOneAndUpdate({_id: order._id}, update, {new: true}, function (err, order) {
if (err) handleError(res);
if (!order) return res.status(404).send('Order not found.');
return res.json({order: order});
});
}
An example of req.body in this case may look like this:
{
order: {
_id: 829198218932shdbn,
serviceNotes: {
_id: 8932838nsd2sdnbd,
baseCost: 1
},
vehicle: {
_id: iu283823872378bd,
name: 'Honda'
}
}
}
The order update should also update the serviceNotes with the updated information, and the vehicle with the updated information.
The only way I have been able to update the nested documents is by calling a findOneAndUpdate on the children and updating them one by one. I am looking for a solution to just call update on the parent (order) and have the children update as well.

How can I merge results into a single JSON while querying data from two different models in node.js and mongoose?

I have two mongoose models in my app:
var UsersSchema = new Schema({
username: {type: String},
facebook_username: {type: String},
display_name: {type: String}
}
and
var CommentsSchema = new Schema({
user_id: {type: String, required: true},
text_content: {type: String},
photo_content_url: {type: String}
comment_date: {type: Date, default: Date.now},
created_at: {type: Date, default: Date.now},
}
currently each single comment contains user_id - a direct link to its author.
I created a node.js endpoint that takes POST query and returns JSON with all details about comments:
commentsRoutes.post('/comments', function (req, res) {
var startDate = req.body.startDate;
var endDate = req.body.endDate;
var query= {};
query.$and = [];
// and condition on start date
if(startDate != undefined) {
var startDate = new Date(req.param('startDate'));
var endDate = new Date(req.param('endDate'));
query.$and.push({"comment_date":{$gte: startDate}});
query.$and.push({"comment_date":{$lte: endDate}});
}
var finalquery = Comments.find(query)
finalquery.exec(function(err, comments){
if(err) {
res.send(err);
return;
}
res.json(comments);
});
});
This endpoint returns me a JSON with all the comments, however - I need to attach to each JSON details about its author - username, facebook_username and display_name (fetched based on user_id from UsersSchema). Can you tell me how could I do it?
user_id in CommentsSchema is a mongoose id of a specific user from UsersSchema
====== EDIT
Like #Antonio suggested in his answer below, I used populate in my case, it worked well and I saw merged JSON at the end. All user details were added, it looked like:
{
"text_content": "blah",
"photo_content_url": "http://www...",
"comment_date": "some date",
"created_at": "some date",
"user_id": { "username": "someUsername",
"facebook_username": "fbUsername",
"display_name": "sth" }
}
however - is there a way to include POST parameters in my endpoint that will apply to the user_id json?
Currently I'm sending a POST parameters startDate and endDate. But if I send also facebook_username - how can I include it in a query and find only comments that were written by this author?
I tried adding a simple condition:
var fb_username = req.body.facebookUsername;
query.$and.push({"facebook_username": fb_username});
but it didn't work because there's no such field facebook_username in CommentsSchema...
So how can I include condition attached to fields from UsersSchema that will limit results from CommentsSchema?
Since you have a reference to the corresponding user you could use populate.
Taking into account that try this:
Comments
.find({
comment_date: {
$gte: startDate,
$lte: endDate
}
})
.populate('user_id')
.exec(function(err, comments) {
if(err) {
return res.send(err);
}
res.json(comments);
});
By the way, not related to the main question, but since you are not doing any change in the server I think a GET would be a better option.
I also abbreviated your query, $and is not necessary here.
EDIT
You can add filtering to populate, in your case:
.populate({
path: 'user_id',
match: {
facebook_username: fb_username
}
})

Finding Mongoose subdocument by time updated

I have two Mongoose Schemas:
var ItemSchema = new Schema({
trade: {
type: Schema.Types.ObjectId,
ref: 'Trade'
}
});
var Item = mongoose.model('Item', ItemSchema);
and
var TradeSchema = new Schema({
expiresOn: {
type: Date
}
});
var Trade = mongoose.model('Trade', TradeSchema);
I am trying to use Item.find() to find a item if its trade date is less than the date the user passes in via the query string in the request. I'm using the following code:
if (req.query.expiresBefore) {
Item.find({
'trade.expiresOn': {
$lte: req.query.expiresBefore
}
}, function (err, trades) {
console.log(trades)
})
}
However, I am receiving an empty array on the console.log() call. Using $gte also returns an empty array (though my research tells me that I need to use $lte for my purposes). What do I need to do to find the item document by matching the property (expiresOn) of its child (Trade)?
Referring to this one Stackoverflow question, what you want to do is not possible.I am not sure why you set the schemas like this, but if you want to keep them as they are. I would suggest you make a little change like the following
var ItemSchema = new Schema({
trade: {
type: Schema.Types.ObjectId,
ref: 'Trade'
}
});
var Item = mongoose.model('Item', ItemSchema);
and
var TradeSchema = new Schema({
itemId: { //add this
type: Schema.Types.ObjectId,
ref: 'Item'
},
expiresOn: {
type: Date
}
});
var Trade = mongoose.model('Trade', TradeSchema);
if (req.query.expiresBefore) {
Trade.
find({
'expiresOn': {
$lte: req.query.expiresBefore
}
}).
populate('itemId').
exec(function (err, trades) {
console.log(trades)
});
}

Storing a copy of a document embedded in another document in MongoDB via Mongoose

We have a requirement to store a copy of a Mongo document, as an embedded subdocument in another document. It should have a reference to the original document. The copied document needs to be a deep copy, like a snapshot of the original.
The original document's schema (defined with Mongoose) is not fixed -
it currently uses a type of inheritance to allow different additions to the Schema depending on "type".
Is there a way to such a flexible embedded schema within a Mongoose model?
Is it something that needs to be injected at runtime, when we can know
the schema?
The models / schemas we have currently look like this:
///UserList Schema: - this should contain a deep copy of a List
user: {
type: ObjectId,
ref: 'User'
},
list: {
/* Not sure if this is a how we should store the reference
type: ObjectId,
ref: 'List'
*/
listId: ObjectId,
name: {
type: String,
required: true
},
items: [{
type: ObjectId,
ref: 'Item'
}]
}
///List Schema:
name: {
type: String,
required: true
},
items: [{
type: ObjectId,
ref: 'Item'
}],
createdBy: {
type: ObjectId,
ref: 'User'
}
The code we currently have uses inheritance to allow different item types. I realise this technique may not be the best way to achieve the flexibility we require and is not the focus of my question.
///Item Model + Schema
var mongoose = require('mongoose'),
nodeutils = require('util'),
Schema = mongoose.Schema,
ObjectId = Schema.Types.ObjectId;
function ItemSchema() {
var self = this;
Schema.apply(this, arguments);
self.add({
question: {
type: String,
required: true
}
});
self.methods.toDiscriminator = function(type) {
var Item = mongoose.model('Item');
this.__proto__ = new Item.discriminators[type](this);
return this;
};
}
nodeutils.inherits(ItemSchema, Schema);
module.exports = ItemSchema;
I think you just need to create an empty {} object for the document in your parent mongoose schema. This way you´ll be able to store any object with a hardcopy of all it´s data.
parentobj : {
name: Sring,
nestedObj: {}
}
I think at this point, what you´ll need is to mark your nested objet as modified before you save it. Here is an example of my mongoose code.
exports.update = function(req, res) {
User.findById(req.params.id, function (err, eluser) {
if (err) { return handleError(res, err); }
if(!eluser) { return res.send(404); }
var updated = _.merge(eluser, req.body);
//This makes NESTEDDATA OBJECT to be saved
updated.markModified('nestedData');
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, eluser);
});
});
};
In addition, if you need an array of different documents in nestedDocument, the right way is this one:
parentobj : {
name: Sring,
nestedObjs: [Schema.Types.Mixed]
}
Please check Mongoose Schema Types carefully
EDIT
As you said, I´ll add you final solution as including ItemSchema in the nestedObj array definition to clarifythe type of the object to a determined one..
var ItemSchema = new Schema({
item1: String,
item2: String
});
var parentobj = new Schema({
name: Sring,
nestedObj: [ItemSchema]
});
EDIT 2:
Remember adding new Items to the nestedArray, must be done with nestedArray.push(item)
regards!!

Resources