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
});
Related
I am using findOneAndUpdate, where I want
to return updated document
i dont want to return the entire document but only the following:
one object out of an array + a virtual property in the document.
const notifications = {
to:
messages: [
{_id: "23452", title:"hello"}, {_id: "23452", title:"bye"}
]
...
}
so for example I would want to only return the object {_id: "23452", title:"bye"} AND unreadCount virtual field prop.
my code works so far as I am returning updated document and only the message I want, but I dont know how to return also the unreadCount prop.
schema:
const notificationSchema = new mongoose.Schema({
to: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
messages: [{
title: {
type: String,
required: true
},
isRead: {
type: Boolean,
default: false
},
createdAt: {
type: Date,
default: new Date()
}
}, ]
},
{timestamps: true, toObject: {virtuals: true}
});
notificationSchema.virtual('unreadCount').get(function() {
... return count;...
})
updateRead: async (userId, id) => {
const notification = await Notification.findOneAndUpdate({to: userId, 'messages._id': id}, {
$set: { "messages.$.isRead": true} },
{ select: {
messages: {
$elemMatch: {_id: id}
}
}, new: true});
}
I'm quite new to Mongoose (NoSQL) so I wonder how to calculate the number of users based on the date (updated).
Currently, I can get the total number of customers but I don't know how to get the number of users based on (today, this week or this month). In MySQL we can just put all the queries to get the information but I don't know how to do it in Mongoose in one query.
Here's the code
Customer Schema/Model
const customerSchema = Schema({
_id: Schema.Types.ObjectId,
fullname: {
type: String,
},
phone: {
type: String,
},
address: {
type: String,
},
updated: { type: Date, default: Date.now },
adminId: { type: String, required: true },
});
API
router.post("/getcustomer/:adminId", (req, res, next) => {
const adminId = req.params.adminId;
Customer.find({ adminId: adminId })
.sort({ updated: "descending" })
.exec()
.then((docs) => {
const response = {
count: docs.length,
customer_info: docs.map((doc) => {
return {
fullname: doc.fullname,
phone: doc.phone,
address: doc.address,
updated: doc.updated,
adminId: doc.adminId,
_id: doc._id,
};
}),
};
res.status(200).json(response);
})
.catch((err) => {
console.log(err);
res.status(500).json({
error: err,
});
});
});
You can group the customers by date using aggregation:
Customers.aggregate([{$group: { _id: { $dateToString: { format: "%Y-%m-%d", date: "$updated" }}, count:{$sum:1} }}])
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.
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.
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);
});
});