mongoose/mongo insert multiple elements into sub document - mongodb

I'm trying to insert multiple items into sub document using mongoose:
User.update(
{'_id': userId, 'pages._id': pageId},
{ $push:
{'pages.$.items':
{ $each: data }
}
}, function(err, result) {
console.log(result); // prints { n: 1, nModified: 1, ok: 1 }
}
);
What am I doing wrong? It't neither throwing an error nor inserting any item.
Edit: User schema:
var PageSchema= new Schema({
items: [{
a: String,
b: String
}]
});
var UserSchema = new Schema({
email: String,
pages: [PageSchema]
});
mongoose.model('User', UserSchema);
I'm able to insert 1 item using this code:
User.findOne({'_id': userId, 'page._id': pageId}).exec(function(err, user) {
user.pages.id(pageId).items.push(data[0]);
user.save();
});
but I guess it's not good approach for inserting multiple elements.

Related

mongoose select query with count from other collections

I am working with node(express) with mongoose and I have two collections,
Users
Comments
I added the sample Schema(added few fields only)
const UserSchema = mongoose.Schema({
name: String,
email: String,
});
const CommentsSchema = mongoose.Schema({
comments: String,
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
text: String,
});
So I trying to fetch the users list and no of comments count based on user..
Expecting output like below:
data = [
{
name: 'abcd',
email: 'aa#test.com',
commentsCount: 5
},
{
name: 'xxx',
email: 'xx#test.com',
commentsCount: 3
}
]
I am not sure how to get the results, because we don;t have ref in user table..
userModel.find({}).exec((err, users) => {
if (err) {
res.send(err);
return;
}
users.forEach(function(user){
commentsModel.countDocuments({user_id: users._id}).exec((err, count) => {
if(!err){
user.commentsCount = count;
}
})
});
console.log('users', users)
});
Can you anyone please help to fix, I needs to list out the users and count of comments

findOneAndUpdate document with array

Two questions here.
What is the correct way to findOneAndUpdate when there is an array? The example below errors with err MongooseError [CastError]: Cast to embedded failed for value.
Should you arrays of objects become separate collections?
* Example *
var ProductSchema = new Schema({
_id: Schema.Types.ObjectId,
product_name: String
});
var purchaseOrderSchema = new Schema(
{
purchaseOrderNo: Number,
products: [ProductSchema]
}
);
const purchaseOrder = new PurchaseOrder(req.body);
PurchaseOrder.findOneAndUpdate(
{ _id: req.body._id },
{
$set: req.body,
$push: req.body.products
},
{ upsert: true, new: true }
)
.then((result) => {
console.log('result', result);
res.status(200).json(result);
})
.catch((err) => {
console.log('err', err);
res.status(500).json({ error: err });
});
const body = {
_id: 'skjdhflksjdf',
purchaseOrderNo: 1,
products: [
{
_id: '111',
product_name: 'Cup'
},
{
_id: '222',
product_name: 'Spoon'
}
]
}
In the ProductSchema the type of _id field to set to ObjectId. The product id 111 and 222 are not a valid ObjectId and it fails to cast it. You can update the type of _id in ProductSchema to Number for this to work
var ProductSchema = new Schema({
_id: Number,
product_name: String
});

mongoose .findById & .find return object & array, causes compatibility issue

When the user visits a certain page of my App, the Component dispatches an action to fetch information. Namely, the action performs the following operations:
Base.find({req.params.id})
BaseUser.find({ baseId: req.params.id }) **returns multiple docs**
Message.find({ baseId: req.params.id }) **returns multiple docs**
The operation happens in this order. I could query the first one via .findById, but for uniformity of the problem I chose .find(). The problem now is that the results of
Promise.all([
Base.find({ _id: req.params.id }),
BaseUser.find({ baseId: req.params.id }),
Message.find({ baseId: req.params.id })
])
come in an array, like so:
[
[ { created: 2018-08-29T23:59:35.380Z,
_id: 5b8741151985662f10d04fdb,
creatorId: 5b86f7970cd98b2004969bf0,
title: 'testBase1',
} ],
[ { created: 2018-08-30T00:57:57.764Z,
acceptedMembership: true,
isCreator: true,
_id: 5b8741151985662f10d04fdc,
userId: 'tester1',
baseId: 5b8741151985662f10d04fdb }
],
[ { created: 2018-08-30T00:58:09.182Z,
_id: 5b8741211985662f10d04fdd,
baseId: 5b8741151985662f10d04fdb,
content: 'testMessage1' }
]
]
This quite obviously causes problems when further trying to map/filter/res.json() the data. Is there any known way to return this in a single array, or even better, pass it to the front-end (redux action) as an object? Does anyone know of a better solution which handles this problem slightly differently, and prevents me from fetching each of those methods on subcomponents ?
update:
I have now constructed this, which is fairly ugly to look at:
let completeObject = {
base: {},
users: [],
messages: []
};
Base.findById(req.params.id)
.then(data => {
completeObject.base = data;
return data;
})
.then(data => {
BaseUser.find({ baseId: req.params.id })
.then(data => {
completeObject.users = data;
return data;
})
.then(data => {
Message.find({ baseId: req.params.id }).then(data => {
completeObject.messages = data;
return res.json(completeObject);
});
});
})
Why don't you setup ref in the Base model to the BaseUser and Message and then use populate to fill those arrays and get one object as result filled with the arrays of BaseUser and Message?
From what I see you key on the req.params.id which means you have a cross-reference between those collections anyway.
Here is an example:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var BaseSchema = Schema({
_id: Schema.Types.ObjectId,
creatorId: Schema.Types.ObjectId,
title: String,
users: [{ type: Schema.Types.ObjectId, ref: 'User' }],
messages: [{ type: Schema.Types.ObjectId, ref: 'Message' }],
});
var UserSchema = Schema({
_id: Schema.Types.ObjectId,
acceptedMembership: Boolean,
isCreator: Boolean,
userId: String,
baseId: Schema.Types.ObjectId
});
var MessageSchema = Schema({
_id: Schema.Types.ObjectId,
baseId: Schema.Types.ObjectId,
content: String
});
var Base = mongoose.model('Base', BaseSchema);
var User = mongoose.model('User', UserSchema);
var Message = mongoose.model('Message', MessageSchema);
Now that the schemas are defined (and you added some records) you could find a Base by _id and populate users and messages:
Base.
findOne({ _id: req.params.id }).
populate('users').
populate('messages').
exec(function (err, base) {
if (err) return handleError(err);
console.log(base);
});
You should check the mongoose documentation on how to save / populate references etc.

Mongoose pull ObjectId from array

i'm trying to do a pretty simple operation, pull an item from an array with Mongoose on a Mongo database like so:
User.update({ _id: fromUserId }, { $pull: { linkedUsers: [idToDelete] } });
fromUserId & idToDelete are both Objects Ids.
The schema for Users goes like this:
var UserSchema = new Schema({
groups: [],
linkedUsers: [],
name: { type: String, required: true, index: { unique: true } }
});
linkedUsers is an array that only receives Ids of other users.
I've tried this as well:
User.findOne({ _id: fromUserId }, function(err, user) {
user.linkedUsers.pull(idToDelete);
user.save();
});
But with no luck.
The second option seem to almost work when i console the lenghts of the array at different positions but after calling save and checking, the length is still at 36:
User.findOne({ _id: fromUserId }, function(err, user) {
console.log(user.linkedUsers.length); // returns 36
user.linkedUsers.pull(idToDelete);
console.log(user.linkedUsers.length); // returns 35
user.save();
});
So it looks like i'm close but still, no luck. Both Ids are sent via the frontend side of the app.
I'm running those versions:
"mongodb": "^2.2.29",
"mongoose": "^5.0.7",
Thanks in advance.
You need to explicitly define the types in your schema definition i.e.
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
linkedUsers: [{ type: Schema.Types.ObjectId, ref: 'User' }]
and then use either
User.findOneAndUpdate(
{ _id: fromUserId },
{ $pullAll: { linkedUsers: [idToDelete] } },
{ new: true },
function(err, data) {}
);
or
User.findByIdAndUpdate(fromUserId,
{ $pullAll: { linkedUsers: [idToDelete] } },
{ new: true },
function(err, data) {}
);
I had a similar issue. I wanted to delete an object from an array, using the default _id from mongo, but my query was wrong:
const update = { $pull: { cities: cityId }};
It should be:
const update = { $pull: { cities: {_id: cityId} }};

Mongoosejs populate and aggregate subdocuments

I have a '3-layered' relationship in MongooseJS like so, it's two one-to-many relationships between subdocuments. Like so:
var BroadcastSchema = new Schema({
...
_donationAddresses: [{
type: Schema.Types.ObjectId,
ref: 'DonationAddress'
}]
});
var DonationAddressSchema = new Schema({
...
_donations: [{
type: Schema.Types.ObjectId,
ref: 'Donation'
}]
});
var DonationSchema = new Schema({
...
amount: Number
});
I want to get the $sum total of the amount:Number on the DonationSchema
So far I've populated the Donation by using a work-around listed here (because as far as I know you can't populate a populate so far as I know)
Broadcast.find()
.exec(function(err, broadcasts) {
// this works
var iter = function(broadcast, callback) {
DonationAddress.populate(broadcast._donationAddresses, {
path: '_donations'
}, callback);
};
// tried to iterate over the donation address and
// aggregate the _donations.amount
var iter2 = function(broadcast, callback) {
DonationAddress.aggregate([{
$match: {
_id: broadcast._donationAddresses
}
}, {
$unwind: "$_donations"
}, {
$group: {
_id: "$_id",
total: {
$sum: "$_donations.amount"
}
}
}], callback);
};
async.each(broadcasts, iter, function done(err) {
async.each(broadcasts, iter2, function done(err) {
res.json(broadcasts);
});
});