Chaining collection methods / promises in get request Node.js - javascript

I'm trying to get some data from a MongoDB database with the find() method, returning only those documents that contain a specified "room". Then, I want to return all distinct values, of the found array of rooms, whose key is equal to "variety". I tried this in two different ways and I could be way off in my approach. The first way was to chain the collection methods find() and distinct(). This did not work:
This is what the plantList collection looks like:
[
{
"_id": {
"$oid": "56c11a761b0e60030043cbae"
},
"date added": "10/21/2016",
"variety": "Lettuce",
"room": "Room 1"
},
{
"_id": {
"$oid": "56c11a761b0e60030043cbaf"
},
"date added": "10/21/2015",
"variety": "Tomatoes",
"room": "Room 2"
}
]
server.js
//plantList = db collection
var mongojs = require('mongojs');
var MongoClient = require("mongodb").MongoClient;
MongoClient.connect(process.env.MONGOLAB_URI, function(err, db) {
var plantList = db.collection("plantList");
app.get('/gettypesbyroom/:room', function(req, res) {
var roomReq = req.params.room;
plantList
.find({"room":roomReq})
.toArray()
.distinct("variety", function(err, docs) {
if (err) throw err;
res.json(docs);
});
});
});
My second approach was to chain promises with .then() and use underscore.js to select the keys of the array of rooms (also did not work):
app.get('/gettypesbyroom/:room', function(req, res) {
var roomReq = req.params.room;
plantList
.find({"room":roomReq})
.toArray(function(err, docs) {
if (err) throw err;
return docs;
}).then(function(docs) {
_.keys(docs, function(docs) { return docs.variety; });
}).then(function(varieties) {
res.json(varieties); //not inside scope of .get function?
});
});
Is there something I could do differently to make these work or perhaps a different approach altogether?

Try calling it without toArray:
//plantList = db collection
app.get('/gettypesbyroom/:room', function(req, res) {
var roomReq = req.params.room;
plantList
.find({ room: roomReq })
.distinct('type', function(err, docs) {
if (err) throw err;
res.json(docs);
});
});
See How Do I Query For Distinct Values in Mongoose.

You can do it two more ways. One is to make it easier with a projection operator - just project what you need. Eg. if you have a document that looks like:
{
room: 123,
type: 'soundproof_room',
other: 'stuff'
}
...you can project it with a query like this to just select the type:
db.rooms.find({ room: room }, { type: 1 }).toArray();
That would give you an array of objects like this:
let roomTypes = [{type: 'soundproof_room'}, {type: 'windy_room'}, {type: 'soundproof_room'}, {type: 'room without doors'}]
(Obviously, I'm making types up, I don't know what they really are.)
And then use a simple map:
return res.json(
roomTypes
// extract the type
.map(room => room.type)
// filter out duplicates
.filter((type, idx, self) => self.indexOf(type) === idx)
);
(I'm using ES6 arrow fn, hope you can read it, if not: babeljs.io/repl/)
Another thing to try is an aggregation.
db.rooms.aggregate({
// first find your room
$match: { room: room }
},
{
// group by type, basically make distinct types
$group: {
_id: '$type',
count: {$sum: 1} // inc this by 1 for each room of this type
}
});
That one would get you your room, and it would return you only the types of rooms and the count per type, as an added bonus.

Related

How to remove data in object from MongoDB document

The object contains the username and category. Categories is another object that contains categories and costs. How can I remove all key-values ​​in a category object? Now in my code, using deleteOne() I find all the fields of the object named "Bob". I am deleting the entire document named "Bob". And I need to clear the categories in this document, for example, deleteOne({ name: "Bob", category }, and have { name: "Bob", category: {} } output to the console
let users = [
{
name: "Bob",
сategories: {
eat: "20$",
entertainment: "100$",
},
},
];
mongoClient.connect(function (err, client) {
if (err) return console.log(err);
const db = client.db("expensesdb");
db.collection("users").deleteOne({ name: "Bob" }, function (err, result) {
console.log(result);
client.close();
});
});
You can use updateOne method to set сategories as empty object using $set or you can use $unset to unset your сategories data.

How can I add to this schema array with mongoose?

Here's the user schema and the part I want to update is ToDo under User.js (further down). I am attempting to add new data to an array within the db.
data.js
app.post("/data", loggedIn, async (req, res) => {
console.log(req.body.content);
let content = { content: req.body.content };
User.update({ _id: req.user._id }, { $set: req.body }, function (err, user) {
if (err) console.log(err);
if (!content) {
req.flash("error", "One or more fields are empty");
return res.redirect("/");
}
user.ToDo.push(content);
res.redirect("/main");
});
});
User.js
new mongoose.Schema({
email: String,
passwordHash: String,
ToDo: {
type: [],
},
date: {
type: Date,
default: Date.now,
},
})
Originally I was trying the .push() attribute, but I get the error:
user.ToDo.push(content);
^
TypeError: Cannot read property 'push' of undefined
First of all, your problem is the callback is not the user. When you use update the callback is something like this:
{ n: 1, nModified: 1, ok: 1 }
This is why the error is thrown.
Also I recommend specify the array value, something like this:
ToDo: {
type: [String],
}
The second recommendation is to do all you can into mongo query. If you can use a query to push the object, do this instead of store the object into memory, push using JS function and save again the object into DB.
Of course you can do that, but I think is worse.
Now, knowing this, if you only want to add a value into an array, try this query:
var update = await model.updateOne({
"email": "email"
},
{
"$push": {
"ToDo": "new value"
}
})
Check the example here
You are using $set to your object, so you are creating a new object with new values.
Check here how $set works.
If fields no exists, will be added, otherwise are updated. If you only want to add an element into an array from a specified field, you should $push into the field.
Following your code, maybe you wanted to do something similar to this:
model.findOne({ "email": "email" }, async function (err, user) {
//Here, user is the object user
user.ToDo.push("value")
user.save()
})
As I said before, that works, but is better do in a query.

How to find matches in mongodb for all elements in a javascript array?

I am trying to attempt to search all matches in the mongodb database that I have created for each of the different elements in an array. I have the following code
var fiatList = ['EUR', 'AUD', 'BRL', 'CAD', 'CHF', 'CNY', 'CZK', 'DKK'];
function updateFiatTotal() {
fiatList.forEach(function(entry){
console.log('Calculating Fiat total data');
models.Market.find({ quote: { $in: entry } }, function(err, markets) {
if (err) return console.log(err);
console.log('markets');
});
});
}
For some reason this is not returning anything for the markets that I am trying to print out into the database. If anyone could give me pointers on why it is not correctly querying the mongodb database it would be apprecaited
$in expects an array, so you'd have to pass the fiatList as parameter. That is, if you are looking for a document where quote: ['EUR', 'AUD', 'BRL', 'CAD', 'CHF', 'CNY', 'CZK', 'DKK']
models.Market.find({
quote: fiatList
}, function(err, markets) {
if (err) return console.log(err);
console.log('markets');
});
Now if you are looking for a document that could have one of these values. Example: quote:['EUR', 'AUD'], quote:['CZK', 'CHF']
Then you could do this:
fiatList.forEach(function(entry) {
models.Market.find({
quote: entry
}, function(err, markets) {
if (err) return console.log(err);
console.log('markets');
});
});
This way you could get the same document if quote:['CZK', 'CHF'], cause you would get it once for
models.Market.find({
quote: 'CZK'
})
and once for
models.Market.find({
quote: 'CHF'
})
Also if you still don't get any results, check if your collection is called market instead of Market. I've heard before that people created the collection in mongoose with capital but in mongodb it was written lowercase.

Add elements to array using $addtoset in mongodb

I am trying a sample that uses addtoset to update an array inside a collection. The new elements are being added but not as intended. According to addtoset a new element is added only if it is not in the list.
Issue:
It is simply taking whatever element is being added.
here is my code sample
Schema(mongo_database.js):
var category = new Schema({
Category_Name: { type: String, required: true},
//SubCategories: [{}]
Category_Type: { type: String},
Sub_Categories: [{Sub_Category_Name: String, UpdatedOn: { type:Date, default:Date.now} }],
CreatedDate: { type:Date, default: Date.now},
UpdatedOn: {type: Date, default: Date.now}
});
service.js
exports.addCategory = function (req, res){
//console.log(req.body);
var category_name = req.body.category_name;
var parent_category_id = req.body.parent_categoryId;
console.log(parent_category_id);
var cats = JSON.parse('{ "Sub_Category_Name":"'+category_name+'"}');
//console.log(cats);
var update = db.category.update(
{
_id: parent_category_id
},
{
$addToSet: { Sub_Categories: cats}
},
{
upsert:true
}
);
update.exec(function(err, updation){
})
}
Can someone help me to figure this out?
many thanks..
As mentioned already, $addToSet does not work this way as the elements in the array or "set" are meant to truly represent a "set" where each element is totally unique. Additionally, the operation methods such as .update() do not take the mongoose schema default or validation rules into account.
However operations such as .update() are a lot more effective than "finding" the document, then manipulating and using .save() for the changes in your client code. They also avoid concurrency problems where other processes or event operations could have modified the document after it was retrieved.
To do what you want requires making "mulitple" update statements to the server. I'ts a "fallback" logic situation where when one operation does not update the document you fallback to the the next:
models/category.js:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var category = new Schema({
Category_Name: { type: String, required: true},
Category_Type: { type: String},
Sub_Categories: [{Sub_Category_Name: String, UpdatedOn: { type:Date, default:Date.now} }],
CreatedDate: { type:Date, default: Date.now},
UpdatedOn: {type: Date, default: Date.now}
});
exports.Category = mongoose.model( "Category", category );
in your code:
var Category = require('models/category').Category;
exports.addCategory = function(req,res) {
var category_name = req.body.category_name;
var parent_category_id = req.body.parent_categoryId;
Category.update(
{
"_id": parent_category_id,
"Sub_Categories.Sub_Category_Name": category_name
},
{
"$set": { "Sub_Categories.$.UpdatedOn": new Date() }
},
function(err,numAffected) {
if (err) throw error; // or handle
if ( numAffected == 0 )
Category.update(
{
"_id": parent_category_id,
"Sub_Categories.Sub_Category_Name": { "$ne": category_name }
},
{
"$push": {
"Sub_Categories": {
"Sub_Category_Name": category_name,
"UpdatedOn": new Date()
}
}
},
function(err,numAffected) {
if (err) throw err; // or handle
if ( numAffected == 0 )
Category.update(
{
"_id": parent_category_id
},
{
"$push": {
"Sub_Categories": {
"Sub_Category_Name": category_name,
"UpdatedOn": new Date()
}
}
},
{ "$upsert": true },
function(err,numAffected) {
if (err) throw err;
}
);
});
);
}
);
};
Essentially a possible three operations are tried:
Try to match a document where the category name exists and change the "UpdatedOn" value for the matched array element.
If that did not update. Find a document matching the parentId but where the category name is not present in the array and push a new element.
If that did not update. Perform an operation trying to match the parentId and just push the array element with the upsert set as true. Since both previous updates failed, this is basically an insert.
You can clean that up by either using something like async.waterfall to pass down the numAffected value and avoid the indentation creep, or by my personal preference of not bothering to check the affected value and just pass all statements at once to the server via the Bulk Operations API.
The latter can be accessed from a mongoose model like so:
var ObjectId = mongoose.mongo.ObjectID,
Category = require('models/category').Category;
exports.addCategory = function(req,res) {
var category_name = req.body.category_name;
var parent_category_id = req.body.parent_categoryId;
var bulk = Category.collection.initializeOrderBulkOp();
// Reversed insert
bulk.find({ "_id": { "$ne": new ObjectId( parent_category_id ) })
.upsert().updateOne({
"$setOnInsert": { "_id": new ObjectId( parent_category_id ) },
"$push": {
"Sub_Category_Name": category_name,
"UpdatedOn": new Date()
}
});
// In place
bulk.find({
"_id": new ObjectId( parent_category_id ),
"Sub_Categories.Sub_Category_Name": category_name
}).updateOne({
"$set": { "Sub_Categories.$.UpdatedOn": new Date() }
});
// Push where not matched
bulk.find({
"_id": new ObjectId( parent_category_id ),
"Sub_Categories.Sub_Category_Name": { "$ne": category_name }
}).updateOne({
"$push": {
"Sub_Category_Name": category_name,
"UpdatedOn": new Date()
}
});
// Send to server
bulk.execute(function(err,response) {
if (err) throw err; // or handle
console.log( JSON.stringify( response, undefined, 4 ) );
});
};
Note the reversed logic where the "upsert" occurs first but if course if that succeeded then only the "second" statement would apply, but actually under the Bulk API this would not affect the document. You will get a WriteResult object with the basic information similar to this (in abridged form):
{ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }
Or on the "upsert":
{
"nMatched" : 1,
"nUpserted" : 1,
"nModified" : 0,
"_id" : ObjectId("54af8fe7628bee196ce97ce0")
}
Also note the need to include the ObjectId function from the base mongo driver since this is the "raw" method from the base driver and it does not "autocast" based on schema like the mongoose methods do.
Additionally be very careful with this, because it is a base driver method and does not share the mongoose logic, so if there is no connection established to the database already then calling the .collection accessor will not return a Collection object and the subsequent method calls fail. Mongoose itself does a "lazy" instantation of the database connection, and the method calls are "queued" until the connection is available. Not so with the basic driver methods.
So it can be done, it's just that you need to handle the logic for such array handling yourself as there is no native operator to do that. But it's still pretty simple and quite efficient if you take the proper care.

Mongodb save into nested object?

I can't get my req.body inserted into my mongodb collection.
I have this route that triggers the add method and inside I am trying to figure out a query to save the req.body into a nested collection array
router.post('/api/teams/:tid/players', player.add);
add: function(req, res) {
var newPlayer = new models.Team({ _id: req.params.tid }, req.body);
newPlayer.save(function(err, player) {
if (err) {
res.json({error: 'Error adding player.'});
} else {
console.log(req.body)
res.json(req.body);
}
});
}
Here is an example document
[
{
"team_name":"Bulls",
"_id":"5367bf0135635eb82d4ccf49",
"__v":0,
"players":[
{
"player_name":"Taj Gibson",
"_id":"5367bf0135635eb82d4ccf4b"
},
{
"player_name":"Kirk Hinrich",
"_id":"5367bf0135635eb82d4ccf4a"
}
]
}
]
I can't figure out how to insert/save the POST req.body which is something like
{
"player_name":"Derrick"
}
So that that the new req.body is now added into the players object array.
My question is how do I set the mongodb/mongoose query to handle this?
P.S I am obviously getting the error message because I don't think the query is valid, but it's just kind of an idea what I am trying to do.
Something like this is more suitable, still doesn't work but its a better example I guess
var newPlayer = new models.Team({ _id: req.params.tid }, { players: req.body });
If you created a Team model in Mongoose then you could call the in-built method findOneAndUpdate:
Team.findOneAndUpdate({ _id: req.params.tid },
{ $addToSet: { players: req.body} },
function(err, doc){
console.log(doc);
});
You could do findOne, update, and then save, but the above is more straightforward. $addToSet will only add if the particular update in question doesn't already exist in the array. You can also use $push.
The above does depend to an extent on how you have configured your model and if indeed you are using Mongoose (but obviously you asked how it could be done in Mongoose so I've provided that as a possible solution).
The document for $addToSet is at http://docs.mongodb.org/manual/reference/operator/update/addToSet/ with the relevant operation as follows:
db.collection.update( <query>, { $addToSet: { <field>: <value> } } );

Categories

Resources