I'm trying to figure out if I can make my MongoDB queries (fron Node.js) more quicker and more efficient.
Basically I have an array of "Players", each Player has a "player_id" property. I want to iterate over each Player and use the player_id to find data about the Player in a MongoDB database.
Because of the Asynchronous nature of Node.js I have to guarantee that when I send in a query to the database, the data that I get back corresponds to the player_id that was used to query.
So far I have the following code (that I simplified). What I'm most interested in is how I can achieve this without using a for loop. Thanks.
var playersArray = new Array({"player_id":1234567}, {"player_id":9847621}, {"player_id":0946783}, {"player_id":8712890});
queryDataForPlayers(playersArray, function(data){
//Done - Each Player now has a "new_data_property" property
})
function queryDataForPlayers(playersArray){
var newPlayersArray = new Array();
var counter = 0;
for(var i=0; i<playersArray.length; i++)
{
retrievePlayerData(playersArray[i].player_id, playersArray[i], function(err,data)
{
newPlayersArray.push(data);
if(++counter == playersArray.length)
{
callback(newPlayersArray);
}//end if
});
}
}
var Schema = mongoose.model('Schema');
var ObjectID = require('mongodb').ObjectID;
function retrievePlayerData(playerID, obj, callback){
Schema.find({_id:ObjectID(String(playerID))}, function(err,data){
obj["new_data_property"] = data;
callback(err,obj);
});
}
I can't really test this, but you can pass in an array of player ID's directly to mongo, and get a document set with the related data back in just one query, something like
var playersArray = new Array({"player_id":1234567}, {"player_id":9847621}, {"player_id":0946783}, {"player_id":8712890});
var Schema = mongoose.model('Schema');
var ObjectID = require('mongodb').ObjectID;
function queryDataForPlayers(playersArray, callback){
var player_ids = playersArray.map(function(player) {
return ObjectID(String(player.player_id));
});
Schema.find({
'_id': { $in: player_ids}
}, function(err, docs){
callback(err, docs);
});
}
Use $in operator... You can use it like
Schema.find({_id:{$in:[array_of_playerid]} }).exec(function(error,results)){
}
Related
I'm trying to create a database with "users" and their data in it. Strangely it doesn't put() new variables in it when I try to for the third time. To do all this I create a local database dblocal and replicate this DB to the remote db called dbremote. At first I create a document with one variable.
function newuser() {
if (window.document.consent_form.consent_to_share.value) {
var id = "p" + Date.now() + "-" + Math.floor(Math.random() * 10000);
var dblocal = new PouchDB(id);
var consenttoshare = window.document.consent_form.consent_to_share.value;
document.cookie = id;
var dbremote = 'http://localhost:5984/experiment';
dblocal.put({
_id: id,
consent: consenttoshare
});
dblocal.replicate.to(dbremote, {live: true});
}
}
This all worked well, in another js file I'm trying to add a variable to the same document by executing the following function putdb(). Im doing this in the following way (as said in their documentation is the right way):
function putdb () {
if (document.cookie){
var id = document.cookie;
var loggedin = "True";
var dblocal = new PouchDB(id);
dblocal.get(id).then(function (doc) {
doc.loggedin = loggedin;
return dblocal.put(doc);
}).then(function () {
return dblocal.get(id);
}).then(function (doc) {
console.log(doc);
var dbremote = 'http://localhost:5984/experiment';
dblocal.replicate.to(dbremote, {live: true});
});
}
}
This succesfully added the variable loggedin to the document as I wanted. However upon trying to add information to this document for the third time (again in another js file), nothing happens. I used exactly the same approach as before but only use different variables.
function putdb (checked) {
if (document.cookie) {
var id = document.cookie;
var checkedlist = [];
for (i = 0; i < checked; i++) {
checkedlist.push($("input[type=checkbox]:checked")[i].value)
}
var playlistname = document.getElementById("playlistname").value;
var dblocal = new PouchDB(id);
dblocal.get(id).then(function (doc) {
doc.checkedlist = checkedlist;
doc.playlistname = playlistname;
return dblocal.put(doc);
}).then(function () {
return dblocal.get(id);
}).then(function (doc) {
console.log(doc);
var dbremote = 'http://localhost:5984/experiment';
dblocal.replicate.to(dbremote, {live: true});
});
}
}
I checked all variables, they are correct.
I tried plain text variables.
The script does run.
I tried to add information to the document the way I did the first time.
None of all this seems to add another variable to the document as I wanted in the last function. I think it has to do with the way pouchDB works which I don't know. help is much appreciated!
There are a number of problems in your code that results in bad usage of PouchDB, and may lead to problems.
First of all, it does not make a lot of sense to give your document the same id as the name of your database. Assuming you want a one database per user approach, there are two approaches you can follow.
Multiple document approach
You can instead make multiple documents within the same database with different id's. For instance, your 'consent' information may be stored like this:
var id = "p" + Date.now() + "-" + Math.floor(Math.random() * 10000);
let dblocal = new PouchDB(id);
document.cookie = id;
let dbremote = 'http://localhost:5984/experiment';
dblocal.put({
_id: "consent",
consent: window.document.consent_form.consent_to_share.value
});
dblocal.replicate.to(dbremote, {live: true});
While your playlist information is stored like this:
dblocal.put({
_id: "playlist",
name: playlistname,
itemsChecked: checkedlist
});
Single-document approach
The second option is to store a single document containing all the information you want to store that is associated to a user. In this approach you will want to fetch the existing document and update it when there is new information. Assuming you named your document global-state (i.e. replace "consent" in the first code snippet with "global-state"), the following code will update a document:
dblocal.get("global-state").then((doc)=>{
doc.loggedIn = true; // or change any other information you want
return dblocal.put(doc);
}).then((response)=>{
//handle response
}).catch((err)=>{
console.log(err);
});
Furthermore, you should only call the
dblocal.replicate.to(dbremote, {live: true});
function once because the 'live' option specifies that future changes will automatically be replicated to the remote database.
I'm trying to get HTML form data, loop it through, change it a bit and insert it to database. I have tried like below app.js.
How can I make callbacks so that formdata what I have modified is available for .create function?
I have searched from everywhere and I always end up in dead end and undefined variable somehow.
app.js:
//Find the day where to save
Day.findById(req.params.id, function(err, day) {
if (err) {
console.log(err);
res.redirect("/diary");
} else {
// Search function to find data with _id
function ingredientIdQuery(reqBodyId) {
var ingQuery = Ingredient.find({_id:reqBodyId});
return dbQuery;
}
// This loops through HTML formdata and formats it for mongoose model
for (var i = 0; i < req.body.amount.length; i++) {
if (req.body.amount[i] !== "") {
var amount = Number(req.body.amount[i]);
var singleMealTempObj = {};
singleMealTempObj.amount = amount;
var _id = req.body.id[i];
var query = ingredientIdQuery(_id);
// Executing the query for the data I need with id
query.exec(function(err, ingr){
if(err) {
return console.log(err);
} else {
singleMealTempObj.ingredient = ingr[0];
singleMealTempArr.push(singleMealTempObj);
}
});
}
}
}
// This inserts data into day
Meal.create(singleMealTempArr, function(err, singleMealObject) {
if (err) {
console.log(err);
} else {
day.meals.push(singleMealObject);
day.save();
res.redirect("/day/" + day._id + "/dayshow");
}
});
});
});
Edit:
Thanks for reply and notices! While I was trying to do everything to get this work I missed those few things like declaring variables. Sorry for that. I threw the towel in to the cage at this point.
flow goes like this:
User sends HTML form data to app.js which is inside object of two arrays (id[] and amount[]). Amount array needs to be looped through if it has value other than 0. Same index id array value is used to fetch data from database. This data what is found from database with id from id[] is used with same index amount[] and it should be saved to mongo.
I can get the values from HTML form ok. but I have tried to make a search in Mongo in a for loop (query.exec in the code) I get the data ok. When I log the data outside the database query, variable is undefined.
I hope this clarifys a bit what I'm trying to achieve.
I'll continue this later... :)
I guess issue originates because of this function.
function ingredientIdQuery(reqBodyId) {
var ingQuery = Ingredient.find({_id:reqBodyId});
return dbQuery;
}
Is find function asynchronous or synchronous?
Also you are returning dbQuery but dbQuery does not seem to be changed inside the function.
Couple I noticed that may fix this:
You never define singleMealTempArr, so when you try to push data to it, you are gonna run into problems.
Your ingredientIdQuery function returns dbquery - which also isn't defined. You actually call it ingQuery. Even so...are you positive that this will return the data that you want?
// lets loop through all the form fields in req.body.amount
for (var i = 0; i < req.body.amount.length; i++) {
// keep going unless the form field is empty
if (req.body.amount[i] !== "") {
// assign all the form fields to the following vars
var amount = Number(req.body.amount[i]);
var singleMealTempObj = {};
singleMealTempObj.amount = amount;
var _id = req.body.id[i];
var query = ingredientIdQuery(_id);
// we are executing the ingredientIdQuery(_id), better
// double-check that this query returns the result we are
// looking for!
query.exec(function(err, ingr){
if(err) {
return console.log(err);
} else {
singleMealTempObj.ingredient = ingr[0];
// now that we've gone through and mapped all the form
// data we can assign it to the singleMealTempArr
// WOOPS! Looks like we forgot to assign it!
singleMealTempArr.push(singleMealTempObj);
}
});
}
}
}
I have a problem where I want to be able to get all the unique cities for a collection, and my code looks something like this:
var mongoose = require("mongoose"),
Schema = mongoose.Schema;
var PersonSchema = new Schema({
name: String,
born_in_city: String
});
var Person = mongoose.model('Person', PersonSchema);
In native MongoDb I could just do db.person.distinct("born_in_city"), but there doesn't seem to be anything equivalent for Mongoose. Is the only option to iterate over all of the documents myself to do this, or is there a better solution?
In an attempt to use the underlying node-mongodb-native as suggested by the answerer I attempted to do this:
mongoose.connection.db.collections(function(err, collections){
collections[0].distinct('born_in_city', function( err, results ){
console.log( err, results );
});
});
However the results is empty and there's no error. I would also prefer to be able to fetch only the needed collection by name rather than have to filter what collections return if at all possible.
Just to give an update for Mongoose 3.x:
MyModel.find().distinct('_id', function(error, ids) {
// ids is an array of all ObjectIds
});
In my program, this code works.
Person.collection.distinct("born_in_city", function(error, results){
console.log(results);
});
by
node.js v0.4.7,
mongoose 1.3.3
I read through the source code and the node-mongodb-native driver is what powers the class. So on the connection object. So after you have done mongoose.connect(mongodb://), you can give this a shot.
if(mongoose.connections.length > 0) {
var nativeconn = mongoose.connections[0].conn;
nativeconn.person.distinct('born_in_city', function(error, results){
});
}
const findBornCity = async() => {
const bornCity = await Person.find().distinct("born_in_city")
return bornCity
}
Here is a question for parse.com gurus.
I am using Parse Javascript API and trying to execute a query over 2 Pointers and cannot get it to work.
So i have following classes: Posts, Users, Groups. Posts has Pointer to Users. Users has a Pointer to Groups.
I need to get all POSTS, where a USER belongs to GROUP, which name starts with "Admin". Here is my code that doesn't work:
var Posts = Parse.Object.extend("Posts");
var Users = Parse.Object.extend("Users");
var Groups = Parse.Object.extend("Groups");
var query = new Parse.Query(Posts);
var innerQueryUsers = new Parse.Query(Users);
var innerQueryGroups = new Parse.Query(Groups);
innerQueryGroups.startsWith("name", "Admin");
innerQueryUsers.matchesQuery("group", innerQueryGroups);
query.matchesQuery("user", innerQueryUsers);
query.find({
success: function(data) {
},
error: function(error){
// here i get error: {code: 102, message: "bad type for $inQuery"}
}
});
Anybody have an idea how to do it right?
Edit - This can be done in one (untested) query by combining a group query and a post query with the same user query...
function postsInGroup(name) {
var groupQuery = new Parse.Query("Group");
groupQuery.equalTo("name", name);
var userQuery = new Parse.Query(Parse.User);
userQuery.matchesQuery("group", groupQuery);
var postQuery = new Parse.Query("Post");
postQuery.matchesQuery("user", userQuery);
return postQuery.find();
}
Call it like this...
postsInGroup("Admin").then(function(posts) {
console.log(JSON.stringify(posts));
}, function(error) {
console.log(JSON.stringify(error));
});
Its not clear what savings there is between this approach and first querying the group. It's likely that parse.com runs the inner queries much as you would. The difference in readability is a matter of taste.
Before putting this on the cloud code I tested it in Angular with success producing the correct console.log responses throughout the program. Since this function manipulates data in the user table it must use the master key and be in cloud code. With this code in the cloud it saves the column 'duty' to the user table but with no data (there is data to be saved, this I am sure of). Moreover, I'm not even sure that the code runs past the first Parse Query as the console.log returns nothing in the Parse Logs. Where am I going wrong?
'use strict';
var express = require('express');
var app = express();
app.use(express.bodyParser());
var _ = require('underscore');
var server = require('http').createServer(app);
Parse.Cloud.define("updateMerchant", function(request, response) {
Parse.Cloud.useMasterKey();
var user = Parse.Object.extend("User")
var merchantQuery = new Parse.Query(Parse.User);
var Offers = Parse.Object.extend("Offer");
var offerQuery = new Parse.Query(Offers);
var Matches = Parse.Object.extend("Matched");
var matchQuery = new Parse.Query(Matches);
var merchantDuty = [];
var merchants = request.params.data;//I confirmed the validity of this a key value pair where the value is an array of objects.
var merchantIds = _.map(merchants, function(n){return n.id});
console.log(merchantIds)
offerQuery.containedIn("user", merchants);
offerQuery.limit(1000);
offerQuery.find({//CODE STOPS RUNNING?!?
success: function (offers) {
var offerIds = _.map(offers, function (n) {
return n.id});
console.log(offers)//this is telling as it does not appear in the Parse log!
var offersBeta = _.map(offers, function (n) {
return _.extend(_.find(n), {id: n.id})});
matchQuery.containedIn("offer", offers);
matchQuery.limit(1000);
matchQuery.find({
success: function (matches) {
var merchantArray = _.map(_.flatten(matches), function (n) {return _.find(n)});
var offers3 = _.map(offersBeta, function (n) {return _.extend(n, {
Matched: _.filter(merchantArray, function (a) {return a.offer.id == n.id})})})
var duty = function (TotalBill, id) {
var promise = new Parse.Promise();
merchantQuery.get(id, {
success: function (merchantBill) {
merchantBill.set("duty", TotalBill);
merchantBill.save().then(function(obj){ console.log(obj); }, function(error){console.log(error)})}})}
merchantDuty.push(duty(_.map(offer9, function(n){return n.TotalBill}), _.map(offer9, function(n){return n.id})));
},
error: function(){console.log(error);
}
})
}
})
//Code begins running again!
return Parse.Promise.when(merchantDuty).then(function() {
response.success("Success");
},
function(error) {response.error("Something is still wrong");
console.log(error);})
})
To be more clear, nothing between offerQuery.find and return Parse.Promise is run.
You need to pass pointers in offerQuery.containedIn("user", merchants);. See this.
Try this:
var _ = require('underscore');
Parse.Cloud.define("updateMerchant", function(request, response) {
Parse.Cloud.useMasterKey();
var merchantDuty = [];
var merchants = request.params.data;//I confirmed the validity of this a key value pair where the value is an array of objects.
// var merchantIds = _.map(merchants, function(n) {return n.id;});
// console.log(merchantIds);
// Since I don't have the merchants request parameter, I'll fake it with some fake users
var fakeMerchants = [{"username":"Batman","objectId":"f7zZkPx7kT","createdAt":"2015-04-07T19:41:25.014Z","updatedAt":"2015-04-07T19:41:25.014Z","__type":"Object","className":"_User"},{"username":"Robin","objectId":"wgG4EfaFN1","createdAt":"2015-04-07T19:41:35.024Z","updatedAt":"2015-04-07T19:41:35.024Z","__type":"Object","className":"_User"}];
// We can get some users like this:
// var fakeMerchantsQuery = new Parse.Query(Parse.User);
// fakeMerchantsQuery.find().then(function(users) {
// console.log(users);
// });
// Since the 'user' column in Offer Class is a pointer, we need to pass merchant pointers.
// Otherwise we get the error "pointer field user needs a pointer value"
// See https://www.parse.com/questions/using-containedin-with-array-of-pointers
var fakeMerchantsPointers = _.map(fakeMerchants, function(merchant) { // TODO change to real merchants
var pointer = new Parse.User();
pointer.id = merchant.objectId;
return pointer;
});
console.log(fakeMerchantsPointers);
var offerQuery = new Parse.Query(Parse.Object.extend("Offer"));
offerQuery.containedIn("user", fakeMerchantsPointers); // TODO change to real merchants
offerQuery.limit(1000);
offerQuery.find().then(function(offers) {
console.log("inside offer query");
console.log(offers);
// Here I assume that the column 'offer' is a Pointer
var matchQuery = new Parse.Query(Parse.Object.extend("Matched"));
matchQuery.containedIn("offer", offers);
matchQuery.limit(1000);
return matchQuery.find();
}).then(function(matches){
console.log("inside matches query");
console.log(matches);
// Add the duty stuff here...
// We must call success or error
response.success("Success");
});
});
Let me know if it worked.
Please note that you shouldn't mix Cloud Code with ExpressJS code. The Cloud Code should be in main.js, and the ExpressJS code in app.js. Then, in Cloud Code main.js call require('cloud/app.js'); if you want the request pass through ExpressJS.
The line return Parse.Promise.when(merchantDuty) is executing before there are any promises in the merchantDuty array (initialized as empty).
So the whole function is terminating before your query find success function.
I think if you create and add query promises to the merchantDuty array you will fix your bug.
I also suggest you to use promise callbacks for the query methods. Like:
query.find().then(function(){
//success
}, function(error){
//error
});
You can then chain them by returning another promise and make the code better structured.