Find and update child of child in MongoDB - node.js

Let's take an exemple :
listCollection = {
id_list:"111",
child_list:[{
id_list:"222",
child_list:[{
id_list:"333",
child_list:[{
// and it can be more
}]
}]
}]
}
As you see, it is always the same object inside the same object. The initial object is :
var list = {
id_list: "string",
child_list:[]
};
Using mongoDB, I would like to Find, Update or Push list anywhere I want inside the collection, only by knowing the id_list.
This is the most far I was :
db.collection("listCollection").findOneAndUpdate({
"child_list.id_list": "listId"
}, {
"$push": {
"child_list.$.child_list": newList
}
}, function(err, success) {
if (err) {
console.log(err);
} else {
console.log(success);
}
})
It works fine for the first level of child, but not more.
I am pretty new to Node JS + mongoDB, can you help me a little bit with this ? Thank you :)

I have found my way through this. Not a really beautiful one, but a working one.
Let's retake my exemple :
listCollection = {
child_list:[{
child_list:[{
child_list:[
]
}]
}]
}
I want to add a new child_list at the end of the hierarchy. At the end, I want my collection to look like that :
listCollection = {
child_list:[{
child_list:[{
child_list:[{
child_list:[
]
}]
}]
}]
}
Yeah, it is kind of a dumb exemple but it is a simple one :)
So here is the object I want to insert :
var theNewChildList= {
child_list:[]
};
Then, I will need a kind of "route" of where I want to put theNewChildList.
Here, I want to push it at listCollection.child_list[0].child_list[0].child_list
So I just use that string :
var myRoute = "child_list.0.child_list.0.child_list";
Then, I prepare your mongoDB query like that :
var query = {};
query[myRoute] = theNewChildList;
and finally, I do the query :
db.collection("myCollectionOfList").findOneAndUpdate(
{}, // or any condition to find listCollection
{"$push": query},
function(err, success) {
if (err) {
console.log(err);
} else {
console.log(success);
}
})
And it works.
Note that you have many ways to prepare the var myRoute dynamically.
I you have other ideas, please share :)

Related

How do I update data in mongo

So I am currently working on a project with mongodb and nodejs and I was wondering, how can you update data in mongodb via nodejs? My problem is that I want to keep the old data and add new. For example, here is the data currently in my mongodb
{
"_id" : ObjectId("5a1c0c1c3b147ec2e31cceb3"),
"event_id" : "1",
"event_medium" : "null",
"event_tags" : ["#JustTesting"]
}
So I want to add new data to the event_tags array and still keep the old data.
So for example the end result would be this:
{
"_id" : ObjectId("5a1c0c1c3b147ec2e31cceb3"),
"event_id" : "1",
"event_medium" : "null",
"event_tags" : ["#JustTesting", "#Test", "#Something"]
}
You should use the update function of MongoDB for that. MongoDB knows different update operators, in your case you may use $push or $pushAll (the second is deprecated):
update one after the other with $push
YourCollection.update({ _id: 'xxx' }, { $push: { event_tags: '#Test' } });
YourCollection.update({ _id: 'xxx' }, { $push: { event_tags: '#Something' } });
or both at once with $pushAll (deprecated now)
YourCollection.update({ _id: 'xxx' }, { $pushAll: { event_tags: ['#Test', '#Something'] } });
To interact with MongoDB form your NodeJS app, I would use a library like this one.
Your starting point is the Update function in CRUD (Create,Read, Update, Delete) operations in Mongodb.
Your node program should have among others the update function where you set the _id field you want to update and load the content fields to be update in 'data' for example as below:
myModel.prototype.update = function (_id, data, callback) {
const query = { _id: this.mongo.ObjectId(_id) };
debug(' update:' + JSON.stringify(query));
this.mongo.collection('mycollection').update(query, data, callback);
};
This piece of code should be put in your Model, if you use MVC pattern.
There is a lot to go.
Honestly I recommend a more deep tutorial like parts 3 and 4 of this one for nodejs and Mongoose (mongo db driver):
MDN tutorial for mongo/node/express
I assume you are using mongoose..
eventModel.findOne({event_id:1 },
function(err, eventObj){
if(err){
//handle error
} else {
if(eventObj === null) {
//event doesnot exist
}
var tagList = eventObj.event_tags;
tagList.push('new_tag1');
tagList.push('new_tag2');
eventObj.event_tags = tagList;
eventObj.save(function(err){
if(err){
//handle error
} else {
//success
}
})

Can't find a easy way out of multiple async for each node js (sails)

So here's the deal :
I have an array of objects with a child array of objects
askedAdvices
askedAdvice.replayAdvices
I'm looping trough the parent and foreach looping trough the childs and need to populate() two obejcts (I'm using sails)
The child looks like :
askedAdvices = {
replayAdvices : [{
bookEnd : "<ID>",
user : "<ID>"
}]
}
So my goal is to cycle and populate bookEnd and user with two findOne query, but I'm going mad with the callback hell.
Here's the Models code :
AskedAdvices Model
module.exports = {
schema : false,
attributes: {
bookStart : {
model : 'book'
},
replayAdvices : {
collection: 'replybookend'
},
user : {
model : 'user',
required : true
},
text : {
type : "text"
}
}
};
ReplyBookEnd Model
module.exports = {
schema : false,
attributes: {
bookEnd : {
model : 'book'
},
user : {
model : 'user',
required : true
},
text : {
type : "text"
}
}
};
Here's the Method code :
getAskedAdvices : function(req, res) {
var queryAskedAdvices = AskedAdvices.find()
.populate("replayAdvices")
.populate("user")
.populate("bookStart")
queryAskedAdvices.exec(function callBack(err,askedAdvices){
if (!err) {
askedAdvices.forEach(function(askedAdvice, i){
askedAdvice.replayAdvices.forEach(function(reply, i){
async.parallel([
function(callback) {
var queryBook = Book.findOne(reply.bookEnd);
queryBook.exec(function callBack(err,bookEndFound) {
if (!err) {
reply.bookEnd = bookEndFound;
callback();
}
})
},
function(callback) {
var queryUser = User.findOne(reply.user)
queryUser.exec(function callBack(err,userFound){
if (!err) {
reply.user = userFound;
callback();
}
})
}
], function(err){
if (err) return next(err);
return res.json(200, reply);
})
})
})
} else {
return res.json(401, {err:err})
}
})
}
I can use the async library but need suggestions
Thanks folks!
As pointed out in the comments, Waterline doesn't have deep population yet, but you can use async.auto to get out of callback hell. The trick is to gather up the IDs of all the children you need to find, find them with single queries, and then map them back onto the parents. The code would look something like below.
async.auto({
// Get the askedAdvices
getAskedAdvices: function(cb) {
queryAskedAdvices.exec(cb);
},
// Get the IDs of all child records we need to query.
// Note the dependence on the `getAskedAdvices` task
getChildIds: ['getAskedAdvices', function(cb, results) {
// Set up an object to hold all the child IDs
var childIds = {bookEndIds: [], userIds: []};
// Loop through the retrieved askedAdvice objects
_.each(results.getAskedAdvices, function(askedAdvice) {
// Loop through the associated replayAdvice objects
_.each(askedAdvice.replayAdvices, function(replayAdvice) {
childIds.bookEndIds.push(replayAdvice.bookEnd);
childIds.userIds.push(replayAdvice.user);
});
});
// Get rid of duplicate IDs
childIds.bookEndIds = _.uniq(childIds.bookEndIds);
childIds.userIds = _.uniq(childIds.userIds);
// Return the list of IDs
return cb(null, childIds);
}],
// Get the associated book records. Note that this task
// relies on `getChildIds`, but will run in parallel with
// the `getUsers` task
getBookEnds: ['getChildIds', function(cb, results) {
Book.find({id: results.getChildIds.bookEndIds}).exec(cb);
}],
getUsers: ['getChildIds', function(cb, results) {
User.find({id: results.getChildIds.userIds}).exec(cb);
}]
}, function allTasksDone(err, results) {
if (err) {return res.serverError(err);
// Index the books and users by ID for easier lookups
var books = _.indexBy(results.getBookEnds, 'id');
var users = _.indexBy(results.getUsers, 'id');
// Add the book and user objects back into the `replayAdvices` objects
_.each(results.getAskedAdvices, function(askedAdvice) {
_.each(askedAdvice.replayAdvices, function(replayAdvice) {
replayAdvice.bookEnd = books[replayAdvice.bookEnd];
replayAdvice.user = users[replayAdvice.bookEnd];
});
});
});
Note that this is assuming Sails' built-in Lodash and Async instances; if you're using newer versions of those packages the usage of async.auto has changed slightly (the task function arguments are switched so that results comes before cb), and _.indexBy has been renamed to _.keyBy.

dynamically add fields to find() mongodb

Hi i am working with mongoDB , i am trying to crate a dynamic object and pass as an argument to the find()
my object is like this
Library
var search = {};
if(data.orderId) {
search["_id"] = { $in: data.orderId.split(",") } ;
}if(data.path) {
search["stops.districtId"] = data.path;
}if(data.special) {
search["special.option"] = { $in: data.special.split(",") } ;
}if(data.userInfo) {
search["UserId"] = data.userInfo;
}
then i will pass my search objet to the query like this
Model
var col = db.collection( CustomerOrderModel.collection() );
col.find(
{
serviceType:data.serviceType,
**search**
}
).skip(data.skip).limit(data.limit).sort(data.sort).toArray(function(err, res) {
if (err) {
reject( err );
} else {
resolve( res );
}
});
the problem here is when i console.log my search object i can see
'special.option': { '$in': [ 'ROUND_TRIP' ] } }
my $in is wrapped with quotes . so my query doesn't work .
if i directly type "special.option": { $in: [ 'ROUND_TRIP' ] } } in my query it is working correctly .
i m trying to built this search object because i have multiple fields to search with complex logic . i don't want to do these in my
model , so i wil create the search object in my library .
is there any possible ways to this , thanks in advance .
You should make the search object part of the query by adding the extra filters into the search object. As you are currently doing
col.find({
serviceType:data.serviceType,
search
})
this is interprated as
col.find({
serviceType:data.serviceType,
{ 'special.option': { '$in': [ 'ROUND_TRIP' ] } }
})
You should be able to add the serviceType filter to your existing search object using the square bracket notation as follows:
search["serviceType"] = data.serviceType;
then you can pass that object in your query:
var col = db.collection( CustomerOrderModel.collection() );
search["serviceType"] = data.serviceType;
col.find(search)
.skip(data.skip)
.limit(data.limit)
.sort(data.sort)
.toArray(function(err, res) {
if (err) { reject( err ); }
else { resolve( res ); }
});
That is not the problem.
console.log({ "special.option": { $in: [ 'ROUND_TRIP' ] } });
gives
{ 'special.option': { '$in': [ 'ROUND_TRIP' ] } }
so this is correct.
In your code you just write **search** in the most critical part, but try this:
search["serviceType"] = data.serviceType;
col.find( search )

Node.js How to republish the data.

We have published items based on list ids , we use myLists variable to filter out the List Id, but this variable is not reactive by nature ,when we try to add the new list , then items of new list are not publishing automatically.
Meteor.publish('subscription_items', function () {
var userName = this.userId ? Meteor.users.find({ _id: this.userId }).fetch()[0].username : null;
var myLists = [];
var sharedListIDs = [];
SharedLists.find({ shared_with: userName }).forEach(function (list) {
sharedListIDs.push(list.list_id);
});
Lists.find({ $or: [{ owner: userName }, { _id: { $in: sharedListIDs } }] }).forEach(function (list) {
myLists.push(list._id);
});
return Items.find({ list_id: { $in: Lists.find({ $or: [{ owner: userName }, { _id: { $in: sharedListIDs } }] }).fetch() } });.
Can we have any way to always publish fresh data. Please help me to resolve this issue. any help/suggestion would appreciate.
As David Weldon pointed out in this answer to my similar question, what you are looking for is a reactive join. By using the package publish-with-relations, I think the publish function you want looks something like this:
Meteor.publish('subscription_items', function() {
return Meteor.publishWithRelations({
handle: this,
collection: Lists,
filter: {owner: this.userId},
mappings: [{collection: SharedLists, key: 'list_id'}]
});
});
Alternatively, as a (dirty) workaround, you could call
Meteor.subscribe('subscription_items');
everywhere where you need the collection to be republished.

How to use 'BatchGetItem' for the NodeJS AWS-SDK for DynamoDB

I am trying to get items out of a DynamoDB table using the Node JS AWS-SDK. The function getItem is working fine but BatchGetItem is harder to use.
I use the official documentation:
http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/Client.html#batchGetItem-property
I am looking for examples on how to use this function correctly, but I can't find any. The code I wrote is:
var params = {
"RequestItems" : {
"Keys" : [
{"HashKeyElement" : { "N" : "1000" } },
{"HashKeyElement" : { "N" : "1001" } }
]
}
}
db.client.batchGetItem(params, function(err, data) {
console.log('error: '+ err);
console.log(jsDump.parse(data));
});
I get a SerializationException: Start of list found where not expected error but as far as my NodeJS and JSON expertise goes, my syntax is correct. But it's confusing:
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/API_BatchGetItems.html
In that syntax example, you have to provide the table name.
I used the dynamo db client version... after an hour of research I managed to make it work...
var params = {
RequestItems: { // map of TableName to list of Key to get from each table
Music: {
Keys: [ // a list of primary key value maps
{
Artist: 'No One You Know',
SongTitle:'Call Me Today'
// ... more key attributes, if the primary key is hash/range
},
// ... more keys to get from this table ...
],
AttributesToGet: [ // option (attributes to retrieve from this table)
'AlbumTitle',
// ... more attribute names ...
],
ConsistentRead: false, // optional (true | false)
},
// ... more tables and keys ...
},
ReturnConsumedCapacity: 'NONE', // optional (NONE | TOTAL | INDEXES)
};
docClient.batchGet(params, function(err, data) {
if (err) ppJson(err); // an error occurred
else ppJson(data); // successful response
});
I feel your pain... AWS documentation is confusing at best. I think it's caused by aging infrastructure and bad technical writing. The nodejs and JSON syntax used by the SDK reminds me of XML structure.
Anyway, I managed to get the BatchGetItem to work after a whole hour. The params should look like below:
{
"RequestItems": {
"<TableName>": {
"Keys": [
{"<HashKeyName>": {"<type>":"<hash key value>"}},
{"<HashKeyName>": {"<type>":"<hash key value>"}},
{"<HashKeyName>": {"<type>":"<hash key value>"}}
]
}
}
}
I believe that you're missing table name. You want this:
var params = {
"RequestItems" : {
"TableName": {
"Keys" : [
{"HashKeyElement" : { "N" : "1000" } },
{"HashKeyElement" : { "N" : "1001" } }
]
}
}
}
I tried all of the solutions here and none of them worked for me, which likely means the NodeJS library has gotten an update. Referencing their better written docs, you should be able to make a request like so:
var params = {
RequestItems: {
'Table-1': {
Keys: [
{
HashKey: 'haskey',
NumberRangeKey: 1
}
]
},
'Table-2': {
Keys: [
{ foo: 'bar' },
]
}
}
};
var docClient = new AWS.DynamoDB.DocumentClient();
docClient.batchGet(params, function(err, data) {
if (err) console.log(err);
else console.log(data);
});
Specifically, providing the type is no longer necessary.
Try this:
db.client.batchGetItem(
{"RequestItems":{
"TableName":{
"Keys":[
{"HashKeyElement" : {"N":"1000"}},
{"HashKeyElement" : {"N":"1001"}}
]
}
}
}, function(err, result){ //handle error and result here });
In your case, the correct answer should be:
var params = {
"RequestItems": {
"<table_name>": {
"Keys": [
{"HashKeyElement" : { "N" : "1000" } },
{"HashKeyElement" : { "N" : "1001" } }
]
}
}
}
Try this, it's untested though:
var params = {
TableName: "tableName",
RequestItems : {
Keys : [
{
HashKeyElement : { N : "1000" }
},
{
HashKeyElement : { N : "1001" }
}
]
}
}

Resources