Accessing nested documents within nested documents - mongodb

I'm having a problem that is really bugging me. I don't even want to use this solution I don't think but I want to know if there is one.
I was creating a comment section with mongodb and mongoose and keeping the comments attached to the resource like this:
const MovieSchema = new mongoose.Schema({
movieTitle: {type: String, text: true},
year: Number,
imdb: String,
comments: [{
date: Date,
body: String
}]
})
When editing the comments body I understood I could access a nested document like this:
const query = {
imdb: req.body.movie.imdb,
"comments._id": new ObjectId(req.body.editedComment._id)
}
const update = {
$set: {
"comments.$.body": req.body.newComment
}
}
Movie.findOneAndUpdate(query, update, function(err, movie) {
//do stuff
})
I then wanted to roll out a first level reply to comments, where every reply to a comment or to another reply just appeared as an array of replies for the top level comment (sort of like Facebook, not like reddit). At first I wanted to keep the replies attached to the comments just as I had kept the comments attachted to the resource. So the schema would look something like this:
const MovieSchema = new mongoose.Schema({
movieTitle: {type: String, text: true},
year: Number,
imdb: String,
comments: [{
date: Date,
body: String,
replies: [{
date: Date,
body: String
}]
}]
})
My question is how would you go about accessing a nested nested document. For instance if I wanted to edit a reply it doesn't seem I can use two $ symbols. So how would I do this in mongodb, and is this even possible?
I'm pretty sure I'm going to make Comments have its own model to simplify things but I still want to know if this is possible because it seems like a pretty big drawback of mongodb if not. On the other hand I'd feel pretty stupid using mongodb if I didn't figure out how to edit a nested nested document...

according to this issue: https://jira.mongodb.org/browse/SERVER-27089
updating nested-nested elements can be done this way:
parent.update({},
{$set: {“children.$[i].children.$[j].d”: nuValue}},
{ arrayFilters: [{ “i._id”: childId}, { “j._id”: grandchildId }] });
this is included in MongoDB 3.5.12 development version, in the MongoDB 3.6 production version.
according to https://github.com/Automattic/mongoose/issues/5986#issuecomment-358065800 it's supposed to be supported in mongoose 5+
if you're using an older mongodb or mongoose versions, there are 2 options:
find parent, edit result's grandchild, save parent.
const result = await parent.findById(parentId);
const grandchild = result.children.find(child => child._id.equals(childId))
.children.find(grandchild => grandchild._id.equals(grandchildId));
grandchild.field = value;
parent.save();
know granchild's index "somehow", findByIdAndUpdate parent with:
parent.findByIdAndUpdate(id,
{ $set: { [`children.$.children.${index}.field`]: value }});

Related

Mongoose update entire nested elements

I am using mongoose and MongoDB. Is there any way to update the entire schedule array in the schema? or do I have to use a for loop and insert one by one?
My req.body is an array to replace the entire schedules array object.
[{"_id":"1","description":"hi","time":"Jul 29, 2020 8:55 PM","url":"aaa.com"},{"_id":"2","description":"hi","time":"Jul 29, 2020 8:58 PM","url":"bbb.com"},{"_id":"3","description":"hi"}]
here is my schema.
const schedule = new mongoose.Schema({
user_id: Number,
schedules: [{
description: String,
time: String,
url: String
}]
});
Thank you.
If you're using mongoose, we can avoid using $set. #jitendra's answer is a pure mongodb query which you could run in the mongo shell.
You can refer to this link https://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate .
As the link says,
var query = { name: 'borne' };
Model.findOneAndUpdate(query, { name: 'jason bourne' }, options, callback)
// is sent as
Model.findOneAndUpdate(query, { $set: { name: 'jason bourne' }}, options, callback)
This helps prevent accidentally overwriting your document with { name: 'jason bourne' }.
So in your case we just need to write :
Schedule.findOneAndUpdate({_id: "id_of_object"}, {schedules: req.body});
That should do it for you. But internally, as the doc says, it is being sent as:
Schedule.findOneAndUpdate({_id: "id_of_object"}, {$set: {schedules: req.body}})
Ofcourse this assumes that your req.body consists of the array of schedules only. Most likely you're sending it as an object from the front end so maybe it's req.body.object_name . Up to you.
You can update the entire schedule array by using $set , try as follow:
db.collection.update({query},{ $set: { schedules: req.body } },{option});
Here your req.body should be the array with same keys as per defined in your schema.

MongoDB: use of subdocuments

TLDR; Should you use subdocuments or relational Id?
This is my PostSchema:
const Post = new mongoose.Schema({
title: {
type: String,
required: true
},
body: {
type: String,
required: true
},
comments: [Comment.schema]
})
And this is my Comment Schema:
const Comment = new mongoose.Schema({
body: {
type: String,
required: true
}
})
In Postgres, I would have a post_id field in Comment, instead of having an array of comments inside Post. I am sure you can do the same in MongoDB but I don't know which one is more conventional. If people use subdocuments over references (and joining tables) in MongoDB, why is that? In other words, why should I ever use subdocuments? If it's advantageous, should I do the same in Postgres as well?
What I understood from your question, answering based on that.
If you will keep sub documents, you don't have to query two tables to know comments specific to one post.
Let's say we have following db structure for post:-
[{
_id:1,
title:'some title',
comments:[
{
...//some fields that belongs to comments
} ,
{
...//some fields that belongs to comments
} ,
...
]
},
{
_id:2,
title:'some title',
comments:[
{
...//some fields that belongs to comments
} ,
{
...//some fields that belongs to comments
} ,
...
]
}]
Now you can query based on _id of the post (1) and can get comments array that belongs to the specific post.
If you will just keep the comment's id inside post, you have to query both the tables, which I don't think is a good idea.
EDIT :-
If you are keeping post id inside comments record, then it will help you to track which comment is for which post i.e. if you want to query comments table based on post id and you need only fields from comments records.
What I think, use case will be which post contains what all comments. So keeping comment inside post will give you comments fields as well as fields from post record.
So it's totally depends on your requirement, how you will design your data structure.

Can't update or query embedded sub-documents using MongoDB? Now what?

I took the NoSQL plunge against all my RDBMS prejudices from my past. But I trusted. Now I find myself 3 months into a project and the exact reasons we adhered to RDMS principles seem to be biting me in the butt. I think I just discovered here on stackoverflow that I can't work with twice embedded arrays. I followed the noSQL, embedded document approach like a good kool-aid drinker and feel like I've been betrayed. Before I swear off noSQL and go back and refactor my entire code-base to adhere to new 'normalized' model I'd like to here from some no-sql champions.
Here is my model using one big document with embedded docs and the works:
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
User = mongoose.model('User');
var Entry = new Schema({
text: String,
ups: Number,
downs: Number,
rankScore: Number,
posted: {
type: Date,
default: Date.now
},
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});
var boardSchema = new Schema({
theme: String,
created: {
type: Date,
default: Date.now
},
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
entered: {
type: Boolean,
default: false
},
entries: [Entry],
participants: [{
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User'},
date: { type: Date, default: Date.now },
topTen: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Entry'} ]
}]
});
mongoose.model('Board', boardSchema);
Basically, I want to query the document by Board._id, then where participants.user == req.user.id, I'd like to add to the topTen[] array. Note participants[] is an array within the document and topTen is an array within participants[]. I've found other similar questions but I was pointed to a Jira item which doesn't look like it will be implemented to allow the use of $ positional operation in multiple embedded arrays. Is there no way to do this now? Or if anyone has a suggestion of how to model my document so that I don't have to go full re-write with a new normalized reference model...please help!
Here are some of my query attempts from what I could find online. Nothing worked for me.
Board.update({_id: ObjectId('56910eed15c4d50e0998a2c9'), 'participants.user._id': ObjectId('56437f6a142974240273d862')}, {$set:{'participants.0.topTen.$.entry': ObjectId('5692eafc64601ceb0b64269b') }}
I read you should avoid such 'nested' designs but with the embedded model its hard not to. Basically this statement says to me "don't embed" go "ref".

MongoDB: Get all mentioned items

I've got two relations in my Mongoose/MongoDB-Application:
USER:
{
name: String,
items: [{ type: mongoose.Schema.ObjectId, ref: 'Spot' }]
}
and
ITEM
{
title: String,
price: Number
}
As you can see, my user-collection containing a "has-many"-relation to the item-collection.
I'm wondering how to get all Items which are mentioned in the items-field of on specific user.
Guess its very common question, but I haven't found any solution on my own in the Docs or elsewhere. Can anybody help me with that?
If you are Storing items reference in user collection,then fetch all items from user,it will give you a array of object ids of items and then you can access all items bases on their ids
var itemIdsArray = User.items;
Item.find({
'_id': { $in: itemIdsArray}
}, function(err, docs){
console.log(docs);
});
You can get the items at the same time you query for the user, by using Mongoose's support for population:
User.findOne({_id: userId}).populate('items').exec(function(err, user) {
// user.items contains the referenced docs instead of just the ObjectIds
});

Searching embedded documents Mongoose + nodejs

Im new to Mongoose, and i'm facing a problem in searching.
These are my Schemas:
var CommentSchema = new Schema({
body : String
, comments : [CommentSchema]
});
var PostSchema = new Schema({
body : String
, comments : [CommentSchema]
});
There is a deep nesting of comments. When somebody answers to the existing comment, how can I find that one?
you can look at the mongoose test suite on github for examples.
model_querying_test
Here is what you are looking for:
test finding based on embedded document fields:
function () {
var db = start(), BlogPostB = db.model('BlogPostB', collection);
BlogPostB.create({comments: [{title: 'i should be queryable'}]}, function (err, created) {
should.strictEqual(err, null);
BlogPostB.findOne({'comments.title': 'i should be queryable'}, function (err, found) {
should.strictEqual(err, null);
found._id.should.eql(created._id);
db.close();
});
});
},
One solution to this is to store Comments as a separate Model which you can query directly, and store references to the related ObjectIds and paths between Comments and Posts.
Using the Populate feature in Mongoose related documents can function similarly to embedded documents, although there are some important differences in the way you query them, and you have to be more careful to keep the relationships populated.
Set it up like this:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
, ObjectId = Schema.Types.ObjectId;
var PostsSchema = new Schema({
body : String,
stories : [{ type: ObjectId, ref: 'Story' }]
});
var CommentsSchema = new Schema({
body : String,
post : { type: ObjectId, ref: 'Post' },
comments : [{ type: ObjectId, ref: 'Comment' }]
});
var Story = mongoose.model('Post', PostsSchema);
var Comment = mongoose.model('Comment', CommentsSchema);
If you do it this way it requires more queries to get the post with all its comments (which will be slower than being able to load the Post and its complete comment hierarchy with a single query) however you'll be able to query comments directly and retrieve the Post they were made on (but not easily find the full path to the comment when its nested).
These are all trade-offs; the best decision (either to recursively search for comments, or store them independently then recursively load them) should be made in the context of your application and its expected usage patterns.
One other caveat; the populate feature is currently limited to a single-level of linked ObjectIds; you have to call it on each comment that is returned to get the full nested dataset. There are several plugins that help with this, such as mongoose-subpopulate, and soon enough it'll be supported natively in Mongoose - see the github issue here.