Querying Embedded Documents with NodeJS and Mongoose - node.js

I need to query the following data from mongodb:
Project has many Regions, a Region has many Links
Here's the data:
{ "_id" : ObjectId( "4f26a74f9416090000000003" ),
"description" : "A Test Project",
"regions" : [
{ "title" : "North America",
"_id" : ObjectId( "4f26a74f9416090000000004" ),
"links" : [
{ "title" : "A Really Cool Link" } ] },
{ "description" : "That Asia Place",
"title" : "Asia",
"_id" : ObjectId( "4f26b7283378ab0000000003" ),
"links" : [] },
{ "description" : "That South America Place",
"title" : "South America",
"_id" : ObjectId( "4f26e46b53f2f90000000003" ),
"links" : [] },
{ "description" : "That Australia Place",
"title" : "Australia",
"_id" : ObjectId( "4f26ea504fb2210000000003" ),
"links" : [] } ],
"title" : "Test" }
Here's my model setup:
// mongoose
var Link = new Schema({
title : String
, href : String
, description : String
});
var Region = new Schema({
title : String
, description : String
, links : [Link]
});
var Project = new Schema({
title : String
, description : String
, regions : [Region]
});
mongoose.model('Link', Link);
mongoose.model('Region', Region);
mongoose.model('Project', Project);
var Link = mongoose.model('Link');
var Region = mongoose.model('Region');
var Project = mongoose.model('Project');
The query that I'm struggling with is returning a single region object matched on ID. I'm fine returning all of the regions with all of their respective links, but limiting it to the ID of just one region has been a problem.
Here's my broken route that I'm working with:
app.get('/projects/:id/regions/:region_id', function(req, res){
Project.findById(req.param('id'), function(err, project) {
if (!err) {
project.regions.findById(req.params.region_id, function(err, region) {
if (!err) {
res.render('project_regions', {
locals: {
title: project.title,
project: project
}
});
}
});
} else {
res.redirect('/')
}
});
});
I know the above will not work because the object returned by "findById" does not respond to findByID, but it illustrates what I'm trying to do. I tried doing a custom query, but it always returned the entire document, not the single Region that I wanted.
I also tried querying directly off the Region Schema, but that is not returning any results. I assume to query from a Schema that's a subdocument I would have to contextually provide what the parent document is.
In other words, the following does not return an "region" object with data:
app.get('/projects/:id/regions/:region_id', function(req, res){
Region.findById(req.params.region_id, function(err, region) {
if (!err) {
res.render('project_regions_show', {
locals: {
title: "test",
region: region
}
});
} else {
res.redirect('/')
}
});
});
Appreciate any help.

bstar,
You can prevent sub documents from loading by using the query.select:
ie.
Entity.find({ ... })
.select({childentities: 0}) //excludes childentities
.exec(function(err, entities){
...
res.json({entities: entities});
});

I'd recommend extracting the regions into a separate collection with a "projectId: ObjectId()" field. That may give you the querying flexibility you're looking for.

Related

Prevent mongoose "Model.updateOne" from updating ObjectId(_id) of the model when using "$set"

I'm updating the age and name of a character with a specific _id from an array of characters that is inside a document of model Drama.
The document I'm working with:-
{
"_id" : ObjectId("619d44d2ec2ca20ca0404b5a"),
"characters" : [
{
"_id" : ObjectId("619fdac5a03c8b10d0b8b13c"),
"age" : "23",
"name" : "Vinay",
},
{
"_id" : ObjectId("619fe1d53810a130207a409d"),
"age" : "25",
"name" : "Raghu",
},
{
"_id" : ObjectId("619fe1d53810a130207a502v"),
"age" : "27",
"name" : "Teju",
}
],
}
So to update the character Raghu I did this:-
const characterObj = {
age: "26",
name: "Dr. Raghu",
};
Drama.updateOne(
{ _id: req.drama._id, "characters._id": characterId },
{
$set: {
"characters.$": characterObj,
},
},
function(err, foundlist) {
if (err) {
console.log(err);
} else {
console.log("Update completed");
}
}
);
// req.drama._id is ObjectId("619d44d2ec2ca20ca0404b5a")
// characterId is ObjectId("619fe1d53810a130207a409d")
This updated the character but it also assigned a new ObjectId to the _id field of the character. So, I'm looking for ways on how to prevent the _id update.
Also, I know I can set the individual fields of character instead of assigning a whole new object to prevent that but it will be very tedious if my character's object has a lot of fields.
//Not looking to do it this way
$set: {
"characters.$.age": characterObj.age,
"characters.$.name": characterObj.name,
},
Thanks.
I found something here, just pre define a schema (a blueprint in a way) that affects the id
var subSchema = mongoose.Schema({
//your subschema content
},{ _id : false });
Stop Mongoose from creating _id property for sub-document array items
Or I would say, when you create a character assign it a custom id from the start, that way it will retain that id throughout.
I'm leaving this question open as I would still like to see a simpler approach. But for now, I did find one easy alternative solution for this issue which I'm will be using for some time now until I find a more direct approach.
In short - Deep merge the new object in the old object using lodash and then use the new merged object to set field value.
For example, let's update the character Raghu from my question document:-
First install lodash(Required for deep merging objects) using npm:
$ npm i -g npm
$ npm i --save lodash
Import lodash:
const _ = require("lodash");
Now update the character Raghu like this:-
const newCharacterObj = {
age: "26",
name: "Dr. Raghu",
};
Drama.findById(
{ _id: req.drama._id, "characters._id": characterId },
"characters.$",
function(err, dramaDocWithSpecificCharacter) {
console.log(dramaDocWithSpecificCharacter);
// ↓↓↓ console would log ↓↓↓
// {
// "_id" : ObjectId("619d44d2ec2ca20ca0404b5a"),
// "characters" : [
// {
// "_id" : ObjectId("619fe1d53810a130207a409d"),
// "age" : "25",
// "name" : "Raghu",
// }
// ],
// }
const oldCharacterObj = dramaDocWithSpecificCharacter.characters[0];
const mergedCharacterObjs = _.merge(oldCharacterObj, newCharacterObj);
// _.merge() returns a deep merged object
console.log(mergedCharacterObjs);
// ↓↓↓ console would log ↓↓↓
// {
// _id: 619fe1d53810a130207a409d,
// age: "26",
// name: "Dr. Raghu",
// };
Drama.updateOne(
{ _id: req.drama._id, "characters._id": characterId },
{
$set: {
"characters.$": mergedCharacterObjs,
},
},
function(err, foundlist) {
if (err) {
console.log(err);
} else {
console.log("Update completed");
}
}
);
}
);
// req.drama._id is ObjectId("619d44d2ec2ca20ca0404b5a")
// characterId is ObjectId("619fe1d53810a130207a409d")
Note: We can also use the native Object.assign() or … (spread operator) to merge objects but the downside of it is that it doesn’t merge nested objects which could cause issues if you later decide to add nested objects without making changes for deep merge.
You can pass your payload or request body like this if we provide _id it will prevent update to nested document
"characters" : [
{
"_id" : "619fdac5a03c8b10d0b8b13c",
"age" : "updated value",
"name" : "updated value",
}, {
"_id" : "619fe1d53810a130207a409d",
"age" : "updated value",
"name" : "updated value",
}, {
"_id" : "619fe1d53810a130207a502v",
"age" : "updated value",
"name" : "updated value",
}
],
It works for me for bulk update in array object

How do i retrieve just the child-object in Azure Cosmos using mongoose and Node.js?

I am using Azure cosmos db with the Mongodb API. Also i am using mongoose to create schemas and create new documents in the database. I am also using Node.js.
At this point I am considering using a One-to-Many relationship with embedded documents.
The data structure is like this :
{
"_id" : "locality1",
"_type" : "Locality",
"name" : "Wallmart",
"subsectionList" : [
{
"_id" : "subsection1",
"_type" : "SubSection",
"name" : "First floor",
"sensorList" : [
{
"_id" : "sensor1",
"_type" : "Sensor",
"placement" : "In the hallway"
},
{
"_id" : "sensor2",
"_type" : "Sensor",
"placement" : "In the ceiling"
}
]
},
{
"_id" : "subsection2",
"_type" : "SubSection",
"name" : "Second floor",
"sensorList" : [ ],
}
],
}
I want to retrieve ONLY the "sensor1"-object, not anything from the parent.
Using querying i am only able to retrieve the entire "locality1"-object, with all its underlying subsections and sensors. On a larger scale that is an unnecessary large amount of data.
Here is my query so far.
Locality.find().where('subsectionList.sensorList._id').equals("sensor1").then(doc => {
console.log(doc)
})
I appreciate any tips! :)
Based on my test, i can't get rid of the _id property anyway even though i followed the parameters which is mentioned here.
Locality.find({},'subsectionList', function (err, locas)
The above query still return the results including _id property.(It seems a default item)
I get a workaround from this blog that you could loop the array to filter your desired columns.
var mongoose = require('mongoose');
var COSMOSDB_CONNSTR= "mongodb://***.documents.azure.com:10255/db";
var COSMODDB_USER= "***";
var COSMOSDB_PASSWORD= "***";
mongoose.connect(COSMOSDB_CONNSTR+"?ssl=true&replicaSet=globaldb", {
auth: {
user: COSMODDB_USER,
password: COSMOSDB_PASSWORD
}
}).then(() => console.log('Connection to CosmosDB successful'))
.catch((err) => console.error(err));
const Locality = mongoose.model('Locality', new mongoose.Schema({
_id: String,
subsectionList: [{
sensorList: [{
_id: String,
_type: String,
placement: String
}]
}]
}));
Locality.find({},'subsectionList', function (err, locas) {
if (err) return handleError(err);
var returnArray = [];
for(var i = 0; i<locas.length;i++){
for(var j = 0; j<locas[i].subsectionList.length;j++){
for(var k = 0; k<locas[i].subsectionList[j].sensorList.length;k++){
if(locas[i].subsectionList[j].sensorList[k]._id == 'sensor1')
returnArray.push(locas[i].subsectionList[j].sensorList[k]);
}
}
}
console.log(returnArray);
});

$unwind nested document and $match

I have a nested document which looks like:
var User = new Schema({
id: String,
position: [{
title: String,
applied:[{
candidate_id: String,
name: String
}],
}],
What I am looking to do is return all of the 'applied' subdocuments which match a certain 'candidate_id'
What I have so far:
app.get('/applied', function(req, res){
var position = "58dc2bd4e7208a3ea143959e";
User.aggregate(
{$unwind : "$position"},
{$unwind : "$position.applied"},
{$match:{'position.applied.candidate_id': position}}).exec(function (err, result) {
console.log(result);
});
res.render('applied', { title: 'applied',layout:'candidate'});
});
I have another function which returns all the positions that match, and that code works:
app.post('/search', function (req, res) {
var position = new RegExp(req.body.position, 'i');
var location = new RegExp(req.body.location, 'i');
User.aggregate(
{$unwind : "$position"},
{$match:{'position.title': position,'position.location':location}}).exec(function (err, result) {
console.log(result);
res.send({ results: result });
});
});
So basically I am struggling with getting a sub-sub-document. Any idea where I'm going wrong?
Sample data:
{
"_id" : ObjectId("58c2871414cd3d209abf5fc9"),
"position" : [
{
"_id" : ObjectId("58d6b7e11e793c9a506ffe8f"),
"title" : "Software Engineer",
"applied" : [
{
"candidate_id" : "58d153e97e3317291gd80087",
"name" : "Sample user"
},
{
"candidate_id" : "58d153e97e3317291fd99001",
"name" : "Sample User2"
}
]
},
{
"_id" : ObjectId("58c2871414cd3d209abf5fc0"),
"title" : "Software Engineer",
}
],
}
What is going on above is there 2 positions, one of which (first entry) has 2 applied candidates, What I need to do is return the nested object if it matches the mongoose query.
Your code seems fine to me I have implemented same and it works for me only possible issue can be that your position="58dc2bd4e7208a3ea143959e" it might be talking it as a string just convert it to objectId by using the following code and check it should work for you.
var mongoose = require('mongoose');
var position = mongoose.Types.ObjectId("58dc2bd4e7208a3ea143959e");
User.aggregate(
{$unwind : "$position"},
{$unwind : "$position.applied"},
{$match:{'position.applied.candidate_id': position}}).exec(function (err, result) {
console.log(result);
});
res.render('applied', { title: 'applied',layout:'candidate'});
});

MongoDB updating embedded document isn't working

I'm trying to update embedded document, but it is not working. This is what documents look like:
{
"_id" : ObjectId("577c71735d35de6371388efc"),
"category" : "A",
"title" : "Test",
"content" : "Test",
"tags" : "test",
"comments" : [
{
"_id" : ObjectId("57811681010bd12923eda0ca"),
"author" : "creator",
"email" : "creator#example.com",
"text" : "helloworld!"
},
{
"_id" : ObjectId("57811b17b667676126bde94e"),
"author" : "creator",
"email" : "creator#example.com",
"text" : "helloworld2!"
}
],
"createdAt" : ...,
"updatedAt" : ...
}
you can see the comments field is embedded document that contains comments. I want to update specific comment, so I made query like this(node.js):
db.update('posts', {
_id: new ObjectID(postId), // ID of the post
comments: {
$elemMatch: {
_id: new ObjectId(commentId)
}
}
}, {
$set: {
"comments.$.author": newComment.author,
"comments.$.email": newComment.email,
"comments.$.text": newComment.text,
"comments.$.updatedAt": new Date()
}
}) ...
when I run this query, no error was shown but update wasn't applied. I tried this query too:
{
_id: new ObjectId(postId),
"comments._id": new ObjectId(commentId)
}
but not worked either. Am I missing something? I'm using Mongo v3.2.7.
Please try the below code. I think the "ObjectId" (i.e. case) should be the problem. Just check how you defined the object id and keep it consistent in the two places that you have used (i.e. posts _id and comments _id -> both places).
ObjectID = require('mongodb').ObjectID
The below code works fine for me. Basically, your query seems to be correct.
var Db = require('mongodb').Db, MongoClient = require('mongodb').MongoClient, Server = require('mongodb').Server, ReplSetServers = require('mongodb').ReplSetServers, ObjectID = require('mongodb').ObjectID, Binary = require('mongodb').Binary, GridStore = require('mongodb').GridStore, Grid = require('mongodb').Grid, Code = require('mongodb').Code, assert = require('assert');
var db = new Db('localhost', new Server('localhost', 27017));
db.open(function(err, db) {
var collection = db.collection("posts");
var postId = '577c71735d35de6371388efc';
var commentId = '57811681010bd12923eda0ca';
var query = {
_id : new ObjectID(postId),
comments : {
$elemMatch : {
_id : new ObjectID(commentId)
}
}
};
collection.update(query, {
$set : {
"comments.$.author" : "new author",
"comments.$.email" : "newemail#gmail.com",
"comments.$.text" : "new email updated",
"comments.$.updatedAt" : new Date()
}
}, {
multi : false
}, function(err, item) {
assert.equal(null, err);
console.log("comments updated ..." + JSON.stringify(item));
});
});

Mongoose/Mongo faster to have separate collections?

Currently I have a schema USERS with a sub doc VIEWED
As a 'user' views other users, their ID gets logged in the viewers sub doc
So when viewing, technically this gets all the users, then filters that through all the viewed users [for any given user]. So you get a list of unique/fresh users.
My method is currently fetching the list of users - Query 1
Then its fetching the list of viewed users (for a given user) - Query 2
Then using array.filter function to get a list of new users.
(using async parallel for those queries)
Question is, would it be faster to just have a separate document/collection that stores a list of viewed users for any given user. e.g:
{
userID: 1002,
viewedID: 9112
},
{
userID: 1002,
viewedID: 9222
},
{
userID: 1002,
viewedID: 9332
}
Is it possible for me to some how do a query that gets me a fresh list of users, so i don't have to do the computation myself. i.e let mongo do all the work.
edit, adding code to make it more clear
var ViewedSchema = new Schema({
coupleId: {type: Number, required: true}
});
var UserSchema = new Schema({
name : { type: String, trim: true, required: true}
, partnerId : { type: Number}
, viewed : [ViewedSchema]
});
code to view partners/users that have not been viewed before
async.parallel([
function(callback) {
//gets all the users/partners
User.find({}, function(err, users) {
var allPartners = [];
users.forEach(function(user){
if(allPartners.indexOf(user.partnerId) == -1) {
allPartners.push(user.partnerId);
}
});
callback(null, allPartners);
});
},
function(callback) {
//gets all the users/partners i have already viewed
var votedPartners = [];
User.findById(id, function(err, user) {
user.viewed.forEach(function(user){
votedPartners.push(user.coupleId);
});
callback(null, votedPartners);
});
}
],
function(err, result) {
//gets the differences between the 2 arrays
function exists(element) {
return (result[1].indexOf(element) == -1);
}
var showPartners = result[0].filter(exists);
User.find({partnerId: showPartners[0]}, function(err, user){
var json = {objects: user};
res.render('index', json);
});
});
};
I'm not sure what you mean by fresh or new users, exactly, but have you loked at the distinct() command? You can use it to get all the unique viewed user IDs for all the users in the collection, which is what it sounds like you want to do. See
http://docs.mongodb.org/manual/reference/method/db.collection.distinct/
From the documentation:
Return an array of the distinct values of the field sku in the subdocument item from all documents in the orders collection:
db.orders.distinct( 'item.sku' )
If you give an example of your current document schema, I could try to write the exact query for you.
Edit: You can use $nin to find the userIds that are not in a given list. Here is an example I set up in my local Mongo:
> db.dating.insert({"userId":100,"viewedId":["200","201"]})
> db.dating.findOne()
{
"_id" : ObjectId("5398d81799b228e88aef2441"),
"userId" : 100,
"viewedId" : [
"200",
"201"
]
}
> db.dating.insert({"userId":200,"viewedId":[""]})
> db.dating.insert({"userId":201,"viewedId":[""]})
> db.dating.insert({"userId":202,"viewedId":[""]})
> db.dating.insert({"userId":203,"viewedId":[""]})
> db.dating.insert({"userId":204,"viewedId":[""]})
> db.dating.insert({"userId":205,"viewedId":[""]})
> db.dating.find({"userId":{$nin: [200,201]}})
{ "_id" : ObjectId("5398d81799b228e88aef2441"), "userId" : 100, "viewedId" : [ "
200", "201" ] }
{ "_id" : ObjectId("5398d84099b228e88aef2444"), "userId" : 202, "viewedId" : [ "
" ] }
{ "_id" : ObjectId("5398d84799b228e88aef2445"), "userId" : 203, "viewedId" : [ "
" ] }
{ "_id" : ObjectId("5398d85699b228e88aef2446"), "userId" : 204, "viewedId" : [ "
" ] }
{ "_id" : ObjectId("5398d85c99b228e88aef2447"), "userId" : 205, "viewedId" : [ "
" ] }

Resources