selecting an nested attribute to update in mongo with node driver - node.js

In the following code using the Node Js Driver for MongoDB, the console log in the callback would log the number of drivers for a particular vehicle. My problem is with trying to increment the number of drivers by one with this code
$inc:{vehicles[vehicle_number].drivers:1}
It's giving me unexpected token errors with the [ and also I'm not even sure if by starting the selector with vehicles it would be acting on the family that's been queried. Can you explain how I might change the code to make the increment not break the function.
families.findAndModify({_id:family_id}, [],
{$inc:{vehicles[vehicle_number].drivers:1}} , function(err, doc) {
console.log(doc.vehicles[vehicle_number].drivers)
})
Update
the vehicles key contains an array of vehicles. In the code above, vehicle_number represents the index in the array. I'm trying to increment the number of drivers in the findAndModify above.
{
"_id" : ObjectId("5143ddf5bcf1bf4ab37da054"),
"name" : "Jones",
"vehicles" : [
{
"make" : "Ford",
"year" : "2001",
"color" : "blue",
"registration" : "xdklde",
"drivers" : 3
},
{
"make" : "Dodge",
"year" : "1992",
"color" : "green",
"registration" : "klrv7z",
"drivers" : 2
},

var selector = {};
selector['vehicles.' + vehicle_number + '.drivers'] = 1;
then you use it in your query:
families.update({'_id':family_id}, {'$inc':selector} , function(err, doc) {
console.log(doc);
});
This should work.

Related

complicated mongoose pull list of data from api and insert into mongodb if it doesn't already exist

I am connecting to the Yelp API using the RapidAPI module in Nodejs. I am able to request a token, connect, and request data, retrieve that data, and insert the relevant information for each result it into mongodb. Here's where it gets complicated...
Let's say I make a Yelp API request and search for bars. I get a list of bars and insert them into the database. Let's say one of these in the list is "Joe's Bar & Grill". One of the fields in my mongodb is "type" and it's an array. So now, this particular document will look something like this:
{
id: 'joes-bar-and-grill',
name: 'Joe\'s Bar & Grill',
type: ['bar']
}
But then I run another request on the Yelp API on "restaurants", and in this list "Joe's Bar & Grill" shows up again. Instead of inserting a new duplicate document into mongodb, I'd like the existing document to end up looking like this:
{
id: 'joes-bar-and-grill',
name: 'Joe\'s Bar & Grill',
type: ['bar', 'restaurant']
}
In addition to this, let's say I run another request again for "bars", and "Joe's Bar & Grill" comes up again. I don't want it to automatically insert "bar" into the type array again, if "bar" already exists in its array.
I've tried findOneAndUpdate with upsert: true and a $push of new data into the array, but I cannot get it to work at all. Does anyone have any ideas?
You can use findOneAndUpdate, combined with $addToSet (to make sure that an entry in the array only exists once) and $each (to allow passing arrays to $addToSet):
Bar.findOneAndUpdate({ id : 'joes-bar-and-grill' }, {
id : 'joes-bar-and-grill',
name : 'Joe\'s Bar & Grill',
$addToSet : { type : { $each : [ 'restaurant' ] } }
}, { upsert : true })
EDIT: now that you posted your entire code, the problem becomes more obvious.
For one, I'm not sure if the third and fourth arguments that you're passing to Location.update() make sense. As far as I know, the third should be an option object, and the fourth an async function.
Secondly, it looks like you're just ignoring any update errors.
And lastly, this isn't going to work:
for (var i = 0; i < payload.businesses.length; i++) { Location.update(...) }
Because Location.update() is asynchronous, the i variable will get clobbered (you should browse around on SO to find the explanation for that; for example, see this question).
You're going to need a library that will provide you with better async support, and preferably one that will also help limiting the number of update queries.
Once such library is async, and using it, your code would become something like this:
const async = require('async');
...
async.eachLimit(payload.businesses, 5, function(business, callback) {
Location.update({ yelpID : business.id }, {
name : business.name,
latitude : business.location.latitude,
longitude : business.location.longitude,
address1 : business.location.address1,
address2 : business.location.address2,
address3 : business.location.address3,
city : business.location.city,
state : business.location.state,
zip_code : business.location.zip_code,
country : business.location.country,
timezone : 'CST'
$addToSet : { type : 'bar' }
}, { upsert : true }, callback);
}, function(err) {
if (err) {
console.error(err);
} else {
console.log('All documents inserted');
}
});
You may use $addToSet operator
The $addToSet operator adds a value to an array unless the value is
already present, in which case $addToSet does nothing to that array.
$addToSet only ensures that there are no duplicate items added to the
set and does not affect existing duplicate elements. $addToSet does
not guarantee a particular ordering of elements in the modified set.
If the field is absent in the document to update, $addToSet creates
the array field with the specified value as its element.
If the field is not an array, the operation will fail.
The below solution assumes that on each update, you receive a single type and not an array. If the input document is an array itself, you may use robertklep's solution with $each operator
db.mycoll.update(
{ "id" : "joes-bar-and-grill" },
{
$set:{
name : 'Joe\'s Bar & Grill',
},
$addToSet : { type : 'restaurant' }
},
true, false);
I have also used $set operator.
The $set operator replaces the value of a field with the specified
value.
The $set operator expression has the following form:
{ $set: { field1: value1, ... } }
Here is the mongo shell output to explain it further :
> db.mycoll.find({ "id" : "joes-bar-and-grill" });
// NO RESULT
> db.mycoll.update(
... { "id" : "joes-bar-and-grill" },
... {
... $set:{
... name : 'Joe\'s Bar & Grill',
... },
... $addToSet : { type : 'restaurant' }
... },
... true, false);
WriteResult({
"nMatched" : 0,
"nUpserted" : 1,
"nModified" : 0,
"_id" : ObjectId("58e719b4d543c5e30d615d59")
})
// INSERTED A NEW DOCUMENT AS IT DOES NOT EXIST
> db.mycoll.find({ "id" : "joes-bar-and-grill" }); // FINDING THE OBJECT
{ "_id" : ObjectId("58e719b4d543c5e30d615d59"), "id" : "joes-bar-and-grill", "name" : "Joe's Bar & Grill", "type" : [ "restaurant" ] }
> db.mycoll.update(
... { "id" : "joes-bar-and-grill" },
... {
... $set:{
... name : 'Joe\'s Bar & Grill',
... },
... $addToSet : { type : 'bar' }
... },
... true, false);
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
// UPDATING THE DOCUMENT WITH NEW TYPE : "bar"
> db.mycoll.findOne({ "id" : "joes-bar-and-grill" });
{
"_id" : ObjectId("58e719b4d543c5e30d615d59"),
"id" : "joes-bar-and-grill",
"name" : "Joe's Bar & Grill",
"type" : [
"restaurant",
"bar"
]
}

mongoose $match wont return document

I use two ways to retrieve documents from my collection, the first one:
db.comments.find({"nid" : "req.body.data"});
returns many doc like:
{
"nid" : 20404,
"_id" : ObjectId("5638ba331294943d3d0a092b"),
"uid" : 1937,
"posted" : ISODate("2015-11-03T13:44:19.811Z"),
"text" : "txt",
"title" : "Test nid 2",
"stars" : 3,
"__v" : 0
}
,
And for another query I need to use aggregate and the query:
var pipleline = [
{$match: {nid:req.body.data}}
];
Comments.aggregate(pipleline, function(err, rank){
if(err) {
res.send("Error", String(err));
}
res.send(rank);
});
Returns [] - empty array.
Any ideas?
You can use the built in function chaining mongoose provides. Aside from match, it also has sort, project, group, and few others I don't know off the top of my head. More info here
Comments.aggregate().match({nid:req.body.data})
.exec(function(err,rank){
if(err) {
res.send("Error", String(err));
}
res.send(rank);
});

Need to "build" a "key" name in Mongoskin request

I am working on a Node.js app, using Mongoskin and Express.js.
First, here is a simple overview of the MongoDB collection I'm working with :
db.profiles.find()
{ "_id" : ObjectId("5559e6ad8da0dc030010cf64"),
"userid" : "63e9c530-fd60-11e4-a30c-f3b609480e30",
"emailaddr" : { "value" : "x#y.fr", "share" : false },
"fullname" : { "value" : "Azerty Ytreza", "share" : true },
"telnumber" : { "value" : "0606060606", "share" : true }
As you can see, I'm storing multiple objects, following the same architecture (value + boolean)
Depending on what the user will want to share / don't share anymore, I will need to update the "share" value of the good Object.
First, I can't find out how to modify a value stored in an Object.
Referring to this : Modify nested Object value , I thought I could do like this in my Mongoskin requests :
db.collection.update( { _id:...} , { $set: { some_key.param2 : new_info } }
In this case, Node.js is reporting an error, saying "SyntaxError: Unexpected token '.' ".
The thing is that as I said earlier, depending on what the user will want to modify, I won't modify the same "key".
So, I need to build the key name. Example: if the user wants to modify the "share" value of his email address, I will need to update emailaddr.share. But how can I do this using Mongoskin?
I tried different solutions, but it's impossible to do things like :
var key = "emailaddr",
newVal = "true";
key += ".share";
db.collection.update( { _id: ... } { $set: { key : newval } }
Say you want to change the share status of the fullname property :
> var property = "fullname"
> var value = false
You have to dynamically build the object {"fullname.share": false}ยน :
> var updt = {}
> updt[property + ".share"] = value
Then use that object as the parameter to $set:
> db.test.update({"_id" : ObjectId("5559e6ad8da0dc030010cf64")},
... {$set: updt})
// ^^^^
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
1 As a side note, as explained in the doc the quotes (" or ') are mandatory around the name of an object property if it contains a . -- hence the syntax error you mentioned in your question.

Learning Map-Reduce in MongoDB

While searching through the internet, I found out that joins can be emulated in mongodb through the map-reduce function. Going through the docs was confusing.
I have two collections: one with a list of friends of one user. And the other collection is of all the users. I want to fetch the profile pictures of all the friends. how do I create a mongodb query to get the desired results?
The USERS collection:
{
"_id" : ObjectId("524c194a6e3715ce0a000001"),
"email" : "qwerty#abc.com",
"password" : "",
"phone" : "",
"salt" : "",
"upic" : "someuser2fd2751259bb7519d7b760ffee9b7fce203ad1f34.jpg",
"username" : "someuser2"
}
{
"_id" : ObjectId("524be475fafb35480a000001"),
"email" : "",
"password" : "",
"phone" : "",
"salt" : "",
"upic" : "amitverma2522b7a52e054c350f78fd7f3558919f2e2dab58.jpg",
"username" : "amitverma"
}
The friends of each user collection:
{
"_id" : ObjectId("526547ed2389630000000001"),
"friends" : [
{
"_id" : ObjectId("524be475fafb35480a000001"),
"username" : "amitverma"
},
{
"_id" : ObjectId("524be475fafb35480a000001"),
"username" : "someuser2"
}
],
"upic" : "macbookfd2751259bb7519d7b760ffee9b7fce203ad1f34.jpg",
"username" : "someuser"
}
Help would be appreciated.
There are no official docs for this as it's not a recommended best practice. It's complex that you need to do multiple passes carefully outputting the same results into the same collection.
You'd be better served by gathering the list of friends and using the $in operator (reference) to fetch the users and projecting the results to only include the fields you require (like the image).
Ideally, you'd cache those results locally to avoid needlessly requesting image paths. Following is untested code that should work in the shell:
db.friends.find({ username: 'someuser'}).forEach(friend_list) {
// this would gather the list of friend's _ids
// the _id will be passed as an array for the $in operator
var friends = friend_list.friends.map(function(friend) { return this._id; });
// gather up the images for each of the friends
var upics = db.users.find({_id : { $in : friends }},
{ _id: 1, upic: 1 }).toArray();
// now, do something with upics -- outside of the MongoDB shell, this will
// return asynchronously ....
});

Node.js and MongoDB Use results from one query in another

I have created a node.js module that can already query MongoDB for a set of documents using a find and output those results to JSON. My question is, knowing that node.js is asynchronous, how can I use the results from this query (items) to create a query that goes back to MongoDB to find another set of documents. This query basically returns a list of employee ids that can be used to query documents containing information on those employees(i.e. firstName, lastName etc.). Then output those results instead as JSON. The first query is basically saying, give me all of the employees that can be viewed by a particular user. I then need to take the employee ids and do a query on another set of documents that contains those individuals information, like you see below.
Here are the two documents schema:
Employee
{
"_id" : ObjectId("5208db78ecc00915e0900699"),
"clientId" : 1,
"employeeId" : "12345",
"lastName" : "DOE",
"firstName" : "JOHN",
"middleName" : "A",
"badge" : "8675309",
"birthDate" : "10/12/1978"
}
Users an employee can access (User Cache)
{
"_id" : ObjectId("520920a99bc417b7c5e36abf"),
"clientSystem" : "SystemX",
"customerNumber" : "1",
"clientUserId" : "jdoe3",
"securityCode" : "authorize",
"employeeId" : "12345",
"creationDate" : "2013-Aug-12 13:51:37"
}
Here is my code:
exports.employeeList = function(req, res) {
console.log(req.params);
var clientSystem = req.query["clientSystem"];
var clientUserId = req.query["clientUserId"];
var customerNumber = req.query["customerNumber"];
var securityCode = req.query["securityCode"];
if (clientSystem != null && clientUserId != null && customerNumber != null && securityCode != null){
db.collection('ExtEmployeeList', function(err, collection){
collection.find({'clientSystem': clientSystem, 'clientUserId':clientUserId, 'customerNumber':customerNumber, 'securityCode': securityCode}).toArray(function (err, items){
console.log(items);
res.jsonp(items);
});//close find
});//close collection
}//close if
else {
res.send(400);
}//close else
};//close function
What you're wanting to do is possible, but probably not the most effective use of Mongo. I tend to design Mongo documents around how the data will actually be used. So if I needed the user's names to show up in a list of users I can view, I would embed that data so I don't have to do multiple round trips to mongo to get all the information I need. I would do something like the following:
{
"_id" : ObjectId("520920a99bc417b7c5e36abf"),
"clientSystem" : "SystemX",
"customerNumber" : "1",
"clientUserId" : "jdoe3",
"securityCode" : "authorize",
"employeeId" : "12345",
"creationDate" : "2013-Aug-12 13:51:37"
"employee": {
"_id" : ObjectId("5208db78ecc00915e0900699"),
"clientId" : 1,
"employeeId" : "12345",
"lastName" : "DOE",
"firstName" : "JOHN",
"middleName" : "A",
"badge" : "8675309",
"birthDate" : "10/12/1978"
}
}
Yes, you are duplicating data but you're dramatically reducing the number of round trips to the database. This is typically the tradeoff you make when using document based databases since you can't join tables.

Resources