Mongodb update array for specific fields only - node.js

I have a json array reorderList for a topic:
const reorderList = [
{ _id: '5e6b419c76a16d5c44d87132', order: 0 },
{ _id: '5e6b41a276a16d5c44d87139', order: 1 },
{ _id: '5e6b41a776a16d5c44d87140', order: 2 }
]
And my TopicSchema is like this:
var TopicSchema = new Schema({
topicTitle: String,
topicQuestion: [
{
questionTitle: String,
answer: String,
order: Number
}
]
}
Now I want to update my topic questions order based on the reorderList's _id.
But the below statement will replace all the things from topicQuestion (e.g. questionTitle and answer will be removed)
Topic.findOneAndUpdate(
{ '_id': topicId },
{ $set: { 'topicQuestion': reorderList } }, //replaces here
{ upsert: true },
function (err, response) {
...
});
How to update it based on reorderList and also keep the original data inside topicQuestion?

The schema that you're using is badly designed. What you can do here is create another schema, TopicQuestionSchema and put a ref to the topic it belongs to.
var TopicQuestionSchema = new Schema({
questionTitle: String,
answer: String,
order: Number,
topic: {type: ObjectId, ref: 'Topic'} // the name of your model
}
This way you can still keep track of the topic the questions belong to, and still be able to update the order easily.

Related

Querying objects for an array of attributes in Mongoose

I am trying to fetch chats for an array of users.
Chats schemas are defined like this:
const ChatSchema = new Schema<IChatSchema>(
{
messages: [
{
type: Schema.Types.ObjectId,
ref: "MessageSchema",
},
],
participants: [
{
type:Schema.Types.ObjectId,
ref: "UserSchema",
}
]
},
{
timestamps: true,
}
);
I have two usersnames 'A' and 'B' and I want to query common chats of those two users. Any idea how to do it?
User schema
const UserSchema = new Schema<IUserSchema>(
{
username: {
type: String,
required: true,
unique:true,
},
},
{
timestamps: true,
}
);
I tried this approach but did not work.
let chat = await Chats.find({
participants: { $elemMatch: { username: usernames } },
})
I also tried this
let chat = await Chats.find({
"participants.username": { $all: usernames },
})
I can think of a few ways:
Option A: Use aggregation to lookup the participants, then match the usernames
Option B: Use find to retrieve the user records from Users, then query Chats for matching ObjectID values
Option C: modify the schema so the chats also contain the usernames, so you can query them directly

Mongoose/MongoDB : How to update item which is nested 4 levels down?

In my mongo database I have a tree like this:
-shopping-lists
|- list
|-products
|-item
|-item
|- list
|-products
|-item
|-item
I need to find which shopping list I'm updating and then go to products array, find an item inside and update one property.
I'm trying to solve this from few hours right now, but didn't find any solution.
How to update item which is nested 4 levels down??
After 6 hours of investigation, I have found an answer.
For any of you guys, having the same problem here is the answer.
While adding a new item to the Parent model, I've been just pushing these items as plain objects and the Parent model was like this:
const Parent = new Schema(
{
description: { type: String },
childrenArray: [Object],
},
{ timestamps: { createdAt: 'created_at' }, collection: 'shopping-lists' }
);
The problem was in childrenArray, that in the model I had declared this an array of objects.
Changing this to ChildrenModel solved the problem, cause now these items are saved as mongoose subdocuments and I can operate on then using $ operator.
new Schema looks like this:
const Children = new Schema(
{
prop1: { type: String, required: true },
prop2: { type: String, required: true },
prop3: { type: String },
},
{ timestamps: { createdAt: 'createdAt' } }
);
const Parent = new Schema(
{
description: { type: String },
products: [ItemSchema],
},
{ timestamps: { createdAt: 'created_at' }, collection: 'shopping-lists' }
);
Now I can easily access subdocuments like this:
const { name, isOrdered, author, voterIds, _id, listId } = req.body;
Parent.findOneAndUpdate(
{ _id: listId, 'children._id': _id },
{
$set: {
'children.$.isOrdered': true
}
},
(err, doc) => {
console.log(doc);
}
);
As far as i know We can update the nested document by positional operator that is $ but as according to the mongodb documentation we cannot update property nested more then 2 levels deep array. In simple you cannot use $ positional operator two times like "shoppinglists.$.list.$.products so its better to change the structure of your db to do it in better way

Count the number of occurence of a nested field

I am trying to count the number of documents with a matching criteria in a nested field.
My schema is defined as follows:
let userSchema = new Schema({
//...
interests: [{
type: Schema.Types.ObjectId,
ref: 'Interests'
}],
//...
});
let interestSchema = new Schema({
id: ObjectId,
name: {
type: String,
required: true
},
//...
});
The count must reflect how many times an interest with the same name is choosed by the users.
For example, I must have a result of 2 with the interest 'coding' in the following documents:
{
//Other Fields of user 1
"interests": [
{
"id": "XXX"
"name": "coding"
},
{
"id": "YYY"
"name": "surfing"
}]
}
{
//Other Fields of user 2
"interests": [
{
"id": "ZZZ"
"name": "coding"
}
]
}
I looked into the countDocuments method, but it doesn't seem to allow this kind of count.
EDIT + First solution:
This is how I managed to solve it:
const res = await UserModel.aggregate([
{
$unwind: '$interests'
},
{
$lookup: {
from: "interests",
localField: "interests",
foreignField: "_id",
as: "interests"
}
},
{
$match:{
"interests.name": name
}
},
{
$count: "count"
}
]);
return res[0].count;
The fact that the interests is a referenced type, I can not query for its name unless I pass the lookup stage. I am not sure if this is a good solution regarding performance, since the unwind stage must pass through all the users of the database and create a new element for each of their interests. This why I am not posting it as an answer
To work with elemMatch, I had to change the schema in order to embed the Interest in the User instead of referencing it:
let userSchema = new Schema({
//...
interests: [InterestSchema],
//...
});
let InterestSchema = new Schema({
id: ObjectId,
name: {
type: String,
required: true
},
//...
});
This is how I used the elemMatch:
const count = UserModel
.where('interests').elemMatch( interest => {
interest.where({ name: name });
})
.count();
As I mentioned in my question, the aggregate method works but I am not sure about its performance since I was using a referenced array instead of a sub-document, this is why I had to change the schema of my collection

Mongoose: how to structure a schema with a SET of subdocuments (one unique field)?

I'm trying to make the following schema to work:
var FormSchema = new mongoose.Schema({
form_code: { type: String, unique: true },
...
});
var UserSchema = new mongoose.Schema({
...
submissions: [{
form_code: { type: String, unique: true },
last_update: Date,
questions: [{
question_code: String,
answers: [Number]
}]
}],
});
The rationale here is that a user can have many unique forms submitted, but only the last submission of each unique form should be saved. So, ideally, by pushing a submission subdocument when updating a user, the schema would either add the submission object to the set, or update the subdocument containing that form_code.
The following code doesn't work as desired (it pushes the new subdocument even if the form_code is already present):
User.findOneAndUpdate(
{ _id: user.id },
{ $addToSet: { submissions: submission_object } },
function (err, user) {
// will eventually have duplicates of form_code at user.submissions
}
);
The above schema clearly doesn't work, what must be changed to achieve that "upsertToSet"?

Mongoose Relationship Populate Doesn't Return results

var SecuritySchema = new Mongoose.Schema({
_bids: [{
type: Mongoose.Schema.Types.ObjectId,
ref: 'BuyOrder'
}],
_asks: [{
type: Mongoose.Schema.Types.ObjectId,
ref: 'SellOrder'
}]
});
var OrdersSchema = new Mongoose.Schema({
_security: {
type: Mongoose.Schema.Types.ObjectId,
ref: 'Security'
},
price: {
type: Number,
required: true
},
quantity: {
type: Number,
required: true
}
});
// declare seat covers here too
var models = {
Security: Mongoose.model('Security', SecuritySchema),
BuyOrder: Mongoose.model('BuyOrder', OrdersSchema),
SellOrder: Mongoose.model('SellOrder', OrdersSchema)
};
return models;
And than when I save a new BuyOrder for example:
// I put the 'id' of the security: order.__security = security._id on the client-side
var order = new models.BuyOrder(req.body.order);
order.save(function(err) {
if (err) return console.log(err);
});
And attempt to re-retrieve the associated security:
models.Security.findById(req.params.id).populate({
path: '_bids'
}).exec(function(err, security) {
// the '_bids' array is empty.
});
I think this is some sort of naming issue, but I'm not sure, I've seen examples here and on the moongoose website that use Number as the Id type: http://mongoosejs.com/docs/populate.html
The ref field should use the singular model name
Also, just do:
models.Security.findById(req.params.id).populate('_bids').exec(...
My main suspicion given your snippet at the moment is your req.body.order has _security as a string instead of an array containing a string.
Also, you don't need an id property. Mongodb itself will automatically do the _id as a real BSON ObjectId, and mongoose will add id as a string representation of the same value, so don't worry about that.
While I don't understand your schema (and the circular nature of it?), this code works:
var order = new models.BuyOrder({ price: 100, quantity: 5});
order.save(function(err, orderDoc) {
var security = new models.Security();
security._bids.push(orderDoc);
security.save(function(err, doc) {
models.Security.findById({ _id: doc._id })
.populate("_bids").exec(function(err, security) {
console.log(security);
});
});
});
It:
creates a BuyOrder
saves it
creates a Security
adds to the array of _bids the new orderDoc's _id
saves it
searches for the match and populates
Note that there's not an automatic method for adding the document to the array of _bids, so I've done that manually.
Results:
{ _id: 5224e73af7c90a2017000002,
__v: 0,
_asks: [],
_bids: [ { price: 100,
quantity: 5,
_id: 5224e72ef7c90a2017000001, __v: 0 } ] }

Resources